KAIROS.WORKSPACE
技术宇宙 / ONLINE
返回项目墙
平台工程活跃维护本地5 分钟

周报订阅与投递流水线

从邮箱确认、MySQL 持久化到可插拔邮件 provider 的订阅系统:状态机、幂等、重试和投递回执的完整链路说明。

#Node.js#MySQL#订阅#邮件投递

它解决什么问题

内容站希望读者“按主题订阅更新”,但订阅是一个容易做错的功能:要防误填他人邮箱、要防刷、要在数据库故障时不丢状态、要保证确认和退订链接重复点击不出错。这套流水线覆盖从表单到回执的完整链路。

架构与关键决策

表单 → POST /api/subscribe(校验+限流)
     → subscription-store(pending 记录,MySQL 权威存储)
     → 确认邮件(token 链接)→ /api/subscribe/confirm
     → active / 退订 → unsubscribed
后台:周报预览(/admin/weekly-digest)→ POST /api/admin/weekly-digest 投递
     → email provider adapter(console | http-api)→ 投递记录 + 回执
  • 双阶段确认(double opt-in):提交只产生 pending,邮箱点击确认后才 active——防止把别人邮箱填进列表。
  • 状态机九态pending / confirmed / already-active / unsubscribed / already-unsubscribed / expired / invalid / conflict / rate-limited,公开响应不返回 token 本体,确认与退订幂等(重复点击返回同型结果,不重复变更)。
  • fail-closed 存储保护:生产缺少 DATABASE_URL 时禁止订阅写入而非静默落内存;开发态内存存储与 MySQL 存储共用同一接口层(subscription-store)。
  • 可插拔邮件 adapterconsole(沙箱打印)与 http-api(配置化外呼)两种 provider;发送开关默认关闭,投递记录表保存每封的状态、失败原因与最多 3 次的有限重试,周期幂等防同一周重复群发。
  • 限流按 IP+账号维度,10 分钟窗口 5 次,429 响应携带可渲染文案。

难点与处理

  1. 错误语义透传:服务故障(503)与输入错误(400)不能共用一套前端文案——接口为每个失败分支返回独立 state + message,前端优先渲染服务端 message(详见踩坑《订阅接口 503 被前端翻译成了“邮箱格式不对”》)。
  2. 无 JS 路径可用:表单原生 POST 提交到同一端点,服务端 303 重定向到带状态参数的结果页,JS 版只是把反馈前置为行内提示(详见文章《渐进增强不是口号》)。
  3. 数据迁移安全:订阅表走版本化 migration(schema_migrations + advisory lock + checksum),已在 staging 完成备份/隔离库恢复演练。

当前状态与边界

  • 已完成:全链路沙箱闭环、migration 与恢复演练、provider adapter 与投递记录。
  • 待完成:staging 真实发送回执验证与生产部署验收(受外部授权阻塞)。系统在所有未验证环节均选择显式失败而不是模拟成功。

下一步

真实 SMTP 验证通过后接入 bounce 处理回调;订阅统计看板增加按主题的确认率视图。