Go 的 net/smtp 不支持直接发送 HTML 邮件,需用 gomail 等库构造 MIME 多部分消息;发 HTML 必须调用 SetBody("text/html", html) 并内联样式、用绝对 URL 图片;注意 SMTP 认证(应用专用密码)、端口加密配置及连接复用。

Go 的 net/smtp 本身不支持 HTML 邮件体
直接调用 smtp.SendMail 发纯文本可以,但发 HTML 邮件会显示为源码或乱码——因为 SMTP 协议要求多部分(multipart)结构和正确的 MIME 头,而 net/smtp 只负责底层传输,不封装邮件内容格式。
常见错误现象:Content-Type: text/plain 被硬编码进邮件头,HTML 标签原样显示;收件箱里看到的是 <h1>Hello</h1> 而不是加粗标题。
- 必须手动构造符合 RFC 2822 和 RFC 2046 的 MIME 消息体(含 boundary、headers、body parts)
- 推荐用
gopkg.in/gomail.v2或github.com/go-gomail/gomail(v3),它封装了 MIME 构建逻辑,且仍基于标准库net/smtp - 避免自己拼接
boundary字符串或手写Content-Type头——容易漏空行、错大小写、缺换行符,导致某些邮箱(如 Outlook)拒绝解析
用 gomail 发 HTML 邮件的最小可行配置
不是所有 SMTP 服务商都允许任意发信。Gmail、QQ 邮箱等要求开启“SMTP 服务”并使用应用专用密码;企业邮箱常需 TLS/SSL 端口与域名白名单。
典型失败场景:535 5.7.8 Username and Password not accepted(账号密码错)、530 5.7.0 Must issue a STARTTLS command first(没启用加密)。
立即学习“go语言免费学习笔记(深入)”;
- SMTP 地址和端口要匹配:Gmail 用
smtp.gmail.com:587(STARTTLS),QQ 邮箱用smtp.qq.com:587,163 用smtp.163.com:465(SSL) -
gomail.NewDialer的第四个参数是密码,不是邮箱登录密码(如果是 Gmail,必须是应用专用密码;QQ 邮箱同理) - 发 HTML 必须调用
m.SetBody("text/html", htmlString),不能只写m.SetBody("text/plain", "...")—— 否则默认仍是纯文本 - 如果想同时发 HTML + 纯文本备用(提升兼容性),用
m.AddAlternative("text/plain", plainString)
示例片段:
d := gomail.NewDialer("smtp.qq.com", 587, "user@qq.com", "app-specific-password")
m := gomail.NewMessage()
m.SetHeader("From", "user@qq.com")
m.SetHeader("To", "receiver@example.com")
m.SetHeader("Subject", "测试 HTML 邮件")
m.SetBody("text/html", "<h2>你好</h2><p>这是一封 <strong>HTML</strong> 邮件</p>")
if err := d.DialAndSend(m); err != nil {
log.Fatal(err) // 注意:这里 err 可能是网络超时、认证失败、或 MIME 构造错误
}
HTML 内联样式失效?别用外部 CSS 或 <style>
多数邮箱客户端(包括 Gmail、Outlook Web、Apple Mail)会剥离 <style> 标签和外部链接,仅保留内联 style 属性。这不是 Go 的问题,是邮件生态的通用限制。
常见错误现象:HTML 在浏览器里渲染正常,但发到邮箱后文字全变黑、无缩进、表格塌陷。
- 所有样式必须写在对应标签的
style属性里,例如<p style="color: #333; font-size: 14px;"> - 避免使用 CSS 类名、媒体查询、Flex/Grid 布局——它们基本不被支持
- 表格布局仍是邮件最稳妥的排版方式(
<table>嵌套),width和cellpadding属性比 CSS 更可靠 - 图片务必用绝对 URL(
https://...),且加上alt属性;很多邮箱默认屏蔽远程图片
发信频率高时被拒?检查 gomail.Dialer 复用和连接池
每调一次 d.DialAndSend(m) 默认新建一个 TCP 连接,频繁调用易触发 SMTP 服务商的速率限制(如 Gmail 每天 500 封、每分钟 10 封),或导致 EOF / i/o timeout 错误。
性能影响明显:单连接发 10 封耗时约 3 秒,复用连接可压到 0.8 秒以内。
- 不要每次发信都 new 一个
gomail.Dialer,应复用同一个实例(它是线程安全的) - 如果并发发送,用
gomail.NewPool(d, 10)创建连接池,再调用pool.Send(m, 10)批量发 - 注意:连接池不会自动重连断开的连接,遇到
connection refused或read: connection reset仍需手动重建池 - 生产环境建议加重试逻辑(指数退避),并记录
gomail.SendError中的 SMTP 状态码(如454 4.7.1 Too many login attempts)
gomail 能简化,但没法绕过协议本身。真正难的不是写代码,是让不同邮箱客户端一致地渲染出来。











