邮件订阅在单台节点上:sqlite 文件 —— 1,258 行,不用平台
双重确认、一键退订和发送 CLI,共 1,258 行可审计代码 —— 让它变短的 RFC 8058 请求头,以及诚实的送达率屋顶。
■ space · 野外笔记 · fig 1
■ 先说答案
整份订阅系统是 1,258 行:双重确认、一键退订、发送 CLI —— 没有平台账号。
一键退订就是 一个请求头 —— RFC 8058,2024 年 2 月起 Google 和 Yahoo 强制要求。
诚实的屋顶:不对齐 DKIM 的话, mail-tester ~5/10,严格一点的收件方可能丢进垃圾箱 —— 对小名单够用;加个中继就能治好。
1,258行 —— 全部可读
2张表 —— 订阅者 + 发送记录
1退订只靠一个请求头
一份邮件订阅,其实是三个端点、两张表,外加一个大多数平台都藏起来的请求头。
01非要说自托管图什么?
诚实的盘点 —— 一屏之内的交易;文件赢的是所有权,平台赢的是不用动脑
| 这台服务器 | 托管平台 | |
|---|---|---|
| 我能读的代码行数 | 全部 1,258 行 | 0 |
| 对名单的所有权 | 一个 SQLite 文件 | 导出按钮 |
| 退订 | RFC 8058 请求头,自己签名 | 面板里的一个设置 |
| 送达率屋顶 | DKIM 对齐前 ~5/10 mail-tester | 他们的名声 |
| 到首封发出的时间 | 一个周末 | 一个晚上 |
平台赢—送达名声,加上不用动脑
文件赢—名册在编辑器里就能审读,离消失只差一条
rsync 的距离这份要求—2024 年 2 月,Google 和 Yahoo 把一键退订立成大批量发件人的规则 —— RFC 8058
02订阅路径
一个 POST,三道岗 —— 签名是唯一的权威,所以门在账本入睡时也能开
FIG 1 · 打盹者的门按键 1 · 2 · 3 —— 或用按钮
路线 2 · 确认 —— 表盘读的是封印 · 账本入睡 · 判词 仍然确认
tag 携带 base64url(email|action) 加一道 HMAC-SHA256 封印 —— 不开会话,不建 token 表,没有可查的东西
curl -X POST https://mrsaynothing.dev/letters/api/subscribe \
-H 'content-type: application/json' \
-d '{"email":"[email protected]"}'
db.ts—两张表:
subscribers,一个 CHECK 把 status 钉在 pending · confirmed · unsubscribed;再加上 sends,审计用的流水蜜罐—一个名字叫
website humans never see — a filled field buys a fake ok 的表单字段,人类永远看不见 —— 填了它,就买到一个假的 the bucket而已水桶
enumeration每 IP 每小时 10 次请求 · 每地址每 10 分钟只许重发一次枚举攻击
the status lock无论地址已否订阅,回复刻意一字不差状态锁
SET status='maybe' is refused一条 CHECK 约束只干一件事 —— THE TOKEN PRESS
压 token 的机台
import { createHmac, timingSafeEqual } from 'node:crypto';
// payload = base64url(email|action), MAC = HMAC-SHA256(payload)
export function sign(email: string, action: 'confirm' | 'unsub'): string {
const payload = Buffer.from(`${email}|${action}`).toString('base64url');
const mac = createHmac('sha256', config.hmacSecret).update(payload).digest('base64url');
return `${payload}.${mac}`;
}
你那一半 —— 在这里就能算payload = base64url(email|action) → cmVhZGVyQGV4YW1wbGUuY29tfGNvbmZpcm0
机盒那一半 —— 砌进了墙MAC = HMAC-SHA256(payload, secret) · 43 个字符 —— 在服务机盒上算出
链接上的 tokentoken = cmVhZGVyQGV4YW1wbGUuY29tfGNvbmZpcm0.mac
验证—
timingSafeEqual 对一个重算出的 MAC —— 没有 token 表,没有要管的过期那把钥匙—住在 repo 之外,运行时以只读方式挂载 —— 机台算你那一半,拒绝伪造另一半
退订 token—故意永不过期 —— 同意的机制必须永远有效;要撤销,就轮换密钥
gmail 的怪癖—一键退订会射出一个跨域 POST —— 那条路只信 MAC;同源检查退到一旁
一个在数据库睡着时仍然好用的确认链接,是少一样会烂掉的东西。
04坏掉了什么,它不做什么
三处故障,已修 —— 屋顶也一并印出,是安装自己印的
ERR_PNPM_IGNORED_BUILDS
pnpm install --frozen-lockfile本地跑得好好的,一进容器就死 —— esbuild 的 postinstall 脚本从未获准执行。修法:把那份小小的 pnpm-workspace.yaml 声明进镜像,指明允许哪些构建脚本。半天才找到,一个文件修好。CSRF 挡住了 GmailGmail 服务器发出的一键 POST 显然没有 same-origin 凭据。这条路由现在只信 token —— 而 token 不可伪造。
送达率屋顶不对齐 DKIM,mail-tester 给这套安装打 5/10 上下,严格的收件方还可能丢进垃圾箱。这就是省掉付费中继的诚实代价 —— 修法早就知道,等名单大到值得再说。
tracking—不搞打开率追踪 —— 这是原则:追踪像素正是人们不信任邮件订阅的原因
范围—一张名单,不分段,除了手动或定时器唤起的 CLI,没有任何排程
备份—拷文件,还原文件 —— 服务器运行期间,WAL 模式让拷贝始终一致
其余—在 systemd 式的庇护下盯着它启动,是剩下的运维负担
# the --dry flag renders the full text and HTML for review before anything moves
cli.mjs send --subject ... --file post.md
■ 诚实印记
实测环境:node:sqlite (WAL) · HMAC-SHA256 token · RFC 8058 请求头 · 普通 SMTP。 首次发布 2026-09-26。行数、限流和 mail-tester 分数都是服务器自己的数字;RFC 引自 IETF 记录,Gmail/Yahoo 的要求引自其发件人指南。迁移备注:跑这篇东西的平台后来下线了 —— 这篇文章留下的,是当时建了什么、学到了什么的记录。
单文件订阅的时间到此为止 · 没有任何 fetch · 全程本地