feat: SMTP salida propio en webhook de soporte
This commit is contained in:
@@ -15,6 +15,15 @@ type SoporteWebhookConfig struct {
|
||||
ResponderAuto bool `json:"responder_auto" gorm:"column:responder_auto;default:true"`
|
||||
MensajeAuto string `json:"mensaje_auto" gorm:"column:mensaje_auto;type:text"`
|
||||
AsignarA *uint `json:"asignar_a" gorm:"column:asignar_a;index"` // auto-asignar tickets a este user
|
||||
|
||||
// SMTP salida para notificaciones y auto-respuesta
|
||||
SmtpHost string `json:"smtp_host" gorm:"column:smtp_host;size:255"`
|
||||
SmtpPort int `json:"smtp_port" gorm:"column:smtp_port;default:587"`
|
||||
SmtpUsername string `json:"smtp_username" gorm:"column:smtp_username;size:255"`
|
||||
SmtpPassword string `json:"smtp_password" gorm:"column:smtp_password;size:255"` // cifrado AES
|
||||
SmtpEncryption string `json:"smtp_encryption" gorm:"column:smtp_encryption;size:20;default:'tls'"` // tls|starttls|none
|
||||
SmtpFromAddr string `json:"smtp_from_addr" gorm:"column:smtp_from_addr;size:255"`
|
||||
SmtpFromName string `json:"smtp_from_name" gorm:"column:smtp_from_name;size:255"`
|
||||
}
|
||||
|
||||
func (SoporteWebhookConfig) TableName() string { return "soporte_webhook_config" }
|
||||
|
||||
@@ -56,3 +56,14 @@ func TestSoporteWebhookConfigAsignarANil(t *testing.T) {
|
||||
t.Errorf("AsignarA debe ser nil por defecto, got %v", *cfg.AsignarA)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSoporteWebhookConfigSmtpDefaults(t *testing.T) {
|
||||
cfg := SoporteWebhookConfig{Nombre: "SMTP Test"}
|
||||
// Go zero values — los defaults reales los aplica GORM o el controller
|
||||
if cfg.SmtpHost != "" {
|
||||
t.Errorf("SmtpHost debe ser vacío por defecto, got %q", cfg.SmtpHost)
|
||||
}
|
||||
if cfg.SmtpPort != 0 {
|
||||
t.Errorf("SmtpPort debe ser 0 en Go, got %d", cfg.SmtpPort)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,95 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/smtp"
|
||||
"strings"
|
||||
|
||||
"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
|
||||
}
|
||||
auth := smtp.PlainAuth("", cfg.SmtpUsername, cfg.SmtpPassword, cfg.SmtpHost)
|
||||
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))
|
||||
addr := fmt.Sprintf("%s:%d", cfg.SmtpHost, port)
|
||||
enc := strings.ToLower(cfg.SmtpEncryption)
|
||||
if enc == "tls" {
|
||||
tlsCfg := &tls.Config{ServerName: cfg.SmtpHost}
|
||||
conn, err := tls.Dial("tcp", addr, tlsCfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("soporte SMTP TLS: %w", err)
|
||||
}
|
||||
client, err := smtp.NewClient(conn, cfg.SmtpHost)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return fmt.Errorf("soporte SMTP client: %w", err)
|
||||
}
|
||||
defer client.Close()
|
||||
if err = client.Auth(auth); err != nil {
|
||||
return fmt.Errorf("soporte SMTP auth: %w", err)
|
||||
}
|
||||
if err = client.Mail(from); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = client.Rcpt(to); err != nil {
|
||||
return err
|
||||
}
|
||||
w, err := client.Data()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return w.Close()
|
||||
}
|
||||
if enc == "starttls" {
|
||||
tlsCfg := &tls.Config{ServerName: cfg.SmtpHost}
|
||||
conn, err := smtp.Dial(addr)
|
||||
if err != nil {
|
||||
goto fallback
|
||||
}
|
||||
if err = conn.StartTLS(tlsCfg); err != nil {
|
||||
conn.Close()
|
||||
goto fallback
|
||||
}
|
||||
if err = conn.Auth(auth); err != nil {
|
||||
conn.Close()
|
||||
return fmt.Errorf("soporte SMTP STARTTLS auth: %w", err)
|
||||
}
|
||||
return smtp.SendMail(addr, auth, from, []string{to}, msg)
|
||||
}
|
||||
return smtp.SendMail(addr, auth, from, []string{to}, msg)
|
||||
|
||||
fallback:
|
||||
return app.Http.Mail.Send(to, subject, htmlBody)
|
||||
}
|
||||
|
||||
// SendSoporteAutoRespuesta envía acuse de recibo automático al crear un ticket por email
|
||||
func SendSoporteAutoRespuesta(ticket *models.ProyectoTicket) {
|
||||
if ticket == nil || ticket.EmailFrom == "" {
|
||||
@@ -39,7 +121,7 @@ func SendSoporteAutoRespuesta(ticket *models.ProyectoTicket) {
|
||||
</div></body></html>`, ticket.AutorNombre, mensaje, ticket.ID, ticket.Titulo, ticket.Descripcion)
|
||||
|
||||
go func() {
|
||||
if err := app.Http.Mail.Send(ticket.EmailFrom, subject, htmlBody); err != nil {
|
||||
if err := soporteSendMail(ticket.EmailFrom, subject, htmlBody); err != nil {
|
||||
log.Printf("[Soporte] Error enviando auto-respuesta a %s: %v", ticket.EmailFrom, err)
|
||||
} else {
|
||||
log.Printf("[Soporte] Auto-respuesta enviada a %s (ticket #%d)", ticket.EmailFrom, ticket.ID)
|
||||
@@ -78,7 +160,7 @@ func SendSoporteNotifAdmin(ticket *models.ProyectoTicket) {
|
||||
</div></body></html>`, ticket.AutorNombre, ticket.EmailFrom, ticket.Titulo, ticket.Descripcion, absAppURL(""), ticket.ID)
|
||||
|
||||
go func() {
|
||||
if err := app.Http.Mail.Send(adminEmail, subject, htmlBody); err != nil {
|
||||
if err := soporteSendMail(adminEmail, subject, htmlBody); err != nil {
|
||||
log.Printf("[Soporte] Error notificando admin: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
Reference in New Issue
Block a user