package main import ( "crypto/tls" "fmt" "log" "net" "net/smtp" "os" "strings" "time" ) func main() { smtpHost := getenv("SMTP_HOST", "smtp.163.com") smtpPort := getenv("SMTP_PORT", "465") smtpUser := strings.TrimSpace(os.Getenv("SMTP_USER")) smtpPass := strings.TrimSpace(os.Getenv("SMTP_PASS")) mailTo := strings.TrimSpace(os.Getenv("MAIL_TO")) subject := getenv("MAIL_SUBJECT", "SuperTodo SMTP test") body := getenv("MAIL_BODY", "This is a SMTP connectivity test email from SuperTodo.") if smtpUser == "" || smtpPass == "" || mailTo == "" { log.Fatal("missing required env: SMTP_USER, SMTP_PASS, MAIL_TO") } if err := sendMailTLS(smtpHost, smtpPort, smtpUser, smtpPass, mailTo, subject, body); err != nil { log.Fatalf("send mail failed: %v", err) } log.Printf("mail sent successfully: from=%s to=%s via %s:%s", smtpUser, mailTo, smtpHost, smtpPort) } func sendMailTLS(host, port, user, pass, to, subject, body string) error { addr := net.JoinHostPort(host, port) dialer := &net.Dialer{Timeout: 10 * time.Second} conn, err := tls.DialWithDialer(dialer, "tcp", addr, &tls.Config{ ServerName: host, MinVersion: tls.VersionTLS12, }) if err != nil { return fmt.Errorf("tls dial failed: %w", err) } defer conn.Close() client, err := smtp.NewClient(conn, host) if err != nil { return fmt.Errorf("smtp client failed: %w", err) } defer client.Close() auth := smtp.PlainAuth("", user, pass, host) if err := client.Auth(auth); err != nil { return fmt.Errorf("smtp auth failed: %w", err) } if err := client.Mail(user); err != nil { return fmt.Errorf("mail from failed: %w", err) } if err := client.Rcpt(to); err != nil { return fmt.Errorf("rcpt to failed: %w", err) } w, err := client.Data() if err != nil { return fmt.Errorf("data failed: %w", err) } msg := buildMessage(user, to, subject, body) if _, err := w.Write([]byte(msg)); err != nil { return fmt.Errorf("write message failed: %w", err) } if err := w.Close(); err != nil { return fmt.Errorf("close writer failed: %w", err) } if err := client.Quit(); err != nil { return fmt.Errorf("smtp quit failed: %w", err) } return nil } func buildMessage(from, to, subject, body string) string { var b strings.Builder b.WriteString("From: " + from + "\r\n") b.WriteString("To: " + to + "\r\n") b.WriteString("Subject: " + subject + "\r\n") b.WriteString("MIME-Version: 1.0\r\n") b.WriteString("Content-Type: text/plain; charset=UTF-8\r\n") b.WriteString("\r\n") b.WriteString(body) if !strings.HasSuffix(body, "\n") { b.WriteString("\r\n") } return b.String() } func getenv(key, fallback string) string { v := strings.TrimSpace(os.Getenv(key)) if v == "" { return fallback } return v }