feat(billing): tier-aware upgrade emails
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
subscriptionTransitionsonly firesstartedfor a new subscription. The sync now records the last notified family per organization in billing Redis (billing:notified-family:{orgId}, atomicGETSET). A move to a higher family sendsplan-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/billinginpackages/auth: 77 pass.- Rendered the started (Pro and Team) and upgrade emails to plain text.
packages/emailtypecheck passes.packages/authtypecheck shows 5 errors inserver.tsandaccept-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 lintandbun run typecheckpass (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.