必须用html/template渲染html邮件,因其自动转义防xss且支持上下文感知编码;纯文本邮件可用text/template。混用易致标签明文显示或链接损坏。

模板渲染用 text/template 还是 html/template?
取决于邮件内容是否含用户可控 HTML。纯文本邮件用 text/template;带样式或链接的 HTML 邮件必须用 html/template,否则 {{.URL}} 会被自动转义成 <a href="...">,点不开链接。
常见错误:混用两者却没注意转义逻辑。比如用 text/template 渲染含 <b>{{.Name}}</b> 的模板,结果收到的是明文标签,不是加粗文字。
- HTML 邮件 → 固定用
html/template,并确保所有动态内容都来自可信源(如内部数据库字段) - 需要同时发 HTML + 纯文本备选版本(multipart/alternative)?得分别渲染两份模板,不能靠自动降级
- 模板中避免直接插入
url.QueryEscape后的字符串——html/template会再逃一次,导致链接损坏
怎么安全传入数据给模板,避开 nil panic 和类型错配?
Go 模板对 nil 值极敏感:{{.User.Email}} 在 .User 是 nil 时直接 panic,而不是静默跳过。
实际发送批量邮件时,数据常来自 DB 查询,字段可能为空、类型不一致(比如 DB 返回 sql.NullString,但模板里当 string 用)。
- 统一用结构体接收数据,字段声明为指针或带
sql.Null*类型,并在模板中用{{with .User}}{{.Email}}{{end}}包裹 - 不要传裸 map[string]interface{} —— 编译期无法检查字段是否存在,运行时出错难定位
- 关键字段(如收件人邮箱)在调用
Execute前就校验,别等模板渲染完才发现.To是空字符串
批量发送时如何避免被当垃圾邮件或触发限流?
SMTP 服务器(尤其是 Gmail、Outlook)对单连接并发数、单位时间发信量、HELO 域名、SPF/DKIM 记录极其敏感。硬开 goroutine 并发调 net/smtp 发 1000 封,大概率被拒收或进垃圾箱。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
真正可行的批量策略不是“快”,而是“稳”和“可追溯”。
- 每封邮件单独建立 SMTP 连接(或复用连接池,但需控制
MaxIdleConnsPerHost≤ 2),避免单连接超时中断整批 - 加入指数退避重试:首次失败后等 1s,再失败等 2s、4s… 最多 3 次,别死循环重发
- 收件人地址必须经
mail.ParseAddress校验,过滤掉"test@"或含换行符的脏数据,否则 SMTP 会直接返回501 Invalid address - 发信头务必设
Message-ID(用uuid.NewString())和Precedence: bulk,部分反垃圾系统靠这个识别群发意图
用 gomail 库还是原生 net/smtp?
gomail 封装了 multipart 邮件构建、附件编码、UTF-8 主题编码等细节,省去手动拼 boundary 和 base64 的麻烦;但它默认不支持连接池,高并发下容易耗尽文件描述符。
原生 net/smtp 更轻量,但你要自己处理:
- HTML + 文本双版本的
multipart/alternative结构 - 中文主题的
Subject: =?UTF-8?B?...编码 - 附件的 MIME 头和分块读取(避免内存爆掉)
建议:中小规模(gomail,专注业务逻辑;大规模或需精细控流(如按域名限速)时,用 net/smtp + mime/multipart 自建发送器,把连接管理权抓在自己手里。
最容易被忽略的一点:无论用哪个库,发信前必须验证 SMTP 凭据能否登录(用 smtp.Auth 尝试一次 Auth 调用),别等到循环到第 999 封才报 535 Authentication failed。










