Skip to content

Abandoned cart recovery

Your storefront reports in-progress carts; litecommerce stores them, and a recovery cron emails customers who left one behind. Examples reuse the api() helper.

Capture a cart (public)

POST /api/v1/public/abandoned-carts — tenant header, no auth. Call it as the cart changes; it upserts on sessionId, so repeated calls update one row rather than piling up duplicates.

await api("/public/abandoned-carts", {
  method: "POST",
  body: JSON.stringify({
    sessionId: "sess_…",          // required, 8–200 — your storefront session id
    snapshot: {                   // required — frozen cart state
      items: [{ itemId: "…", variantId: "…", name: "Trail Pack 38L", quantity: 1, unitPriceInCents: 18900 }],
      subtotalInCents: 18900,
      totalInCents: 18900,
    },
    email: "dana@example.com",    // optional — capture once you have it
  }),
});
// → { ok: true, wasCreated: boolean }   (deliberately returns no row id or email)

email is optional so you can capture the cart before you know who it belongs to, then fill it in once the shopper enters it. A cart with no email can be stored but can't be emailed a recovery.

Review captures (merchant)

GET /api/v1/merchant/abandoned-carts?filter=pending — open to any member. filter is pending (not yet recovered, the default), recovered, or all.

How recovery works

The recovery send is live and automated:

  1. A scheduled cron sweeps carts that have an email and aren't yet recovered (recoveredAt IS NULL).
  2. It sends a recovery email via Resend and stamps recoveryEmailSentAt and recoveryEmailCount. The send is capped (an unconverted cart gets at most a couple of nudges spaced out over ~a day, never an indefinite stream).

You don't trigger any of this — capturing the cart (with an email) is all your storefront does; the platform handles the send cadence.

Conversion attribution isn't wired yet. recoveredAt exists as a column and the recovered / all merchant filters read it, but nothing stamps it when a captured cart turns into an order today — so in practice every captured cart stays in the pending list, and the send cap (not a conversion signal) is what stops the emails. Closing that loop — matching a new order back to its cart and stamping recoveredAt — is a planned follow-up that pairs naturally with order placement at hosted checkout.