独立站的支付漏斗跑通之后,下一个最容易崩的依赖是邮件。Stripe webhook 失败没人发现、用户密码重置收不到、订阅后没确认信 —— 这些不是支付问题,是邮件问题。
为什么是 Resend
候选三家:
- AWS SES — 便宜($0.10 / 千封),但要绑卡、IAM、配 SNS、签名版本。配置一次要半天,忘了哪个参数就 403。
- Mailgun — 老牌,API 还行,免费层 100 封/天太少。
- Resend — 新,API 设计现代,React Email / 模板调试器 / 控制台日志都好用。免费层 100 封/天 + 3000 封/月,独立站够用。
我选 Resend。理由:对独立开发者,API 体验比单封价格重要 10 倍。SES 一年省下来的钱,cover 不了一次配置错的半天。
域名验证(SPF + DKIM + DMARC)
发邮件前必须验证发件域名,否则收件方 99% 进垃圾箱。Resend 控制台加 laowe.club,自动给三个记录:
- SPF:
send.resend.com的 TXT - DKIM:resend._domainkey 的 CNAME
- DMARC:
_dmarc的 TXT(p=none 起手,观察两周)
DNS 改完等 5-30 分钟,Resend 控制台”Verify”按钮变绿。没验证的 from 地址 Resend 直接 422 — 这是好事,反向证明 API key、网络、域名都通了。
Worker 里发邮件
不用 SDK,fetch 直发 REST。SDK 加依赖、Node transport 比 workerd 重,一个 POST 用不着:
const RESEND_ENDPOINT = "https://api.resend.com/emails";
await fetch(RESEND_ENDPOINT, {
method: "POST",
headers: {
authorization: `Bearer ${apiKey}`,
"content-type": "application/json",
},
body: JSON.stringify({
from: "hello@laowe.club",
to: [input.email],
subject: "感谢订阅老蔚的社圈",
html: "<p>...</p>",
text: "感谢订阅!...",
}),
});
nodejs_compat 标志要在 wrangler.toml 打开(workerd 默认没 node:crypto 之类的内置)。
双邮件模式
订阅 / 下单 / 报名,都发两封:
- Admin notification — 给我自己,告诉我”有人刚 X 了”。
reply_to字段设成用户邮箱,我能直接 reply。 - User confirmation — 给用户,welcome / 订单详情 / 重置链接。
Resend 把两封算两次发送,免费层 3000 封/月,放心用。
软失败兜底(关键)
Worker 处理 Stripe webhook 时,邮件发失败不能抛错回 5xx —— Stripe 会无限重试,最终账单炸。正确做法:
try {
await sendOrderConfirmation(env, input);
} catch (error) {
// 失败落 KV,founder 后续手动恢复
await env.ORDERS.put(`order:${eventId}-email-failed`, JSON.stringify({...}));
console.error("[order-email] Resend failure:", error);
}
// 不管邮件成不成,200 返回给 Stripe
return new Response(null, { status: 200 });
KV 是兜底的最后一道。ORDERS namespace 里同时存订单本身 + 邮件失败标记,founder 后续可以扫 KV 找回”邮件漏发”的客户。
用户隐私(别把明文 email 打到日志)
console.log("[subscribe] captured: ${email}") 看似无害,实际:
- Cloudflare worker log 会进第三方日志系统(Datadog / Logflare / 自家)
- email 是 PII,合规上是问题(GDPR / CCPA)
- 一旦 log 平台被脱裤,所有订阅用户邮箱泄漏
修法:node:crypto sha256[:12] 指纹,日志只打 fp=01a57457d588 locale=zh,够查重不漏明文。
import { createHash } from "node:crypto";
const fp = createHash("sha256").update(email.toLowerCase()).digest("hex").slice(0, 12);
console.log(`[subscribe] captured: fp=${fp} locale=${locale}`);
调试心得(从 0 到 production 跑通)
- 先在 Resend 控制台”Test”按钮发一封,确认 from / API key / 网络通。失败的话原因明确(404 / 401 / 422),对照着改。
- 用
smoke-verify@example.com触发 —— Resend 会 422(example.com不允许),但反向证明 API 调用、auth、JSON 结构都对。 - 生产前用真邮箱走一遍完整流程(subscribe / forgot / verify),看 inbox 是不是真的到,没进垃圾箱。
- 盯 Resend 控制台 / Emails 页面 —— 发送状态、spam 投诉、bounce 都在这。生产问题 80% 一查就有。
30 天下来,三条链路(subscribe / forgot-password / email-verification)全部 production 验证通过,漏发 0、误发 0。
收尾
邮件在独立站里看着”基础设施”,实际是支付漏斗的最后一公里。Resend 选型 + workerd fetch + KV 软失败 + 指纹日志,这套组合让”邮件故障”从”用户投诉才发现”变成”控制台一眼看见,KV 自动兜底”。
如果你也在做独立站,这块投入半天能换几个月的省心。
— 老蔚