feat(emails): charge 1 credit per send (POST /api/emails)

SW
4mo ago
+130-22

Why

POST /api/emails charged nothing — sends (incl. agent/scheduled) were free and unmetered (chat#1815 billing item).

Pricing → 1 credit

1 credit = $0.01. Resend's per-email cost is ≤ $0.0004 (cheapest paid tier: Pro $20 / 50,000 emails; every tier is well under $0.01). Per the rule "round up to $0.01 if below, else charge at cost without markup" → 1 credit per email.

How

Mirrors the deep-research handler's gate→work→deduct pattern:

  1. ensureCreditsOrShortCircuit gates the send — returns 402 (no send) if the account can't cover it, auto-recharging via a card on file when possible. It does not deduct.
  2. Send.
  3. On success, recordCreditDeduction({ creditsToDeduct: 1, source: "api" }) — the atomic wallet+meter wrapper (one transaction: credits_usage debit + usage_events insert). A failed send (502) is not charged.

EMAIL_CREDIT_COST is a named, documented const. Only /api/emails charges — the shared processAndSendEmail (MCP send_email) path is unchanged.

Tests

6 handler tests green: gate→charge on success (cost 1, source: api), 402 + no send when insufficient, no charge on send failure. tsc/lint clean.

Tracked on chat#1815.

🤖 Generated with Claude Code


Summary by cubic

Charge 1 credit per email for POST /api/emails, with a credit gate upfront and deduction only after a successful send. Also tags usage with model_id = "POST /api/emails" and returns a CORS-safe 500 on unexpected credit gate failures.

  • New Features

    • Gate with ensureCreditsOrShortCircuit; 402 and no send if insufficient credits, with auto-recharge via CREDIT_AUTO_RECHARGE_FALLBACK_SUCCESS_URL.
    • On success, deduct EMAIL_CREDIT_COST = 1 via recordCreditDeduction({ source: "api", modelId: "POST /api/emails" }); no charge on 502 failures.
    • Only POST /api/emails is billed; the shared processAndSendEmail path remains unchanged.
  • Bug Fixes

    • Wrap sendEmailHandler in try/catch to return a { status: "error" } 500 with CORS when the credit gate throws.

Summary by CodeRabbit

  • New Features

    • Email sending now uses credit-based billing, checking available credits before sending.
    • Email sends now include billing metadata to support usage tracking.
  • Bug Fixes

    • Improved error handling so unexpected email send failures return a controlled response instead of an unhandled error.
    • Prevents email attempts when credits are insufficient, helping avoid failed sends and inconsistent billing.