package services import ( "crypto/tls" "fmt" "log" "net/smtp" "strings" "sync" "github.com/sujit-baniya/fiber-boilerplate/app" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" ) // soporteSendMail envía un correo usando la configuración SMTP del webhook de soporte. // Si no hay SMTP configurado, usa app.Http.Mail como fallback. func soporteSendMail(to, subject, htmlBody string) error { cfg, err := models.GetSoporteWebhookActivo() if err != nil || cfg == nil || cfg.SmtpHost == "" { return app.Http.Mail.Send(to, subject, htmlBody) } from := cfg.SmtpFromAddr if from == "" { from = cfg.EmailDestino } if from == "" { return app.Http.Mail.Send(to, subject, htmlBody) } fromName := cfg.SmtpFromName if fromName == "" { fromName = "Soporte" } port := cfg.SmtpPort if port == 0 { port = 587 } addr := fmt.Sprintf("%s:%d", cfg.SmtpHost, port) msg := []byte(fmt.Sprintf( "From: %s <%s>\r\nTo: %s\r\nSubject: %s\r\nMIME-Version: 1.0\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n%s", fromName, from, to, subject, htmlBody)) if err := enviarPorSMTP(cfg, addr, from, to, msg); err != nil { return fmt.Errorf("SMTP de soporte (%s): %w", addr, err) } return nil } // enviarPorSMTP abre una sola conexión, la asegura según la configuración, // manda el mensaje y cierra con QUIT. // // Antes el camino STARTTLS abría una conexión, hacía StartTLS, autenticaba… y // la descartaba para llamar a smtp.SendMail, que abre otra distinta: la primera // quedaba colgada y el envío real salía por una conexión que podía no estar // autenticada igual. func enviarPorSMTP(cfg *models.SoporteWebhookConfig, addr, from, to string, msg []byte) error { enc := strings.ToLower(strings.TrimSpace(cfg.SmtpEncryption)) var cliente *smtp.Client var err error if enc == "tls" || enc == "ssl" { conn, errDial := tls.Dial("tcp", addr, &tls.Config{ServerName: cfg.SmtpHost}) if errDial != nil { return fmt.Errorf("no se pudo conectar por TLS: %w", errDial) } cliente, err = smtp.NewClient(conn, cfg.SmtpHost) if err != nil { conn.Close() return fmt.Errorf("saludo SMTP rechazado: %w", err) } } else { cliente, err = smtp.Dial(addr) if err != nil { return fmt.Errorf("no se pudo conectar: %w", err) } if enc != "none" { if err := cliente.StartTLS(&tls.Config{ServerName: cfg.SmtpHost}); err != nil { cliente.Close() return fmt.Errorf("STARTTLS rechazado: %w", err) } } } defer cliente.Close() if cfg.SmtpUsername != "" { if err := cliente.Auth(smtp.PlainAuth("", cfg.SmtpUsername, cfg.SmtpPassword, cfg.SmtpHost)); err != nil { return fmt.Errorf("autenticación rechazada para %s: %w", cfg.SmtpUsername, err) } } if err := cliente.Mail(from); err != nil { return fmt.Errorf("el servidor rechazó el remitente %s: %w", from, err) } if err := cliente.Rcpt(to); err != nil { return fmt.Errorf("el servidor rechazó el destinatario %s: %w", to, err) } w, err := cliente.Data() if err != nil { return err } if _, err := w.Write(msg); err != nil { return err } if err := w.Close(); err != nil { return fmt.Errorf("el servidor rechazó el mensaje: %w", err) } return cliente.Quit() } // ProbarEnvioSoporte manda un correo de prueba por el mismo camino que usa el // acuse automático, y devuelve el error tal cual. Es la única forma de saber // por qué no llega: el acuse real se manda en segundo plano. func ProbarEnvioSoporte(destino string) error { if strings.TrimSpace(destino) == "" { return fmt.Errorf("indicá a qué dirección mandar la prueba") } cuerpo := `
Esto es una prueba del envío de soporte.
Si te llegó, el acuse automático de los tickets también va a salir por acá.
` return soporteSendMail(destino, "Prueba de envío de soporte", cuerpo) } // SendSoporteAutoRespuesta envía acuse de recibo automático al crear un ticket por email func SendSoporteAutoRespuesta(ticket *models.ProyectoTicket) { if ticket == nil || ticket.EmailFrom == "" { return } subject := fmt.Sprintf("[Ticket #%d] Recibimos tu solicitud: %s", ticket.ID, ticket.Titulo) mensaje := "Hemos recibido tu solicitud y te responderemos a la brevedad." if app.Http.Database.DB != nil { if cfg, err := models.GetSoporteWebhookActivo(); err == nil && cfg.MensajeAuto != "" { mensaje = cfg.MensajeAuto } } htmlBody := fmt.Sprintf(`Soporte U-site
Hola %s,
%s
Ticket: #%d
Asunto: %s
Mensaje: %s
Este es un mensaje automático. No respondas a este correo.