smtp_mailer.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. package infra
  2. import (
  3. "crypto/tls"
  4. "fmt"
  5. "net"
  6. "net/smtp"
  7. "strings"
  8. )
  9. // SMTPMailConfig SMTP 发信参数
  10. type SMTPMailConfig struct {
  11. Host string
  12. Port int
  13. User string
  14. Password string
  15. FromEmail string
  16. FromName string
  17. }
  18. // SendSMTPMail 通过 SMTP 发送纯文本邮件(支持 465 SSL / 587 STARTTLS)
  19. func SendSMTPMail(cfg SMTPMailConfig, to, subject, body string) error {
  20. if cfg.Host == "" || cfg.FromEmail == "" {
  21. return fmt.Errorf("SMTP 未配置完整")
  22. }
  23. to = strings.TrimSpace(to)
  24. if to == "" {
  25. return fmt.Errorf("收件人邮箱为空")
  26. }
  27. port := cfg.Port
  28. if port <= 0 {
  29. port = 465
  30. }
  31. from := cfg.FromEmail
  32. fromHeader := from
  33. if cfg.FromName != "" {
  34. fromHeader = fmt.Sprintf("%s <%s>", cfg.FromName, from)
  35. }
  36. msg := buildPlainTextMessage(fromHeader, to, subject, body)
  37. addr := fmt.Sprintf("%s:%d", cfg.Host, port)
  38. if port == 465 {
  39. return sendSMTPSImplicitTLS(addr, cfg, from, to, msg)
  40. }
  41. return sendSMTPStartTLS(addr, cfg, from, to, msg)
  42. }
  43. func buildPlainTextMessage(from, to, subject, body string) []byte {
  44. var b strings.Builder
  45. b.WriteString("From: " + from + "\r\n")
  46. b.WriteString("To: " + to + "\r\n")
  47. b.WriteString("Subject: " + subject + "\r\n")
  48. b.WriteString("MIME-Version: 1.0\r\n")
  49. b.WriteString("Content-Type: text/plain; charset=UTF-8\r\n")
  50. b.WriteString("\r\n")
  51. b.WriteString(body)
  52. return []byte(b.String())
  53. }
  54. func sendSMTPSImplicitTLS(addr string, cfg SMTPMailConfig, from, to string, msg []byte) error {
  55. tlsCfg := &tls.Config{ServerName: cfg.Host}
  56. conn, err := tls.Dial("tcp", addr, tlsCfg)
  57. if err != nil {
  58. return fmt.Errorf("连接 SMTP 失败: %w", err)
  59. }
  60. defer conn.Close()
  61. client, err := smtp.NewClient(conn, cfg.Host)
  62. if err != nil {
  63. return fmt.Errorf("创建 SMTP 客户端失败: %w", err)
  64. }
  65. defer client.Close()
  66. if cfg.User != "" {
  67. if err := client.Auth(smtp.PlainAuth("", cfg.User, cfg.Password, cfg.Host)); err != nil {
  68. return fmt.Errorf("SMTP 认证失败: %w", err)
  69. }
  70. }
  71. if err := client.Mail(from); err != nil {
  72. return fmt.Errorf("设置发件人失败: %w", err)
  73. }
  74. if err := client.Rcpt(to); err != nil {
  75. return fmt.Errorf("设置收件人失败: %w", err)
  76. }
  77. w, err := client.Data()
  78. if err != nil {
  79. return fmt.Errorf("打开邮件正文失败: %w", err)
  80. }
  81. if _, err := w.Write(msg); err != nil {
  82. return fmt.Errorf("写入邮件正文失败: %w", err)
  83. }
  84. if err := w.Close(); err != nil {
  85. return fmt.Errorf("关闭邮件正文失败: %w", err)
  86. }
  87. return client.Quit()
  88. }
  89. func sendSMTPStartTLS(addr string, cfg SMTPMailConfig, from, to string, msg []byte) error {
  90. conn, err := net.Dial("tcp", addr)
  91. if err != nil {
  92. return fmt.Errorf("连接 SMTP 失败: %w", err)
  93. }
  94. defer conn.Close()
  95. host := cfg.Host
  96. client, err := smtp.NewClient(conn, host)
  97. if err != nil {
  98. return fmt.Errorf("创建 SMTP 客户端失败: %w", err)
  99. }
  100. defer client.Close()
  101. if ok, _ := client.Extension("STARTTLS"); ok {
  102. if err := client.StartTLS(&tls.Config{ServerName: host}); err != nil {
  103. return fmt.Errorf("STARTTLS 失败: %w", err)
  104. }
  105. }
  106. if cfg.User != "" {
  107. if err := client.Auth(smtp.PlainAuth("", cfg.User, cfg.Password, host)); err != nil {
  108. return fmt.Errorf("SMTP 认证失败: %w", err)
  109. }
  110. }
  111. if err := client.Mail(from); err != nil {
  112. return fmt.Errorf("设置发件人失败: %w", err)
  113. }
  114. if err := client.Rcpt(to); err != nil {
  115. return fmt.Errorf("设置收件人失败: %w", err)
  116. }
  117. w, err := client.Data()
  118. if err != nil {
  119. return fmt.Errorf("打开邮件正文失败: %w", err)
  120. }
  121. if _, err := w.Write(msg); err != nil {
  122. return fmt.Errorf("写入邮件正文失败: %w", err)
  123. }
  124. if err := w.Close(); err != nil {
  125. return fmt.Errorf("关闭邮件正文失败: %w", err)
  126. }
  127. return client.Quit()
  128. }