resend.emails.send 替换为对 SendGrid、Postmark、Amazon SES 或您自己的 SMTP relay 的调用。- 在 dashboard 中创建用于电子邮件的自定义额度权益。
- 将额度附加到订阅计划和一次性充值产品。
- 通过 Resend 发送电子邮件,并在 ledger 中记录每次发送扣除一个额度。
- 从前端读取客户的实时额度余额。
- 验证 Dodo Payments webhooks,并处理
credit.balance_low,在客户余额归零前发出提醒。
What We’re Building
MailKit 销售两种产品:- 一个 Dodo Payments 账户。请在 test mode 中完成所有构建工作。
- 一个免费的 Resend 账户和 API key。
- Node.js 22 或更高版本,以及 TypeScript 基础知识。
第 1 步:创建电子邮件额度权益
额度权益定义了 MailKit 销售的单位:发送一封电子邮件。
The Credits tab under Products lists all your credit entitlements.
Open the Credits Section
- 登录 Dodo Payments dashboard。
- 点击侧边栏中的 Products。
- 选择 Credits 标签页。
- 点击 Create Credit。
Configure the Credit Unit
Email CreditsCredit Type:Custom UnitUnit Name:emailDefine Precision:0。电子邮件是整数单位,因此余额不需要小数。Credit Expiry:30 days。未使用的额度会在发放 30 天后过期。Leave the Other Defaults
Save and Copy the Credit ID
cde_ 开头。后端会使用它读取余额并创建 ledger entries。Email Credits 权益已准备就绪。接下来,创建向客户发放该权益的产品。第 2 步:创建计划和充值包
创建两个附加同一Email Credits 权益的产品:一个每个计费周期提供 5,000 封邮件的 Subscription 计划,以及一个按需额外增加 5,000 封邮件的 One Time 充值产品。
MailKit Plan($19/月,5,000 封邮件)
Create the Subscription
- 前往 Products 并点击 Add Product。
- 输入产品详细信息:
MailKit PlanDescription:5,000 transactional emails per month.- 在 Pricing Type 下选择 Subscription。
- 设置 recurring price:
19.00Repeat payment every:1 月Currency:USDAttach the Email Credit Entitlement
Email CreditsCredits issued per billing cycle:5000Low Balance Threshold (%):20。当余额低于每个周期发放额度的 20%(即 1,000 封邮件)时,Dodo Payments 会发送 credit.balance_low。Import Default Credit Settings:开启,这样产品会使用第 1 步中设置的 30 天过期时间。将额度添加到产品,然后保存产品。复制产品 ID,该 ID 以 pdt_ 开头。Top-Up Pack($9 一次性支付,5,000 封邮件)
Create a One-Time Product
- 前往 Products 并点击 Add Product。
- 输入产品详细信息:
Email Top-Up PackDescription:Add 5,000 emails to your MailKit balance.- 在 Pricing Type 下选择 One Time。
- 设置价格:
9.00Currency:USDAttach the Credit Grant
- Select credits:
Email Credits - No of credits issued:
5000
第 3 步:设置后端
构建 Express server,用于创建 checkouts、发送电子邮件、读取余额和接收 webhooks。Initialize the Project
package.json:Configure Environment Variables
.env:DODO_PAYMENTS_WEBHOOK_KEY。请在 resend.com/api-keys 创建 Resend API key。Build the Server
server.ts。服务器提供五个 routes:subscribe checkout、top-up checkout、balance read、send 和 webhook receiver。Add a Demo UI
public/index.html。它通过一个简单表单调用每个 route,以便您在浏览器中测试流程:第 4 步:连接 Webhook Endpoint
credit.balance_low event 可让您在客户额度用尽前发出提醒。没有它,客户要等到电子邮件发送失败后才会首次发现问题。
Expose Your Local Server
https://1234abcd.ngrok-free.app。Register the Endpoint in Dodo Payments
- 前往 Developer → Webhooks 并点击 Add endpoint。
- 输入 URL
https://1234abcd.ngrok-free.app/webhooks/dodo,并使用您自己的 tunnel host。 - 选择 events
credit.added、credit.balance_low和credit.rolled_over。 - 点击 Create endpoint。
- 从 endpoint 的 Overview 标签页复制 signing secret,并将其作为
DODO_PAYMENTS_WEBHOOK_KEY填入.env。 - 重启 server。
第 5 步:测试完整流程
Start the Server
MailKit running on http://localhost:3000。在浏览器中打开该 URL。Subscribe a Test Customer
- 在第 1 部分输入测试电子邮件地址和姓名,然后点击 Get checkout link。
- 打开链接,并使用测试卡完成 checkout。
- 在 dashboard 中前往 Customers,复制新客户的 ID,该 ID 以
cus_开头。
Send an Email
- 将客户 ID 粘贴到第 3 部分。
- 保持 To 为
delivered@resend.dev,这是一个可接收所有邮件的 Resend test address。 - 点击 Send。
Trigger the Low-Balance Webhook
- 在 Customers 中打开客户,选择 Credits 标签页,然后选择 Email Credits。
- 点击 Apply Credit/Debit,选择 Debit,并输入
4000。此时余额正好为 1,000,尚未低于阈值。 - 从 demo 再发送一封电子邮件。余额降至 999。
Buy a Top-Up Pack
- 将客户 ID 粘贴到第 4 部分。
- 点击 Buy 5,000 emails 并完成测试 checkout。
- 刷新余额。余额增加 5,000。
transaction_type: "credit_added" 的 credit.added event。其背后的 grant 具有 source_type: one_time,您可以通过 List Customer Grants API 读取。充值额度会添加到订阅额度中。扣除额度时,会优先使用最早过期的 grant;如果两个 grant 同时过期,则使用较早创建的 grant。Test the Hard Stop
402 响应:402 是应用的 enforcement。请将 Dodo Payments balance API 视为事实来源,不要在 client 上缓存余额。故障排除
Webhook signature verification fails (401)
Webhook signature verification fails (401)
express.json() 会将 body 替换为已解析的对象,因此 verification 会失败。将 /webhooks/dodo 与 express.raw({ type: 'application/json' }) 一起注册,并置于 app.use(express.json()) 行之前。然后检查 DODO_PAYMENTS_WEBHOOK_KEY 是否与 endpoint Overview 标签页中的 signing secret 匹配。Balance is 0, customer not found, or credits don't deduct
Balance is 0, customer not found, or credits don't deduct
- 客户已完成 checkout。额度会在支付成功时发放,而不是在创建 checkout session 时发放。
.env中的CREDIT_ENTITLEMENT_ID与产品附加的额度匹配。余额和 ledger calls 使用此 ID,因此不匹配会读取或扣除另一个额度。- 您传入的
customer_id是 Dodo Payments customer ID(以cus_开头),而不是您自己的 database 中的 ID。
Resend rejects the recipient
Resend rejects the recipient
onboarding@resend.dev 只会向您 Resend 账户上的电子邮件地址或 delivered@resend.dev 发送邮件。若要发送给其他人,请验证域名,并使用该域名下的 from 地址。您构建的内容
One Reusable Credit Unit
Email Credits 只定义一次,并附加到订阅计划和充值包。Subscription with Prepaid Allowance
Top-Up Pack
Direct Ledger Debits
createLedgerEntry,无需 meter,也没有 aggregation delay。将 Resend message ID 作为 idempotency key,可阻止同一发送被重复扣除。