GitHub

The carts you already lost, recovered on a timer.

Abandoned checkouts are saved as records, and a durable workflow follows up three days later with a code that can only be used once.

PostgreSQLRedisS3StripeResend
admin.yourstore.com/orders
Abandoned checkouts312 idle
Maya Okonkwo · 3 items
$182.40
Leo Martins · 1 item
$96.00
Priya Shah · 5 items
$248.10
Dana Feld · 2 items
$74.00

This money is not new business.

It is business you already did, sitting in a table nobody queries. Every figure below comes from orders you already have, not a channel you have to go and buy.

A cart becomes a record before payment.

That is the whole trick. Once the intent is a row, following it up is a workflow rather than a report somebody has to remember to read.

cart.abandonedstep.sleep('3d')step.run(send)

Cart recovery

Running
312carts idle

$9.4k at stake · oldest 4d

SUMMER10
18%Recovered
$59AOV
$6.2kRevenue

Waits three days, then emails them the code once.

Automate the follow-up

The email leaves three days later and only once, even if you deploy in between.

104 orders that had already left.

Recovered without a second tool, a connector, or an export.

Recovered ordersLast 8 days
104Now
SunMonTueWedThuFriSatNow

Reporting reads the same rows that fulfilled the orders. Nothing is sampled.

A lapsed customer is a query that re-runs itself.

First-time, repeat, VIP and lapsed are derived from order history, so a segment is never stale and nobody has to remember to apply one.

0%
Return ratelapsed 120 days
0%
Recoveredof idle carts

The restock releases the email.

The same stock movement that updates the shelf count triggers the notification. Nothing polls, and nothing runs on a schedule that can drift.

New season
Sunset Linen Shirt4.8

4 colours · in stock

$128
Everyday Merino Crew4.6

6 colourways · machine washable

$96

Three sources, one total.

Links, codes and collabs resolve against the same orders, so the slices add up instead of triple-counting a single sale.

Abandoned44%Win-back38%Back in stock18%$21,530

A cron job at 2am is a guess.

A durable wait is a promise. If the process restarts mid-wait the send still happens once, because the step is the unit of retry rather than the whole workflow.

What happens over three days

  1. 1Buyer leaves with items in the cartOrderIntent
  2. 2The workflow starts and waitsstep.sleep('3d')
  3. 3One email goes out with a single-use codestep.run(send)
  4. 4Redemption is recorded against the codeCouponUsage

One use, one customer.

Every redemption is a row, so the limit is checked against real usage instead of a cached counter that drifts.

10%
SUMMER10

Cart recovery · 3-day wait

one use per customer
15%
COMEBACK15

Win-back · lapsed 120 days

one use per customer
5%
RESTOCK5

Back in stock · notified

one use per customer
$18
FRIEND18

Referral · both sides

one use per customer

Another dashboard to remember to open?

No. An idle cart is one of twenty-nine task kinds, so recovery lands in the same queue as orders to ship — and clears itself when the order comes in.

Arriving now

Live
Order #4192 needs shipping

$182.40 · paid 2 hours ago

40+Domain events

Every one bindable to a workflow

1Send per buyer

Exactly-once, across restarts

0Extra tools

It runs inside the store

MITLicence

All of it, in the repository

Support that already knows what they bought.

Every conversation opens with the customer's orders and history attached.

Inbox

5 open
MO

Maya Okonkwo

#1042 · 12m

Where is my order?

PS

Priya Shah

#1040 · 1h

Can I change the size?

DF

Dana Feld

Resolved · 2h

Thanks — it arrived!

Sorted by last activity — written in the same transaction.

Maya Okonkwo

Assigned · SamOpen

Hi — has my order shipped yet?

order · #1042

Paid

Sunset Linen Shirt +2 · $182.40

Not shipped · express

Internal note · staff only

Merino restock lands Thursday — offer the swap.

Assistant · draft reply

It ships today. Here's 10% off your next order for the wait.

coupon · SORRY10

Parts: product · order · coupon · suggestion · quote

Send
AssignmentOpen · resolved · archivedInternal notesCustomer dashboard
How conversations work
cart
3d
send

Event, wait, send

The wait survives a restart, and the email is still sent once.

SUMMER10

One use, one customer

Every redemption is recorded, so per-customer limits are checked against real usage.

One order, one timeline

Payment, fulfilment, refund and support history sit on the same record.

Start with the carts you already lost.

Clone it, point it at a database, and the recovery workflow is already there.

MIT licensedSelf-hostedNo revenue share