package services import ( "crypto/tls" "fmt" "net/smtp" "strings" ) // DatosSMTP es lo mínimo para mandar un correo por el servidor de cualquiera: // lo usa el buzón de soporte con su configuración y el canal de correo de un // agente con las credenciales del cliente. Una sola implementación — el bug de // STARTTLS que abría dos conexiones ya se pagó una vez. type DatosSMTP struct { Host string Puerto int Encriptado string // ssl | tls | starttls | none Usuario string Password string Desde string DesdeNombre string } // EnviarPorSMTP abre una sola conexión, la asegura según la configuración, // manda el mensaje y cierra con QUIT. func EnviarPorSMTP(d DatosSMTP, para, asunto, cuerpoHTML string) error { if d.Host == "" || d.Desde == "" || para == "" { return fmt.Errorf("faltan datos para el envío (servidor, remitente o destinatario)") } puerto := d.Puerto if puerto == 0 { puerto = 587 } nombre := d.DesdeNombre if nombre == "" { nombre = d.Desde } addr := fmt.Sprintf("%s:%d", d.Host, puerto) 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", nombre, d.Desde, para, asunto, cuerpoHTML)) enc := strings.ToLower(strings.TrimSpace(d.Encriptado)) var cliente *smtp.Client var err error if enc == "tls" || enc == "ssl" { conn, errDial := tls.Dial("tcp", addr, &tls.Config{ServerName: d.Host}) if errDial != nil { return fmt.Errorf("no se pudo conectar por TLS: %w", errDial) } cliente, err = smtp.NewClient(conn, d.Host) 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: d.Host}); err != nil { cliente.Close() return fmt.Errorf("STARTTLS rechazado: %w", err) } } } defer cliente.Close() if d.Usuario != "" { if err := cliente.Auth(smtp.PlainAuth("", d.Usuario, d.Password, d.Host)); err != nil { return fmt.Errorf("autenticación rechazada para %s: %w", d.Usuario, err) } } if err := cliente.Mail(d.Desde); err != nil { return fmt.Errorf("el servidor rechazó el remitente %s: %w", d.Desde, err) } if err := cliente.Rcpt(para); err != nil { return fmt.Errorf("el servidor rechazó el destinatario %s: %w", para, 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() }