摘要: 注册、登录验证、操作确认都要发验证码邮件。自己搭 SMTP,送达率和维护都得操心。这篇讲一个更省事的做法——用一个 HTTP 接口完成发送,后端只留验证码的生成和校验,并给出接入步骤、代码、状态验证和几个容易忽略的点,适合想快速、稳定接入验证码邮件的开发者和运维参考。
背景与问题
发验证码邮件,发送动作本身不难,难的是把它做稳。自己搭 SMTP 通常要面对这些:
- 邮箱服务与 SMTP 的申请、配置成本
- SPF / DKIM / DMARC 没配好,邮件进垃圾箱,送达率不可控
- 发送失败重试、并发与频率控制要自己实现
- 发送量增长后的通道稳定性和封号风险
这些都属于基础设施层面的工作,和业务没有直接关系,却要持续投入维护。所以更省事的做法,是把"发送"这一层交出去,用现成的接口。
方案
整体思路是职责拆分:
- 业务侧(你的后端)负责:生成验证码、存储(如 Redis 带过期)、校验
- 发送侧(外部接口)负责:邮件投递、模板渲染、送达通道
这次用的是现成的邮件推送服务(Spug),调用方式是一个 HTTP POST,模板编码即凭证,没有额外的鉴权流程,接入成本低。
实现步骤
获取模板编码
微信扫码登录后进入「邮件模板」,系统默认提供一个验证码模板,复制其模板编码(形如 Ne6Wq3pV7mD)。该编码即调用凭证,无需申请 token 或签名。
调用接口
模板编码作为接口路径末段,提交收件人与模板变量:
curl -d '{"to": "svc@example.com", "scene": "操作确认", "code": "330948", "minute": "5"}' \
https://push.spug.cc/mail/Ne6Wq3pV7mD
后端建议封装成独立的发送方法,便于统一处理超时与异常:
import requests
PUSH_URL = 'https://push.spug.cc/mail/Ne6Wq3pV7mD'
def send_verify_code(to_email, code, scene='操作确认', minute='5'):
body = {
'to': to_email, 'scene': scene, 'code': code, 'minute': minute}
try:
resp = requests.post(PUSH_URL, json=body, timeout=5)
data = resp.json()
except requests.RequestException:
# 发送失败不应阻塞主流程,可记录日志并允许用户重发
return None
return data.get('request_id')
参数
| 参数 | 必填 | 说明 |
|---|---|---|
| to | 是 | 收件人邮箱,多个用英文逗号分隔,最多 10 个 |
| scene | 否 | 操作场景,如"操作确认""注册验证" |
| code | 否 | 验证码 |
| minute | 否 | 有效分钟数 |
默认模板正文:
您正在进行${scene},本次验证码为:${code}
请在 ${minute} 分钟内输入验证码完成验证。
结果验证
请求成功返回:
{
"code": 200, "msg": "请求成功", "request_id": "dwezjVRDoe5jgLkR"}
这里要明确一点:code=200 表示请求被接收并入库成功,但邮件是异步发送的,200 并不代表已经送达。要确认实际发送状态,需要用返回的 request_id 到推送日志中查询。建议把 request_id 与收件人、发送时间一并落库,作为排查依据——用户反馈未收到时,可直接定位这封邮件的状态,多数情况是被收件方归入垃圾箱。
常见问题
模板编码可以下发到前端吗? 不建议。模板编码等同于调用凭证,暴露后他人可消耗你的发送额度,应仅在服务端调用;服务端 IP 固定时建议配置 IP 白名单,怀疑泄露时在平台重置编码,旧编码立即失效。
发送失败如何处理? 发送是外部网络调用,需设置超时并捕获异常,失败时记录日志并允许用户重发,避免阻塞注册或登录主流程。
能否自定义邮件内容? 可以,新建自定义模板后按模板详情中列出的变量传参即可。
写在最后
把邮件发送变成一次 HTTP 调用之后,后端代码更聚焦,送达和通道维护也不用自己扛。需要留意的主要是两点:返回 200 只是入库成功、邮件异步发送,以及模板编码必须留在服务端。