gomail是Go生态中发送运营/告警邮件的首选,自动处理MIME构造、RFC 2047编码、多部分边界、Message-ID生成、附件流式读取及AUTH降级;需用SetBody+AddAlternative设HTML正文,Attach添加附件,SetHeader设Cc/Bcc,端口与TLS配置须严格匹配服务商要求。

直接用 gomail,别碰 net/smtp 手动拼 MIME —— 生产环境发运营/告警邮件,90% 的失败都卡在编码、边界、TLS 协商或 AUTH 降级上,gomail 全包了。
gomail 发 HTML + 附件的四步写法
它不抽象流程,只封装协议细节:MIME 构造、RFC 2047 主题编码、多部分 boundary、Message-ID 生成、附件流式读取、按服务器 AUTH 列表自动尝试 Plain → CRAMMD5(虽然后者基本不用了)。
-
m.SetBody("text/plain", "Hi there")必须先设纯文本;再调m.AddAlternative("text/html", "才能兼容 Outlook / Apple Mail 等客户端Hi
") - 附件路径要真实存在且进程有读权限:
m.Attach("/tmp/report.pdf");内存中文件(如*bytes.Buffer)得用m.Attach("data.csv", gomail.SetCopyFunc(...)) - 抄送和密送不是方法,是头:
m.SetHeader("Cc", "a@b.com")、m.SetHeader("Bcc", "c@d.com");注意Bcc不会出现在收件人看到的原始邮件头里 - 中文主题完全不用管编码:
m.SetHeader("Subject", "订单确认 - 2026-10-27"),gomail自动转成=?utf-8?b?5rWL6K+VIOaVsOacieW+uuaXtQ==?=
Gmail / 163 / 腾讯企业邮的端口与 TLS 配置不能混用
错一个就 dial tcp: i/o timeout 或卡在 STARTTLS 协商,跟密码无关。
- Gmail:必须用
"smtp.gmail.com:587"+ 默认 TLS(即 STARTTLS),禁用 465;账号填完整邮箱,密码必须是 App Password(两步验证开启后生成) - 163 邮箱:支持
"smtp.163.com:465"(SSL)或"smtp.163.com:587"(STARTTLS);若选 465,测试时可加d.TLSConfig = &tls.Config{InsecureSkipVerify: true},但生产必须校验证书 - 腾讯企业邮:
"smtp.exmail.qq.com:465"最稳;若硬要用 587,得确认服务器实际支持 STARTTLS,否则DialAndSend会 hang 住 - 阿里云邮件推送(如
smtpdm.aliyun.com):优先走:465;:80是明文,Go 1.9+ 默认拒绝,绕 TLS 很麻烦
为什么 net/smtp.SendMail 总报 535 / 432 / “unencrypted connection”?
这些错误基本不出现在业务逻辑里,全在连接建立和协议协商阶段 —— smtp.SendMail 是黑盒,没法看它到底卡在哪一步。
-
535 5.7.8 Authentication failed:不是密码错,是 AUTH 方法不匹配(比如服务器只接受PLAIN,你传了CRAM-MD5)或发件人地址 ≠ 认证账号 -
432 5.7.8 Requested mail action aborted: too many invalid recipients:其实是 STARTTLS 没成功就发了 MAIL FROM,被当成未加密连接拒收 -
unencrypted connection:Go 客户端要求加密,但你连的是 :25 或 :80 明文端口,或tls.Config没传对 - 真要调试,先用
openssl s_client -connect smtp.exmail.qq.com:465 -crlf看证书和响应,再比对gomail.NewDialer的参数
多收件人必须拆成独立字符串切片
这是标准库和 gomail 都共用的底层约束:SMTP 协议要求每个 RCPT TO 命令只能带一个邮箱地址。
- ❌ 错误写法:
[]string{"a@b.com,b@c.com"}→ 服务器返回501 Syntax error in arguments - ✅ 正确写法:
[]string{"a@b.com", "b@c.com", "c@d.com"} - 同时设多个收件人时,
To头也得手动拼好:m.SetHeader("To", "a@b.com", "b@c.com")(gomail会自动处理,但头字段语义仍要合规) - 如果收件人量大(>100),别用单次
Send,改用连接池复用gomail.Dialer实例,避免本地端口耗尽
真正容易被忽略的是:附件文件路径的权限和存在性检查,以及 Bcc 在 SetHeader 后不会出现在原始邮件源码的 To/Cc 字段里 —— 这不是 bug,是 SMTP 协议设计如此。发之前用 m.String() 打印预览下结构,比盲试快得多。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











