sendgrid api key 必须开启 mail.send 权限,否则 post /mail/send 返回 403 或静默失败;需通过环境变量注入密钥并校验;官方 sdk 无重试机制,应封装指数退避重试;单次请求收件人上限 1000,需分批发送。

SendGrid API Key 权限要开对,否则 POST /mail/send 会返回 403
SendGrid 控制台创建的 API Key 默认只有 “Restricted Access”,必须手动勾选 Mail Send 权限,否则 Go 调用 sendgrid.Send 时看似成功(HTTP 202),但邮件根本不会发出,日志里也看不到失败提示。检查方式:进 SendGrid → Settings → API Keys → 点击对应 Key → 看 “Scopes” 列表里是否有 mail.send 打钩。
实操建议:
- 新建 Key 时直接选
Full Access(仅开发环境),或最小化勾选Mail Send+Suppressions Read(生产环境) - Key 值务必通过环境变量注入(如
SENDGRID_API_KEY),绝不要硬编码在.go文件里 - 调用前加一行校验:
if os.Getenv("SENDGRID_API_KEY") == "" { log.Fatal("missing SENDGRID_API_KEY") }
Go 官方 sendgrid-go SDK 的 sendgrid.NewSendClient 不自动重试,网络抖动会导致日报丢失
SendGrid 官方 SDK 的默认 HTTP client 没配重试逻辑,如果定时任务刚好撞上短暂网络波动或 SendGrid 短时 503,client.Send(&msg) 就直接返回 error,整个日报派发中断——而 cron job 通常不带失败重入机制。
实操建议:
- 自己封装一层带指数退避的发送函数,用
github.com/cenkalti/backoff/v4库最省事 - 重试上限设为 3 次,间隔从 1s 开始翻倍(1s → 2s → 4s),避免雪崩
- 每次重试前记录 warning 日志,包含
msg.Subject和当前重试次数,方便排查哪天的日报卡住了 - 最终失败才 panic 或发告警(比如往 Slack webhook 推一条
Failed to send daily report for 2024-06-15)
订阅用户邮箱列表不能全量查库再拼 to 字段,1000+ 用户会触发 SendGrid 单次请求限制
SendGrid 免费层单次 /mail/send 请求最多支持 1000 个收件人(含 to/cc/bcc 总和),且要求所有收件人必须在同一个 personalizations 数组项里。如果直接把 5000 个用户塞进一个请求,API 返回 400: Too many recipients,还浪费一次 quota。
在 Golang 中使用 samber/hot 进行内存缓存,支持 LRU、LFU、TinyLFU、W‑TinyLFU、S3FIFO、ARC、TwoQueue、SIEVE、FIFO 等淘汰算法,提供 TTL、缓存加载器及分片功能。
实操建议:
- 按每 900 人一组分批(留 100 余量应对后续加字段),用
for i := 0; i 切片 - 每组构造独立的
sendgrid.Mail实例,msg.Personalizations[0].Tos只填当前批次邮箱 - 批次间加
time.Sleep(100 * time.Millisecond)防突发流量被限流 - 别用
bcc批量发——它仍计入收件人总数,且无法个性化内容(比如写“Hi, {{.Name}}”)
日报模板里混用 {{.Date}} 和 {{.Stats.Total}} 时,sendgrid.NewEmail 不报错但渲染为空字符串
SendGrid 的模板引擎(Dynamic Templates)和 Go 原生 text/template 是两套系统。如果你直接用 Go 的 template.Must(template.New("").Parse(...)) 渲染 HTML 再塞给 msg.Content,那 {{.Date}} 这类语法能工作;但若启用了 SendGrid 的 template ID 模式(即设置 msg.TemplateID),就必须用 SendGrid 的 JSON 数据结构传参,Go 模板语法完全失效。
实操建议:
- 确认你用的是哪种模式:查代码里是否设置了
msg.TemplateID = "d-xxxxxxxxxxxxxx"—— 有就是动态模板,没设就是纯 HTML 内容 - 动态模板下,用
msg.Personalizations[0].DynamicTemplateData传 map[string]interface{},键名必须和模板里{{first_name}}完全一致(注意是 snake_case,不是 Go 的FirstName) - 本地调试时,先用
curl -X POST https://api.sendgrid.com/v3/templates创建模板并获取 ID,别等上线才发现语法不匹配
SendGrid 的坑不在代码长度,而在权限、重试、分批、模板这四个点上卡住就发不出邮件——尤其是 DynamicTemplateData 字段名大小写和下划线规则,连官方文档示例都写得模糊,建议直接抓包看 SendGrid 控制台里“Test Data”生成的 JSON 结构。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










