优先用net/smtp因其无依赖、可控性强、错误定位直接(如535 5.7.8可立刻识别授权码问题),而第三方SDK隐藏协议细节,遇企业邮箱限制或TLS不匹配时更难排查;实操需手动构造MIME、用应用专用码、配对发件域名与SMTP服务器。

为什么用 net/smtp 而不是第三方邮件 SDK?
Gin 本身不处理邮件,核心是选对底层发送方式。net/smtp 是 Go 标准库,无额外依赖、可控性强、调试信息直接(比如 535 5.7.8 Authentication failed 这类错误能立刻定位到账号或授权码问题)。第三方 SDK(如 gomail)虽封装友好,但隐藏了 SMTP 协议细节,遇到企业邮箱限制、TLS 版本不匹配时反而难排查。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 优先用
net/smtp+ 手动构造 MIME 邮件体,控制权在自己手上 - 不要硬编码邮箱密码,改用应用专用授权码(QQ/163/Outlook 均支持)
- 发件域名必须和 SMTP 服务器一致,例如用
smtp.qq.com就得配 QQ 邮箱账号,不能混用
如何让 Gin 接口安全接收邮箱并触发发送?
验证码邮件接口本质是「防刷 + 异步 + 状态隔离」。直接同步调用 smtp.SendMail 会阻塞 HTTP 请求,且没做频率限制,容易被恶意轮询打爆邮箱服务商配额。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用 Gin 的
c.GetString("email")从 query 或 JSON body 提取邮箱,先校验格式:strings.Contains(email, "@") && strings.Contains(email, ".") - 加内存级限流:用
sync.Map记录邮箱 → 最后发送时间,间隔少于 60 秒直接返回429 Too Many Requests - 发送逻辑扔进 goroutine:
go func() { sendVerifyEmail(email, code) }(),但注意别传指针导致并发读写 panic
验证码怎么生成才既安全又可验证?
用 rand.Intn(10) 拼 6 位数看似简单,但默认 seed 相同会导致重复;用 time.Now().UnixNano() 当 seed 又可能被预测。真正可用的是 crypto/rand —— 它从操作系统熵池读取真随机数,不可预测。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 生成 6 位数字验证码:
bytes := make([]byte, 4); crypto/rand.Read(bytes); code := int(binary.LittleEndian.Uint32(bytes)) % 900000 + 100000 - 验证码必须存服务端(Redis 最佳),过期时间设为 5 分钟,key 形如
verify:email@domain.com - 别把验证码塞进邮件 HTML 里当明文传输,邮件只展示,验证时比对 Redis 中的值
Gin 路由里怎么写才不踩 TLS 和认证坑?
常见错误是直接写 smtp.SendMail("smtp.qq.com:587", ...) 却忽略 STARTTLS 协商,结果卡在连接阶段;或者用错端口(465 是 SSL,587 是 TLS),导致 dial tcp: i/o timeout。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- QQ 邮箱必须用
smtp.qq.com:587+auth := smtp.PlainAuth("", user, pass, "smtp.qq.com") - 163 邮箱同理,但用户填完整邮箱地址(
user@163.com),pass 是授权码不是登录密码 - 发信前加 TLS 配置:
tlsConfig := &tls.Config{ServerName: "smtp.qq.com"},再用tls.Client(conn, tlsConfig)包一层
复杂点在于各家邮箱对 HELO/EHLO、MAIL FROM、RCPT TO 的校验松紧不同,测试时务必用真实邮箱收件,别只看 SendMail 返回 nil —— 有些错误是发出去才退信。


















