View Storefront →

Cart recovery (abandoned checkout emails)

How the auto-drip works, when to send manually, and how to read the data.

When someone starts a checkout but doesn't pay, we don't just lose them. The platform follows up automatically with a 3-stage email drip, and you can also send recovery emails manually from the admin if you want a personal touch.

How the auto-drip works

A scheduled task runs once an hour and looks at every cart that's still in pending_payment status. For each one:

Stage When it sends Subject style
1 2-24 hours after cart abandoned "You left X in your cart"
2 24+ hours after Stage 1 "Only N seats left for X on [date]"
3 48+ hours after Stage 2 "Last chance: 10% off X" (with COMEBACK10 promo code)

After Stage 3, the cart "expires" from the drip — no more emails. If the customer comes back and pays at any point, the system flips the cart to recovered and stops sending.

There's a per-stage cap of 25 sends per hourly run so the platform can't accidentally torch its sender reputation by blasting a backlog of 200 emails in a single batch.

Where to find them

Admin sidebar → Abandoned Carts. Three tabs:

  • Carts — every pending cart in the last 14 days, grouped by stage status (uncontacted, in drip, recovered, skipped, expired)
  • Feedback — customer responses to the "what stopped you?" CTA in the recovery emails
  • Email Templates — edit the email copy itself; changes apply to the next send

Sending manually

On any cart row, click the action button. The form has two modes:

Test mode (default): sends a preview to whatever email you type (defaults to your own). Safe to fire whenever you want to check copy or see what the customer would receive. Doesn't touch the real customer.

To customer mode: requires a confirmation checkbox. Sends a real recovery email to the actual customer on the cart, marks the cart as having received that stage so the auto-drip skips it on the next run, and shows up in the customer's Contact History.

Use manual send when:

  • A customer phoned in and you want to nudge them with a friendlier, faster-than-cron email.
  • You're testing copy changes against a real cart before they go live in the auto-drip.
  • The cart is sitting at the edge of a stage window and you want to fire now rather than wait for the next hourly run.

Reading the data

Each row shows the cart's current state. The state controls whether it's eligible for auto-sends:

  • No state: hasn't been touched yet. Will become Stage 1 candidate once it's 2+ hours old.
  • email_1 / email_2 / email_3: at this stage of the drip; cron is waiting for the gap before next stage.
  • recovered: customer paid. Drip stops automatically.
  • expired: 14+ days old or done with all 3 stages. Drip stops permanently.
  • skipped: someone clicked Skip on this cart. Drip never sends to it. Click Unskip to re-enable.

When something goes wrong

  • Customer says they got the email twice. Almost always means the auto-drip and a manual send overlapped. Manual send marks the cart as having received the stage so the auto-drip should skip; if it still sent, ping Matt with the cart ID.
  • The drip isn't sending at all. Check Vercel env: CART_RECOVERY_DRY_RUN should be false in production. If it's true, the cron walks every candidate but skips the actual send (a safety setting from when we were iterating on copy).
  • A cart shows "recovered" but the customer hasn't paid. The cron's "recovered" check looks for any other paid order from the same email on the same show. If they paid through a different email or for a different show, it won't catch it. Refresh the cart in admin and the state will update on the next cron tick.

Tracking

Every link in every recovery email carries UTM params (utm_source=recovery, utm_medium=email, utm_campaign=cart_recovery_<stage>, utm_content=ord_<order_id>). When the customer clicks, we know exactly which stage drove it. Combined with the Resend tracking subdomain (open + click pixels), the customer detail page shows you whether each recovery email was delivered, opened, and/or clicked.