package email import ( "context" "fmt" "net" "net/smtp" "strings" ) var smtpSendMail = smtp.SendMail type smtpTransport struct { host string port string user string password string } func newSMTPTransport(host, port, user, password string) *smtpTransport { if strings.TrimSpace(port) == "" { port = "587" } return &smtpTransport{host: host, port: port, user: user, password: password} } func (s *smtpTransport) Name() string { return ProviderSMTP } func (s *smtpTransport) Send(ctx context.Context, from FromIdentity, msg Outbound) error { _ = ctx to := strings.TrimSpace(msg.To) if to == "" { return ErrInvalidRecipient } if hasHeaderBreak(to) { return ErrInvalidRecipient } fromAddr := strings.TrimSpace(from.Email) if fromAddr == "" { return ErrInvalidFrom } if hasHeaderBreak(fromAddr) || hasHeaderBreak(from.Name) { return ErrInvalidFrom } if hasHeaderBreak(msg.Subject) { return fmt.Errorf("invalid subject") } replyTo := strings.TrimSpace(from.ReplyTo) if replyTo != "" && hasHeaderBreak(replyTo) { return fmt.Errorf("invalid reply-to") } addr := net.JoinHostPort(s.host, s.port) boundary := "descrybe_mkt_7f3a" var body strings.Builder body.WriteString(fmt.Sprintf("From: %s\r\n", from.Formatted())) body.WriteString(fmt.Sprintf("To: %s\r\n", to)) body.WriteString(fmt.Sprintf("Subject: %s\r\n", msg.Subject)) if replyTo != "" { body.WriteString(fmt.Sprintf("Reply-To: %s\r\n", replyTo)) } for k, v := range msg.Headers { k = strings.TrimSpace(k) v = strings.TrimSpace(v) if k == "" || v == "" { continue } if hasHeaderBreak(k) || strings.Contains(k, ":") { return fmt.Errorf("invalid header name") } if hasHeaderBreak(v) { return fmt.Errorf("invalid header value") } body.WriteString(fmt.Sprintf("%s: %s\r\n", k, v)) } body.WriteString("MIME-Version: 1.0\r\n") if strings.TrimSpace(msg.HTML) != "" { body.WriteString(fmt.Sprintf("Content-Type: multipart/alternative; boundary=%s\r\n\r\n", boundary)) body.WriteString(fmt.Sprintf("--%s\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n%s\r\n", boundary, msg.Text)) body.WriteString(fmt.Sprintf("--%s\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n%s\r\n", boundary, msg.HTML)) body.WriteString(fmt.Sprintf("--%s--\r\n", boundary)) } else { body.WriteString("Content-Type: text/plain; charset=UTF-8\r\n\r\n") body.WriteString(msg.Text) } var auth smtp.Auth if s.user != "" { auth = smtp.PlainAuth("", s.user, s.password, s.host) } if err := smtpSendMail(addr, auth, fromAddr, []string{to}, []byte(body.String())); err != nil { return fmt.Errorf("smtp send failed") } return nil } func hasHeaderBreak(v string) bool { return strings.ContainsAny(v, "\r\n") }