Current state
POST /api/ops/send-abandoned-checkout-recovery exists (shipped in #2270, RequireOpsAccess) but is fed manually: extract abandoned checkout emails from a Stripe CSV on Al's box, paste them into the request body, fire once.
Manual = it doesn't happen on a cadence = revenue left on the table.
Goal
Remove the manual step. The system should detect abandoned Stripe Checkout sessions and email those users automatically, on a predictable schedule, with idempotency so a single user isn't emailed twice for the same session.
Mechanism — open question
Two viable triggers:
Option A — Stripe webhook on checkout.session.expired
- Stripe fires this event when a Checkout Session expires (default 24h after creation).
- Pros: event-driven, no polling, exact timing.
- Cons: needs webhook signature verification (already in place for other events), needs storage to dedupe replays.
Option B — Daily cron pulling from Stripe
stripe.checkout.sessions.list({ status: 'expired', created: { gte: <yesterday> } })
- Pros: simple, no new webhook surface, easy to backfill.
- Cons: timing is approximate, polling cost.
Lean: A. We already verify Stripe webhooks for subscription events; this is an additional event type, not a new mechanism. Confirm before building.
Dedupe
New table or column: track which Stripe session IDs have already received a recovery email. Reject duplicates at the use case.
Acceptance
- Stripe webhook on
checkout.session.expired (or cron, depending on the trigger decision) fires SendAbandonedCheckoutRecoveryUseCase automatically
- Each session ID is emailed at most once (dedupe column or table)
- Backfill ran once with the 234-email CSV on Al's box, then the manual endpoint is retired
- Performance tab
/ops/performance shows recovery-email volume (extension to existing observability)
Out of scope
Effort: S–M. Dependency on #2270 which is already shipped.
Current state
POST /api/ops/send-abandoned-checkout-recoveryexists (shipped in #2270, RequireOpsAccess) but is fed manually: extract abandoned checkout emails from a Stripe CSV on Al's box, paste them into the request body, fire once.Manual = it doesn't happen on a cadence = revenue left on the table.
Goal
Remove the manual step. The system should detect abandoned Stripe Checkout sessions and email those users automatically, on a predictable schedule, with idempotency so a single user isn't emailed twice for the same session.
Mechanism — open question
Two viable triggers:
Option A — Stripe webhook on
checkout.session.expiredOption B — Daily cron pulling from Stripe
stripe.checkout.sessions.list({ status: 'expired', created: { gte: <yesterday> } })Lean: A. We already verify Stripe webhooks for subscription events; this is an additional event type, not a new mechanism. Confirm before building.
Dedupe
New table or column: track which Stripe session IDs have already received a recovery email. Reject duplicates at the use case.
Acceptance
checkout.session.expired(or cron, depending on the trigger decision) firesSendAbandonedCheckoutRecoveryUseCaseautomatically/ops/performanceshows recovery-email volume (extension to existing observability)Out of scope
Effort: S–M. Dependency on #2270 which is already shipped.