package mail import ( "crypto/tls" "fmt" "net/smtp" "doormile/config" ) func SendOTPEmail(cfg *config.Config, toEmail, code string) error { subject := "Your Doormile verification code" body := fmt.Sprintf( "Your Doormile verification code is %s.\r\n\r\nIt expires in 5 minutes. If you didn't request this, you can ignore this email.", code, ) msg := []byte(fmt.Sprintf( "From: %s\r\nTo: %s\r\nSubject: %s\r\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=\"utf-8\"\r\n\r\n%s\r\n", cfg.SMTPFrom, toEmail, subject, body, )) auth := smtp.PlainAuth("", cfg.SMTPUser, cfg.SMTPPassword, cfg.SMTPHost) // Port 465 is implicit TLS (encrypted from the first byte); everything else // (587, 25) goes through smtp.SendMail's opportunistic STARTTLS upgrade. if cfg.SMTPPort == "465" { return sendImplicitTLS(cfg, auth, toEmail, msg) } addr := fmt.Sprintf("%s:%s", cfg.SMTPHost, cfg.SMTPPort) return smtp.SendMail(addr, auth, cfg.SMTPFrom, []string{toEmail}, msg) } func sendImplicitTLS(cfg *config.Config, auth smtp.Auth, toEmail string, msg []byte) error { addr := fmt.Sprintf("%s:%s", cfg.SMTPHost, cfg.SMTPPort) conn, err := tls.Dial("tcp", addr, &tls.Config{ServerName: cfg.SMTPHost}) if err != nil { return fmt.Errorf("tls dial failed: %w", err) } defer conn.Close() client, err := smtp.NewClient(conn, cfg.SMTPHost) if err != nil { return fmt.Errorf("smtp client init failed: %w", err) } defer client.Close() if err := client.Auth(auth); err != nil { return fmt.Errorf("smtp auth failed: %w", err) } if err := client.Mail(cfg.SMTPFrom); err != nil { return err } if err := client.Rcpt(toEmail); err != nil { return err } w, err := client.Data() if err != nil { return err } if _, err := w.Write(msg); err != nil { return err } if err := w.Close(); err != nil { return err } return client.Quit() }