# Abandoned Checkout Recovery in GoCommerce

> GoCommerce now spots a basket left behind within minutes, sends a short run of reminder emails, and credits the order that brings it back. Here is how.

- Canonical: https://kitcommerce.store/blog/abandoned-checkout-recovery/
- Last updated: 2026-10-10

---

Blog

## Abandoned checkouts: reminders that stop the moment a basket becomes an order

By [itswadesh](https://github.com/itswadesh) · 10 October 2026

A shopper who typed their email and then left is the order closest to happening. GoCommerce’s new `cart-recovery` module notices that basket within minutes, emails the shopper a few times, and stops the moment the basket becomes an order. Then it shows you which orders it actually brought back.

### What counts as abandoned

A basket with something in it that sits untouched long enough gets a record of its own, with the items and prices as they were.

| Type | What it means | Idle time before it counts (default) |
| --- | --- | --- |
| Checkout | The shopper typed an email, which a guest does at checkout’s first step | 10 minutes |
| Cart | No email was typed | 30 minutes |

A cart can be emailed only when it carries a verified address, such as a signed-in shopper’s confirmed email. It is listed either way.

Baskets that went idle before you installed the module are shown, but the reminders never go to them — nobody wants a backlog mailed on day one.

A shopper who comes back and leaves again resumes the same record, and the next reminder waits for that visit to go quiet.

### The reminder sequence

Checkouts and carts each get a sequence: an on/off switch, the idle time, and up to five “wait, then send” steps. Each wait counts from the step before; the first from when the basket was marked abandoned.

| Step | Checkout (default) | Cart (default) | Email it sends |
| --- | --- | --- | --- |
| 1 | 1 hour | 2 hours | `cart.recovery.1` — “You left something in your basket” |
| 2 | 20 hours | 24 hours | `cart.recovery.2` — “Still thinking it over?” |
| 3 | 48 hours | — | `cart.recovery.3` — “Last chance for the items in your basket” |

The idle time can be 1 minute to 7 days, and each wait 1 minute to 30 days. Both sequences live on Marketing › Automations; the emails’ wording is edited on Notifications › Setup Email, with the store’s other emails.

Nothing is decided early. Before every send, the module checks the basket as it is now:

| Check | If it fails |
| --- | --- |
| The basket still exists | The record is marked expired |
| It has not become an order | Nothing more is sent |
| Reminders are not suppressed for it | Nothing is sent |
| The shopper has not been in it within the idle time | The step moves to when the visit counts as over |
| It has an email address | Held until the shopper comes back with one |
| At least one item can still be bought | Held until the shopper comes back to it |
| The automation is on | Held until it is switched on and saved |
| An email provider is set up | Held until one is, and the automation is saved |

A held reminder shows its reason. If the provider refuses a message, the module retries after 10 minutes, then 20. After three failed tries it stops with “sending kept failing”; saving the automation tries again.

### The link back

Where a reminder’s link goes depends on two addresses you set.

| You set | The link | Click counted? |
| --- | --- | --- |
| A storefront address and `GOCOMMERCE_PANEL_URL` | `<panel URL>/x/cart-recovery/r/<token>`, which counts the click, then `<storefront>/cart/<cart token>` | Yes |
| A storefront address only | Straight to `<storefront>/cart/<cart token>` | No |
| No storefront address | No link; the default email gives a basket reference instead | — |

If the basket was already bought or has gone, a counted link opens the storefront’s home page instead.

### How an order is credited

The engine’s `order.created` event now carries `cart_id`, the basket the order came from. The module marks that record **recovered**, with the order number and total, and notes whether any reminder — automatic or by hand — went out first. So the figures keep “recovered after a reminder” apart from “came back on their own”.

| Status | Meaning |
| --- | --- |
| Abandoned | Recorded; nothing scheduled, and the reason shown |
| Scheduled | A reminder is planned |
| Contacted | At least one reminder has gone out |
| Recovered | It became an order — final |
| Suppressed | Staff stopped the reminders, with a reason |
| Expired | The basket was deleted by the store’s retention clean-up |

### What staff can do

Orders › Abandoned checkouts lists every record. A record’s page shows the basket then and now — a price that moved, a line that sold out — the address’s past orders, and a timeline of what happened and who did it.

| Action | Right | Given by default to |
| --- | --- | --- |
| See the list, records and figures | `abandonment.read` | Owner, Manager, Staff |
| Send a reminder now, copy the link | `abandonment.contact` | Owner, Manager, Staff |
| Suppress, with a reason | `abandonment.suppress` | Owner, Manager, Staff |
| Change the automation | `abandonment.automate` | Owner, Manager |

**Send now** picks one of the three emails and does not move the sequence on. **Copy link** is for pasting into a chat; it goes on the timeline, because the link opens the basket for whoever holds it. **Suppress** asks why: contacted manually, asked for no more contact, invalid customer, fraud or test, or other, with a note.

If an action cannot happen — the basket was just bought, say — the screen says why.

### The figures

Six cards sit above the list and filter with it: abandoned, potential revenue, recovery emails sent, recovered orders, recovered revenue, and the recovery rate (recovered ÷ abandoned).

Marketing › Recovery analytics adds three views:

- **A funnel:** abandoned, recoverable, emailed, link clicked, recovered. The last stage counts only orders placed after a reminder.
- **Recovered revenue over time,** by day, week or month.
- **Carts against checkouts:** abandoned, recovered and the rate for each.

### How to turn it on

| What | How |
| --- | --- |
| The module | The `-recovery` flag — already on in both Docker Compose files |
| Where the basket lives | `STOREFRONT_URL`, or the automation screen, which wins |
| Counted clicks | `GOCOMMERCE_PANEL_URL`, where the store’s panel is reached (a multi-store platform uses each store’s domain) |
| An email provider | `-resend` or `-sendgrid` with its key; without one, reminders are held |

One store with recovery on

```
# Where the shopper's basket lives
export STOREFRONT_URL=https://shop.example.com
# The engine's public address, so clicks are counted
export GOCOMMERCE_PANEL_URL=https://admin.example.com
# An email provider; without one, nothing is sent
export RESEND_API_KEY=your-resend-api-key

./gocommerce -recovery -resend serve
```

Flags and variables from `cmd/gocommerce/main.go`. As for any store, the database comes from `DATABASE_URL` and the admin token from `GOCOMMERCE_ADMIN_TOKEN`.

For developers: the module adds no third-party dependency and sends through the store’s installed email notifier. Its admin routes are under `/api/admin/x/cart-recovery/`. The one engine change is `cart_id` on `order.created`, explained in decision D82 of `PLAN.md`.

### What it does not do yet

- **SMS or WhatsApp.** A basket carries no phone number, so these steps are listed but switched off.
- **Create a discount or notify staff** as a step. Listed, not built.
- **Chase a failed payment.** Once checkout runs, the basket is an order, and nothing reopens it.
- **Wait for payment before crediting.** A recovery counts when the order is created, so an order whose payment then fails still counts.
- **Count clicks without `GOCOMMERCE_PANEL_URL`.** The link still works; the click is not counted.

### Further reading

- [Checkout features](https://kitcommerce.store/features/checkout/) — abandoned checkouts beside the rest of checkout.
- [The admin panel](https://kitcommerce.store/gocommerce/admin/) — the screens these live in.
- [GoCommerce](https://kitcommerce.store/gocommerce/) — the engine and its modules.
- [The source on GitHub](https://github.com/itswadesh/gocommerce) — the module is `ext/cart-recovery`.
