返回文章列表

Resend + Cloudflare Workers 邮件发送工程实践

Stripe 漏斗的最被低估依赖是邮件。Resend + workerd + KV 软失败兜底,跑通了三条生产链路(subscribe / forgot-password / email-verification)。

独立站的支付漏斗跑通之后,下一个最容易崩的依赖是邮件。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 之类的内置)。

双邮件模式

订阅 / 下单 / 报名,都发两封:

  1. Admin notification — 给我自己,告诉我”有人刚 X 了”。reply_to 字段设成用户邮箱,我能直接 reply。
  2. 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 跑通)

  1. 先在 Resend 控制台”Test”按钮发一封,确认 from / API key / 网络通。失败的话原因明确(404 / 401 / 422),对照着改。
  2. 用 smoke-verify@example.com 触发 —— Resend 会 422(example.com 不允许),但反向证明 API 调用、auth、JSON 结构都对。
  3. 生产前用真邮箱走一遍完整流程(subscribe / forgot / verify),看 inbox 是不是真的到,没进垃圾箱。
  4. 盯 Resend 控制台 / Emails 页面 —— 发送状态、spam 投诉、bounce 都在这。生产问题 80% 一查就有。

30 天下来,三条链路(subscribe / forgot-password / email-verification)全部 production 验证通过,漏发 0、误发 0。

收尾

邮件在独立站里看着”基础设施”,实际是支付漏斗的最后一公里。Resend 选型 + workerd fetch + KV 软失败 + 指纹日志,这套组合让”邮件故障”从”用户投诉才发现”变成”控制台一眼看见,KV 自动兜底”。

如果你也在做独立站,这块投入半天能换几个月的省心。

— 老蔚

键盘快捷键

先按 g 再按下面的键。? 打开这个面板。