用net/smtp发html邮件必须手动构造mime头,包括mime-version、content-type(text/html; charset=utf-8)及空行分隔;漏换行、缺charset或未用multipart/alternative会导致显示异常或被拒收。

用 net/smtp 发 HTML 邮件必须手动构造 MIME 头
go 标准库没有内置的 HTML 邮件封装函数,net/smtp 只负责传输,内容格式得自己拼。直接把 HTML 字符串塞进 msg 里发出去,收件方大概率看到的是纯文本源码,或者被当垃圾邮件拦截。
关键点在于:必须显式写入 MIME-Version、Content-Type 和空行分隔。常见错误是漏掉换行、类型写成 text/html 却没声明 charset,或把整个邮件体当一个字符串传给 smtp.SendMail 而没加头。
- HTML 部分要包裹在
Content-Type: text/html; charset=utf-8块里 - 邮件头和正文之间必须有且仅有一个空行(
rnrn) - 如果同时发 HTML + 纯文本备用,得用
multipart/alternative结构,不能简单拼接
用 gomail 库发 HTML 邮件更稳但要注意默认编码
gomail 是最常用的 Go 邮件库,它自动处理 MIME 封装,Dialer 支持 TLS/ssl,Message 的 SetBody 方法能直接设 HTML 内容。但它的默认行为容易踩坑:不显式指定 charset 时,部分邮箱(比如 outlook Web)会按 ISO-8859-1 解析 UTF-8 字符,导致中文乱码。
- 务必调用
m.SetHeader("MIME-Version", "1.0")(虽然文档说可选,但某些 SMTP 网关会拒收无此头的邮件) -
m.SetBody("text/html", htmlStr)后,再补一句m.SetHeader("Content-Transfer-Encoding", "base64")更稳妥,尤其含中文或特殊符号时 - 避免用
m.Attach()传 HTML 字符串——它只处理文件路径,不是内容注入
Web 应用中发 HTML 邮件要防模板注入和链接失效
Web 服务常把用户输入动态嵌入邮件模板(比如欢迎邮件里的用户名、重置链接),这时 HTML 模板渲染不加过滤,就等于把 xss 漏洞直送收件箱;而相对路径的 CSS 或图片链接,在邮件客户端里基本全失效。
立即学习“go语言免费学习笔记(深入)”;
- 所有用户输入必须经
html.EscapeString()处理,别信前端已过滤——邮件后端是独立上下文 - CSS 只能用内联样式(
style="..."),外部<link>或@import基本被屏蔽 - 图片必须用绝对 URL(如
https://yourapp.com/email-assets/logo.png),且确保该路径允许匿名访问、不带登录态校验 - 密码重置等敏感链接,务必带一次性 Token 并设置短过期时间,别依赖前端 js 拼接
SMTP 连接失败或被拒的常见原因和验证方式
本地测试能发,上线后静默失败?多数不是代码问题,而是 SMTP 配置或网络策略卡住。Gmail、Outlook 等服务商对未验证域名、高频发送、无 SPF/DKIM 记录的邮件直接拒收或进垃圾箱。
- 先用
telnet smtp.gmail.com 587测试端口连通性(注意:有些云服务器禁 outbound 25/587) - 检查发信域名是否配置了 SPF 记录(如
v=spf1 include:_spf.google.com ~all) - Go 日志里捕获到
535 5.7.8 Username and Password not accepted?说明密码错或需开启「应用专用密码」(Gmail 开了 2FA 后必须用) - 用
go run main.go 2>&1 | grep -i "auth|535|421"快速定位认证或限流错误
真正麻烦的从来不是怎么拼 HTML,而是让那封邮件穿过层层网关、不被当成广告、不被丢进垃圾箱——每家邮箱的过滤规则都不声不响地变,你得盯着退信日志,而不是只看 err == nil。