feat(billing): tier-aware upgrade emails

Merged
KI
1d ago
+396-62

What & why

  • Started email names the right plan. It listed the same three features for every tier and used the raw tier id ("pro") as the name. It now takes the plan family from Autumn (Pro, Team, Ultra) and lists what that family includes. The first paid family gets the base list (remote access, automations, Linear and Slack, mobile, plus cloud hours when the plan has them). Higher families show "Everything in {previous}" plus their cloud workspace hours, matching the pricing page.
  • New upgrade email. Moving up a family while already paying (Pro to Team or Ultra) sent nothing, because subscriptionTransitions only fires started for a new subscription. The sync now records the last notified family per organization in billing Redis (billing:notified-family:{orgId}, atomic GETSET). A move to a higher family sends plan-upgraded ("You're on Paradev {plan}"). A first plan, a repeat sync and a downgrade send nothing. On first deploy every paying org just gets its family recorded, so no backlog of emails goes out.
  • Email failures are caught and logged; they never fail the billing sync.

Stacked on #9072 (same email files). Will rebase onto main once #9072 merges.

How I tested it

  • New plan-family.test.ts: upgrade detected for Pro to Team; nothing for first plan, repeat sync or downgrade.
  • bun test src/lib/billing in packages/auth: 77 pass.
  • Rendered the started (Pro and Team) and upgrade emails to plain text.
  • packages/email typecheck passes. packages/auth typecheck shows 5 errors in server.ts and accept-invitation-endpoint.ts, files this PR doesn't touch (better-auth types); none in changed files.
  • Not tested against a live Autumn upgrade.

Checklist

  • PR title follows conventional commits (type(scope): subject)
  • bun run lint and bun run typecheck pass (CI fails on lint warnings too)
  • "Allow edits from maintainers" is checked on fork PRs
  • I have signed the CLA (the bot asks on your first PR)

Summary by cubic

Updates billing emails to be plan-aware. The started email now uses the actual plan family name (Pro, Team, Ultra) and lists what that family includes, instead of the same three generic features with the raw tier id. Moving up a family while already paying (Pro to Team or Ultra) now sends a new plan-upgraded email, which previously sent nothing.

The sync records the last notified family per organization in billing Redis (billing:notified-family:{orgId}) inside the per-organization advisory lock, so overlapping syncs can't reorder families and repeat an email. A failed send restores the previous family unless a newer sync already moved it, so the next sync retries. First plans, repeat syncs, and downgrades send nothing, and no backlog of emails goes out on first deploy. Email failures are caught and logged; they never fail the billing sync.