Skip to content

feat: automate abandoned-checkout recovery email firing (drop the manual CSV step) #2279

@aalemayhu

Description

@aalemayhu

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions