☰
MailCheap如何把20万封邮件铺开到几个小时?QStash批量发送架构揭秘
2026/10/18 1:50:52 网站建设 项目流程

【免费下载链接】mailcheap

Newsletters where you pay for the emails you send, not for dormant subscribers, at a fraction of the price of beehiiv and the like. Open source: self-host it, or use mailcheap.ai.

项目地址:https://gitcode.com/gh_mirrors/mai/mailcheap
点击查看免费下载

MailCheap 是一款开源的 Newsletter 邮件营销平台,主打“只为发出的邮件付费,不为沉睡订阅者付费”的按量计费模式。当你点击发送一封有 20 万收件人的活动时,MailCheap 并不会一股脑把邮件塞给邮件服务商,而是借助 Upstash QStash 把发送任务切成 4000 个小批次,按计算好的时间轴均匀铺开,再配合限流、重试与首批发放保护,稳稳地铺开到几个小时里。这篇文章带你拆穿这套批量发送架构的每一个关键零件。

一、为什么 20 万封邮件不能一次性发出?

直接全速发送 20 万封邮件,几乎必然踩雷:

  • 触发服务商限流:SES 等邮件服务商有账号级发送速率上限,超限就返回ThrottlingException,整批邮件被拒;
  • 损伤发送域名信誉:短时间大量陌生邮箱收到你的邮件,硬反弹率飙升,轻则被暂停发送,重则整个账号被封;
  • 新域名尤其脆弱:冷启动的发送域名需要“热身”才能逐步提升配额。

所以核心问题不是“能不能发完”,而是**“以什么节奏发完”**。MailCheap 的答案是:把发送变成一个可计算、可恢复的分布式流水线。

二、总览:从点击“发送”到邮件出栈

整个流程可以概括为五步:

  1. 建消息:为每个收件人生成一条Message记录(状态queued),批量写入数据库;
  2. 算节奏:根据账号历史发送量算出本次发送应铺开的总时长;
  3. 切批次:每50 封为一个批次(SEND_BATCH_SIZE),20 万封就是 4000 个批次,每个批次是一个独立的 QStash 任务;
  4. 定时投递:QStash 按notBefore时间逐个把批次“唤醒”到/api/jobs/send-batch接口;
  5. 逐封出栈:批次内逐封检查限流、抑制名单、套餐额度,交给邮件服务商发送,并记录结果。

三、批次化:200000 ÷ 50 = 4000 个 QStash 任务

切批的核心在 lib/sending.js 的publishBatches函数:

  • 用chunk(messageIds, SEND_BATCH_SIZE)把消息 ID 切成每批 50 个;
  • 每个批次对应一个指向/api/jobs/send-batch的 QStash 任务,body 只带accountId和messageIds;
  • 任务用client.batchJSON以 100 个一组发布到 QStash,避免发布本身成为瓶颈;
  • 所有批次共享同一个流控键{ key: "account-<accountId>", parallelism: 1 },同一账号任意时刻只有一个批次在跑,多个账号的批次则并行执行。

接收端 app/api/jobs/send-batch/route.js 做了严格校验:只接受 1 到 100 个(MAX_BATCH_MESSAGES)合法的消息 ID,maxDuration = 300保证 Serverless 函数有 5 分钟处理窗口。

四、铺开算法:发送时长是怎么算出来的

“铺开到几个小时”的时长不是拍脑袋,而是 lib/send-spread.js 里的一套公式,配合 lib/config.js 中的WARMUP配置:

参数值含义
quickEmails200不超过 200 封直接全速发
days30账号从首次活动起进入 30 天热身期
growth2 → 5热身第 1 天可全速发“已证明量”的 2 倍,第 30 天起为 5 倍
minHours4需要铺开时,基础时长 4 小时
hoursPerDoubling4每超出配额一倍,再加 4 小时
maxHours72最久铺开 72 小时

“已证明量”(proven volume)指该账号最近 60 天内、反弹与投诉率健康、且至少发出去一天的最大单场活动量。例如一个已证明量为 10 万、热身第 30 天的账号发 20 万封:

配额 = 100000 × 5 = 500000?不对——若已证明量只有 2 万,配额 = 20000 × 5 = 100000;20 万 ÷ 10 万 = 2 倍 → 时长 = 4 + 4×log₂(2) =8 小时。

