package email import ( "context" "crypto/tls" "fmt" "net" "net/http" "net/smtp" "strings" ) // SMTPConfig holds SMTP connection info type SMTPConfig struct { Host string Port int // 465, 587, 25 Username string Password string From string Encryption string // "ssl", "starttls", "none" Recipient string } // EmailMessage represents an email type EmailMessage struct { Subject string Body string // HTML or plain text IsHTML bool } // SendMail sends the email using the given config func SendMail(config SMTPConfig, message EmailMessage, transport *http.Transport) error { // 验证配置 if err := validateConfig(config); err != nil { return err } addr := fmt.Sprintf("%s:%d", config.Host, config.Port) toList := parseRecipients(config.Recipient) // 构建邮件内容 msg, err := buildMessage(config, message, toList) if err != nil { return err } // 根据加密类型选择发送方式 switch strings.ToLower(config.Encryption) { case "ssl": return sendWithSSL(config, addr, toList, msg, transport) case "starttls", "tls": return sendWithStartTLS(config, addr, toList, msg, transport) case "none": return sendPlaintext(config, addr, toList, msg, transport) default: return fmt.Errorf("unsupported encryption type: %s", config.Encryption) } } // 验证配置有效性 func validateConfig(config SMTPConfig) error { if config.Host == "" { return fmt.Errorf("SMTP host is required") } if config.Port <= 0 { return fmt.Errorf("invalid SMTP port: %d", config.Port) } if config.Username == "" { return fmt.Errorf("SMTP username is required") } if config.Password == "" { return fmt.Errorf("SMTP password is required") } if config.From == "" { return fmt.Errorf("SMTP from address is required") } if config.Recipient == "" { return fmt.Errorf("SMTP recipient is required") } if !isValidEncryption(config.Encryption) { return fmt.Errorf("invalid encryption type: %s. Allowed: ssl, starttls, none", config.Encryption) } return nil } // 检查加密类型是否有效 func isValidEncryption(enc string) bool { enc = strings.ToLower(enc) return enc == "ssl" || enc == "starttls" || enc == "none" || enc == "tls" } func parseRecipients(recipient string) []string { toList := strings.Split(recipient, ",") for i := range toList { toList[i] = strings.TrimSpace(toList[i]) } return toList } // 构建邮件内容 func buildMessage(config SMTPConfig, message EmailMessage, toList []string) (string, error) { headers := make(map[string]string) headers["From"] = config.From headers["To"] = strings.Join(toList, ",") headers["Subject"] = message.Subject if message.IsHTML { headers["MIME-version"] = "1.0" headers["Content-Type"] = "text/html; charset=\"UTF-8\"" } else { headers["Content-Type"] = "text/plain; charset=\"UTF-8\"" } var msg strings.Builder for k, v := range headers { if !isValidHeader(k, v) { return "", fmt.Errorf("invalid header: %s: %s", k, v) } msg.WriteString(fmt.Sprintf("%s: %s\r\n", k, v)) } msg.WriteString("\r\n" + message.Body) return msg.String(), nil } // 验证邮件头安全性 func isValidHeader(key, value string) bool { return !strings.ContainsAny(key, "\r\n") && !strings.ContainsAny(value, "\r\n") } // SSL/TLS方式发送邮件 func sendWithSSL(config SMTPConfig, addr string, toList []string, msg string, transport *http.Transport) error { var err error var conn net.Conn if transport != nil && transport.DialContext != nil { conn, err = transport.DialContext(context.Background(), "tcp", addr) } else { conn, err = net.Dial("tcp", addr) } if err != nil { return fmt.Errorf("failed to connect to SMTP server: %w", err) } defer conn.Close() tlsConfig := &tls.Config{ ServerName: config.Host, } tlsConn := tls.Client(conn, tlsConfig) if err := tlsConn.Handshake(); err != nil { return fmt.Errorf("TLS handshake failed: %w", err) } // 创建SMTP客户端 client, err := smtp.NewClient(tlsConn, config.Host) if err != nil { return fmt.Errorf("failed to create SMTP client: %w", err) } defer client.Quit() return sendEmailWithClient(client, config, toList, msg) } // STARTTLS方式发送邮件 func sendWithStartTLS(config SMTPConfig, addr string, toList []string, msg string, transport *http.Transport) error { var err error var conn net.Conn if transport != nil && transport.DialContext != nil { conn, err = transport.DialContext(context.Background(), "tcp", addr) } else { conn, err = net.Dial("tcp", addr) } if err != nil { return fmt.Errorf("failed to connect to SMTP server: %w", err) } defer conn.Close() // 创建SMTP客户端 client, err := smtp.NewClient(conn, config.Host) if err != nil { return fmt.Errorf("failed to create SMTP client: %w", err) } defer client.Quit() // 启用TLS if err = client.StartTLS(&tls.Config{ServerName: config.Host}); err != nil { return fmt.Errorf("failed to start TLS: %w", err) } return sendEmailWithClient(client, config, toList, msg) } // 明文方式发送邮件 func sendPlaintext(config SMTPConfig, addr string, toList []string, msg string, transport *http.Transport) error { var err error var conn net.Conn if transport != nil && transport.DialContext != nil { conn, err = transport.DialContext(context.Background(), "tcp", addr) } else { conn, err = net.Dial("tcp", addr) } if err != nil { return fmt.Errorf("failed to connect to SMTP server: %w", err) } defer conn.Close() client, err := smtp.NewClient(conn, config.Host) if err != nil { return fmt.Errorf("failed to create SMTP client: %w", err) } return sendEmailWithClient(client, config, toList, msg) } // 使用SMTP客户端发送邮件 func sendEmailWithClient(client *smtp.Client, config SMTPConfig, toList []string, msg string) error { auth := smtp.PlainAuth("", config.Username, config.Password, config.Host) if err := client.Auth(auth); err != nil { return fmt.Errorf("authentication failed: %w", err) } if err := client.Mail(config.Username); err != nil { return fmt.Errorf("setting sender failed: %w", err) } for _, addr := range toList { if err := client.Rcpt(addr); err != nil { return fmt.Errorf("adding recipient %s failed: %w", addr, err) } } w, err := client.Data() if err != nil { return fmt.Errorf("preparing data failed: %w", err) } defer w.Close() if _, err := w.Write([]byte(msg)); err != nil { return fmt.Errorf("writing message failed: %w", err) } return nil }