
本文详解如何在 go 中通过 gomail.v2 库一次性向多个收件人发送邮件,确保所有地址既被 smtp 服务器正确投递,又在邮件客户端(如 gmail、outlook)中完整显示于 to: 头字段,避免“仅发给第一个收件人”或“收件人不可见”的常见陷阱。
本文详解如何在 go 中通过 gomail.v2 库一次性向多个收件人发送邮件,确保所有地址既被 smtp 服务器正确投递,又在邮件客户端(如 gmail、outlook)中完整显示于 to: 头字段,避免“仅发给第一个收件人”或“收件人不可见”的常见陷阱。
在 Go 生态中,gomail.v2 是构建健壮邮件发送功能的首选封装库——它自动处理 MIME 结构、编码、多收件人语义及附件等复杂细节,远比直接调用 net/smtp.SendMail 安全可靠。但许多开发者误以为「将多个邮箱拼成逗号分隔字符串传给 SetHeader("To", ...)」即可实现群发,结果却只成功送达首位收件人,其余地址仅在邮件头中“可见”,却未被 SMTP 服务器实际接收(即 RCPT TO 阶段未声明)。根本原因在于:SMTP 协议要求每个收件人必须独立执行 RCPT TO 命令,而 gomail 的 DialAndSend 内部正是依据 To 头的解析结果来构造这些命令——因此 To 头格式必须合法,且与底层投递逻辑严格一致。
✅ 正确做法:SetHeader("To", ...) 接收可变参数,而非单个字符串
gomail.Message.SetHeader 方法对 "To" 字段的特殊支持是关键:它接受多个独立的邮箱地址(或格式化后的 name
recipients := os.Args[3:]
// 可选:为每个收件人添加昵称(如需),否则留空字符串
addresses := make([]string, len(recipients))
for i, r := range recipients {
addresses[i] = gomail.FormatAddress(r, "") // 或 gomail.FormatAddress(r, "User"+strconv.Itoa(i))
}
// ✅ 正确:展开切片为多个参数,gomail 自动处理合规格式
mail.SetHeader("To", addresses...)
// 其他必要头字段
mail.SetAddressHeader("From", "sender@example.com", "My Name")
mail.SetHeader("Subject", os.Args[2])
mail.SetBody("text/html", os.Args[1])
if err := dialer.DialAndSend(mail); err != nil {
log.Fatalf("Failed to send to all recipients: %v", err)
}
⚠️ 注意:mail.SetHeader("To", strings.Join(recipients, ",")) 是错误写法。虽然部分邮件客户端能解析该字符串并显示全部地址,但 gomail 在构造 SMTP 会话时仅提取第一个有效邮箱(逗号前的部分)作为 RCPT TO 目标,其余地址被忽略——这正是你观察到“只发给第一个”的根本原因。
? 补充说明:To 头 vs. 实际投递目标
- SetHeader("To", ...) 设置的是 邮件头中的显示字段(用户看到的“收件人”),同时也被 gomail 用作 生成 RCPT TO 命令的唯一依据。
- gomail 不区分“显示收件人”和“投递收件人”——二者完全绑定。这意味着:你写进 To 头的每一个合法地址,都会触发一次独立的 RCPT TO 指令,确保全部送达。
- 若需 BCC(密送),应使用 mail.SetHeader("Bcc", ...),其行为同理,但不会出现在最终邮件头中。
?️ 生产环境建议
- 始终验证邮箱格式:使用 mail.ParseAddress() 或正则预检,避免无效地址导致整封邮件失败。
- 启用日志调试:dialer.Log = smtp.NewLogWriter(os.Stderr) 可查看原始 SMTP 交互,确认 RCPT TO 是否按预期发出。
- 考虑性能与限速:单次 DialAndSend 发送 N 个收件人比 N 次循环调用更高效,且避免 SMTP 服务器因频繁连接拒绝服务(如 Gmail 的 500/h 限制)。
- 替代方案(高级场景):若需差异化内容(如个性化称呼),应使用模板引擎批量生成独立邮件;若需超大规模投递(>1000 收件人),建议接入 Mailgun、SendGrid 等专业 API,而非依赖自建 SMTP。
综上,gomail.v2 的多收件人支持简洁而严谨:只需将每个邮箱地址作为独立参数传入 SetHeader("To", ...),即可同时满足“全部投递”与“全部显示”两大需求——无需手动拼接、无需循环发送,也无需绕道原始 net/smtp。这是现代 Go 邮件开发的标准实践。











