MySQL 本身不发送邮件,它只是存储邮件相关数据的数据库;真正发邮件得靠应用层(比如 Python、PHP、Java)调用 SMTP 服务,而 MySQL 负责存收件人、模板、发送记录、状态等。
设计邮件任务表 email_tasks
:存待发邮件的核心元数据
别把原始 HTML 内容或大附件塞进这张表,否则查询慢、备份重、还容易拖垮事务。重点存控制字段:
id:主键,自增或 UUID(高并发建议 UUID 避免暴露数量)
to_email:收件人邮箱(
VARCHAR(255),加索引,但别建唯一索引——同一人可收多封)
template_key:模板标识(如
"welcome_v2"),不是直接存 HTML,方便统一管理与热更新
params_json:JSON 字符串(
JSON类型或
TEXT),存动态参数:
{"name": "张三", "order_id": "ORD-789"}
status:枚举值,如
"pending"/
"sent"/
"failed"/
"retrying",加索引用于轮询
created_at和
updated_at:时间戳,
updated_at在状态变更时更新
error_message:失败时存简短错误(如
"550 mailbox full"),长度限制 500 字符以内
建邮件模板表 email_templates
:分离内容与逻辑
硬编码模板到代码里等于给自己埋雷;存在 DB 里才能灰度、A/B 测试、运营后台修改。关键字段:
key:主键,
VARCHAR(64),作为业务调用入口(如调用
send_email("password_reset", {"token": "abc"}))
subject:支持变量插值,如
"重置您的密码 — {{site_name}}
body_html:HTML 模板,含
{{}} 占位符(后端渲染时替换)
body_text:纯文本备用版本(部分邮箱客户端只读 text/plain)
is_active:开关字段,停用旧模板不用删数据
updated_by:记录谁改的(运维追责用)
注意:
body_html别用
TEXT就完事——如果模板超大(比如带内联 CSS 的营销邮件),考虑拆出独立表或对象存储,MySQL 只存 URL。
用 email_logs
表追踪每封实际发出的邮件
email_tasks是“要做什么”,
email_logs是“做过什么”,二者必须通过外键或业务 ID 关联。日志表字段要克制:
task_id:关联
email_tasks.id,允许为 NULL(比如手工触发没走任务队列)
message_id:SMTP 返回的唯一 ID(如
""),用于查投递状态或投诉溯源
from_email和
to_email:冗余记录,避免 JOIN 查不到原始任务
smtp_status_code:整数,如
250(OK)、
451(临时失败)、
554(拒绝)
sent_at:精确到秒的时间戳,用于统计时效性
size_bytes:整数,记录最终发出的邮件字节数(监控是否意外膨胀)
这张表务必按
sent_at分区或定期归档,否则半年后几千万行,
SELECT COUNT(*)都卡。
为什么不能用 MySQL 的事件(EVENT)或存储过程发邮件
MySQL 不提供 SMTP 客户端能力,官方也不支持调用外部网络服务。有人试过用
Sys_exec()或 UDF 调 shell 脚本,后果是: 权限失控:MySQL 进程以系统用户运行,脚本可能被注入执行任意命令 阻塞主线程:发信耗时几百毫秒到几秒,会卡住所有 SQL 查询 无重试/死信机制:失败了就真丢了,连日志都难追 无法监控吞吐:你根本不知道每分钟发了多少封,峰值在哪
正确做法是写一个独立的 worker 进程(比如用 Celery + Python smtplib,或 Go 的
gomail),定时拉取
email_tasks中
status = 'pending'的记录,发完再原子更新状态和写日志。MySQL 在这里只做“可靠消息队列”的轻量替代,不是邮局。
