设计一个对标 api.mail.tm 的极高并发、可分布式部署的临时邮件后端,兼容 DuckMail API 标准,是一个非常经典的高吞吐量 I/O + 实时流推送的架构场景。
为了满足"极高并发"、"可分布式"和"深入思考"的要求,我们抛弃单体架构,采用微服务架构与事件驱动设计 (EDA)。
以下是完整的架构设计、技术栈选型以及严格对照 mail.tm 的 API 设计。
| 组件 | 技术选型 | 深度论证 |
|---|---|---|
| 编程语言 | Golang | Go 语言天生适合高并发的网络 I/O。利用 Goroutine 处理海量长连接(SMTP 接收、SSE 实时推送)资源占用极低;编译为单一二进制文件,极易部署。 |
| SMTP 核心库 | emersion/go-smtp |
Go 生态中最优秀的 SMTP 库(ProtonMail 也在使用其底层组件),支持中间件模式,易于控制并发和安全防御。 |
| MIME 解析库 | emersion/go-message |
处理复杂邮件结构的行业标准,流式解析,防止大邮件导致内存溢出 (OOM)。 |
| 主数据库 | MongoDB | 临时邮件是非结构化数据(JSON 完美契合)。更重要的是,MongoDB 原生支持 TTL 索引,可以实现邮件和账号的自动过期删除,无需手工写定时清理脚本,完美契合临时邮件的生命周期。 |
| 缓存与状态 | Redis (Cluster) | 1. 存储 SMTP 的速率限制状态。 2. 缓存活跃的邮件地址(用于 SMTP RCPT TO 阶段的极速拦截)。3. 提供 Pub/Sub 功能用于分布式 SSE 推送。 4. 三层速率限制(全局 IP + 单 Key + 单 Key 单域名)的固定窗口计数器(Redis INCR + EXPIRE)。 |
| 消息队列 | NATS JetStream / Kafka | 核心解耦点。SMTP 服务器收到原始邮件后不立即解析,而是扔进队列立刻响应 250 OK。防止高并发洪峰压垮数据库。 |
| 对象存储 | MinIO (S3 兼容) | 分布式存储邮件附件,同样支持 Lifecycle Policy 自动过期清理,剥离数据库的大字段存储压力。 |
整个系统分为三个核心无状态服务,可以根据流量随时进行水平扩容(Horizontal Scaling):
[外部发件方 (Gmail/QQ)] [前端/API 调用方]
│ │
(TCP Port 25) (HTTPS / WSS)
▼ ▼
+--------------+ +---------------+
| L4 负载均衡 | | L7 负载均衡 | (Nginx/HAProxy/AWS ALB)
+--------------+ +---------------+
│ │
▼ ▼
+-----------------+ +-------------------+
| SMTP Cluster | | API Cluster | (Go + Gin)
| (go-smtp 多监听) | | (RESTful & SSE) |
| 域名过滤·多IP | | Bearer 统一鉴权 |
+-----------------+ | sk_/dk_ Key + JWT |
│ (原始 EML 流) +-------------------+
▼ │ (读写) ▲ (Pub/Sub)
+-----------------+ +-------------------+
| Message Queue | | Redis Cluster | (三层限流 + 缓存 + 事件)
| (NATS/Kafka) | +-------------------+
+-----------------+ ▲
│ (消费) │ (查询活跃用户)
▼ ▼
+-----------------+ +-------------------+
| Worker Cluster | ----> | MongoDB Cluster | (账号/域名/邮件元数据)
| (解析 MIME) | +-------------------+
+-----------------+ │
│ (上传附件) │
▼ ▼
+-----------------+ +-------------------+
| MinIO (S3) | | TTL Index 删除 | (自动 GC 清理)
| (附件存储集群) | +-------------------+
+-----------------+
- 防洪峰设计 (The
RCPT TOCheck): SMTP 节点在收到RCPT TO: <user@domain.com>指令时,必须去 Redis 中查询该邮箱是否存在(API 创建账号时会同步写入 Redis,设置与账号存活期相同的过期时间)。如果不存在,直接返回550 User unknown断开连接。绝对不能接收后再丢弃,否则系统会被垃圾扫描者瞬间打满 I/O。 - 异步解析与解耦 (Asynchronous Processing):
SMTP 节点接收到
DATA(邮件源码)后,只做简单的大小校验(例如限制 20MB),然后直接打包发入 NATS/Kafka 队列。SMTP 连接随即结束,释放资源。 - 分布式实时推送 (SSE via Redis Pub/Sub):
用户连在
API 节点 A上保持 SSE 长连接;邮件被Worker 节点 B解析完毕并存入 MongoDB;Worker 节点 B向 Redis 发布事件channel:user_id;API 节点 A收到事件,将数据通过 SSE 管道推给前端。
这里严格参照 mail.tm 的规范,并兼容 DuckMail API 标准,设计核心接口(基于 JSON,Bearer 统一鉴权)。
统一通过 Authorization: Bearer 头传递认证信息:
- API Key:默认推荐
Authorization: Bearer sk_xxx(兼容 DuckMail 的dk_xxx) - JWT:
Authorization: Bearer eyJ...
系统自动通过 sk_/dk_ 前缀区分 API Key 和 JWT 令牌。JWT 用户自动限定在其邮箱域名范围内。
GET /domains- 功能:获取可用域名列表(按 API Key/JWT 域名权限过滤)。
POST /accounts- 功能:创建临时邮箱账号。支持两种模式:
- 指定地址:
{"address": "user@domain.com", "password": "..."} - 自动生成人类化前缀:
{"domain": "domain.com", "password": "..."}
- 指定地址:
- 后端逻辑:校验域名、生成 bcrypt 哈希、存入 MongoDB、缓存到 Redis。自动模式下碰撞重试 5 次。
- 功能:创建临时邮箱账号。支持两种模式:
GET /accounts/{id}— 获取账号信息(需 JWT)。DELETE /accounts/{id}— 提前销毁账号(级联删除邮件和附件)。GET /addresses/random— 生成随机人类化邮箱地址(不创建账号,最多 50 个)。
POST /token— 登录并获取 JWT。
GET /messages— 分页获取邮件列表(需 JWT,不含大字段)。GET /messages/{id}— 获取邮件详情(含 HTML/Text 正文和附件元数据)。PATCH /messages/{id}— 标记已读。DELETE /messages/{id}— 删除指定邮件。
GET /messages/{id}/attachments/{attachmentId}— 下载附件。
GET /messages/{id}/download— 下载原始.eml格式文件。
GET /sse— Server-Sent Events 流(需 JWT)。
- 三层体系:全局 IP 限制 + 每 API Key 限制 + 每 API Key 每域名限制
- 所有计数器基于 Redis 固定窗口计数器实现(INCR + 首次设置 TTL)
内置智能邮箱前缀生成器,支持:
- 个人姓名模式(55%):15 种组合模式 × ~420 名 × ~420 姓 × 6 种后缀
- 昵称模式(30%):6 种组合模式 × ~170 昵称 + 形容词×名词组合
- 商务模式(15%):4 种组合模式 × ~50 前缀 × ~35 部门
- 国际化多元姓名(英、中拼音、西、法、德、日、韩等)
- 单域名超 50 亿种独立组合
- 全部小写 ASCII 字符
做这套后端,把 API 写出来只占 20% 的工作量,剩下的 80% 都在处理安全与极端情况:
- 对策:
- OS 级别:使用
iptables/ufw限制单 IP 的并发连接数和连接速率。 - 应用级别:三层 Redis 固定窗口限流(全局 IP + 单 Key + 单 Key 单域名)。
- 使用
go-smtp的中间件,在Connection和HELO阶段就进行黑名单校验。
- OS 级别:使用
- 对策:
emersion/go-message解析时,限制最大层级 50 层。- 严格设置
MaxMessageBytes(20MB),超过在 TCP 层截断。 - 始终使用
io.Reader流式处理,将附件通过 stream 直接透传写入 MinIO。
- 对策:
- API 查询增加条件
createdAt > now - expire_time,逻辑屏蔽已过期数据。 - 考虑按日分表或 Time Series Collections。
- API 查询增加条件
- 所有数据 TTL 机制,物理销毁不可逆。
- 禁止日志中包含邮件正文。
按照这套架构设计,配合几个节点(前端 Nginx,两台 Go 逻辑机,一套托管 MongoDB + Redis),足以支撑千万级别的每日吞吐量。