随后getBatchStarts把 4000 个批次的启动时间均匀分布:第一批立即执行,最后一批在结束时刻,中间的批次等间隔启动。QStash 的notBefore延迟投递让每个批次“准时上班”,无需任何常驻调度进程。

五、首发保护:新联系人先试 5% 再放行

买了或爬来的邮箱列表是信誉杀手。MailCheap 的对策在 lib/first-send.js 与配置FIRST_SEND:

  • 从未收到过邮件的联系人,只先发5%(最少 200 封、最多 2000 封)的一小片,其余标记为held;
  • QStash 在 3 小时后投递/api/jobs/check-first-send检查任务,统计这一小片的送达、反弹、投诉;
  • 反弹率超过暂停线(2%)→ 活动暂停,等用户清理列表后手动恢复;超过封号线(5%)→ 整个账号被冻结;
  • 数据健康 → 放出的held消息继续占剩余时间轴发完。

这一小片就像“试菜”:出了问题,损失被控制在 2000 封以内,而不是 20 万封。

六、失败与重试:签名验证 + 503 弹性回弹

批量任务跑在 Serverless 上,任何一环都可能挂。MailCheap 用三层机制兜底:

  1. 签名验证:lib/qstash-route.js 的withQStashSignature要求所有任务请求必须由 QStash 签名,否则返回 403,防止任何人伪造批次任务刷你的发送配额;
  2. 限流回弹:服务商返回限流(TooManyRequestsException)时,批次把未发出的消息改回queued,接口返回 503——QStash 看到非 2xx 会自动按退避策略重试;若服务商明确告知恢复时间(retryAfter),则用publishBatchRetry以延迟任务的形式“定点重发”;
  3. 连续失败熔断:一个批次内连续 5 封失败就暂停整场活动并发告警,不再硬碰;任务连续失败 3 次后进入 QStash 死信队列并触发管理员告警。

每封邮件发送前还有一条“认领”操作(queued → sent的原子更新),保证即使任务被重复投递,同一封邮件也只会出栈一次。

七、限流车道:多实例共享一条速率

20 万封邮件会拆成上千个 Serverless 并发实例,如何不让它们集体超速?lib/send-rate.js 实现了“双保险车道”:

  • 进程内间隔:每个发送通道(lane)同步预留发送槽位,按配置速率(sendRatePerSecond)逐封排队;
  • 跨实例共享:通过 Upstash Redis 的全局限流器,所有实例共享同一个每秒计数;
  • Redis 不可用时自动降级为仅进程内限流,超限部分交给服务商限流兜底(反正会被重试)。

这样,无论 QStash 同时唤醒多少个批次,对外的发送速率始终平滑可控。

八、关键文件索引

想深入源码,按这条路径读:

  • 发送主流程(建消息、切批、发布任务):lib/sending.js
  • 铺开时长与批次启动时间计算:lib/send-spread.js
  • QStash 任务签名与失败告警:lib/qstash-route.js
  • 批次执行入口:app/api/jobs/send-batch/route.js
  • 首发检查任务:app/api/jobs/check-first-send/route.js
  • 定时发送(含 QStash 免费层 7 天延迟的接力机制):lib/scheduling.js
  • 限流车道:lib/send-rate.js
  • 全局常量(批次 50 封、热身 30 天、暂停/封号阈值):lib/config.js

总结

MailCheap 的批量发送架构没有复杂的消息中间件集群,靠的是三个朴素思想的组合:切小(每批 50 封)、铺开(按已证明量对数增长计算时间轴,用 QStash 延迟投递实现)、兜底(签名验证、原子认领、503 重试与首发试菜)。这套设计让 20 万封邮件可以在几小时内平稳出栈,同时把对域名信誉的伤害锁在最小范围内——这正是它敢于按“发出的邮件”而非“订阅者数量”收费的底气所在。

【免费下载链接】mailcheap

Newsletters where you pay for the emails you send, not for dormant subscribers, at a fraction of the price of beehiiv and the like. Open source: self-host it, or use mailcheap.ai.

项目地址:https://gitcode.com/gh_mirrors/mai/mailcheap
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询