The pitch and the price
Abandoned cart recovery is the most reliably profitable email a store sends. Someone got as far as the checkout with items in the cart and left; a reminder an hour later recovers a meaningful share of them. Every WooCommerce owner has heard the pitch, and most have installed a plugin that promises it.
The price is rarely mentioned. The popular recovery plugins add a tracking script to every page, write to the database on every cart change, run their own cron jobs, and ship an email builder, analytics dashboard and coupon engine you may not need. On a store already struggling with checkout speed, that is a real cost paid on every visit to recover a fraction of the visits that abandon.
You can have the recovery without most of the weight. Here is how it actually works, and the lean version.
How recovery works, mechanically
Three things have to happen:
- Capture the email early. You cannot email a cart you have no address for. The email must be captured when the visitor types it into the checkout, before they submit.
- Detect abandonment. A cart is abandoned when it has an email, has items, and has not become an order after some interval. Usually an hour.
- Send the reminder. An email with the cart contents and a link that restores the cart, sent once or twice, and never sent after the order completes.
That is the whole feature. Everything else in a recovery plugin is decoration around those three steps.
Step 1: capturing the email
In the classic shortcode checkout, plugins hooked the billing email field with JavaScript and posted it to the server on blur. In the checkout block, the fields are React components, and the block exposes a data store you can read instead of scraping the DOM.
A small script on the checkout page can subscribe to the checkout store and post the email to a REST endpoint when it becomes valid:
const { select, subscribe } = window.wp.data
let last = ''
subscribe(() => {
const email = select('wc/store/checkout')?.getCustomerEmail?.() ??
select('wc/store/cart')?.getCustomerData?.()?.billingAddress?.email
if (email && email !== last && /\S+@\S+\.\S+/.test(email)) {
last = email
fetch('/wp-json/store-recovery/v1/capture', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-WC-Store-API-Nonce': window.wcStoreApiNonce ?? '' },
body: JSON.stringify({ email, cart_token: select('wc/store/cart')?.getCartData?.()?.token ?? null }),
})
}
})On the server, the endpoint stores the email against the cart token with a timestamp. A custom table with three columns is enough. This is the only script the lean approach adds, and it loads only on the checkout page.
Tell people you are doing it. A line under the email field, "We'll save your cart in case you need to come back", is honest and is required for consent under most privacy regimes.
Step 2: detecting abandonment
A scheduled task, on the real system cron rather than WP-Cron, runs every fifteen minutes and finds captured carts older than your interval with no matching order. Action Scheduler, which WooCommerce ships, is the right tool: it is what WooCommerce itself uses for delayed work.
add_action( 'init', function () {
if ( ! as_next_scheduled_action( 'store_recovery_scan' ) ) {
as_schedule_recurring_action( time(), 15 * MINUTE_IN_SECONDS, 'store_recovery_scan' );
}
} );
add_action( 'store_recovery_scan', function () {
$rows = store_recovery_pending_carts( HOUR_IN_SECONDS );
foreach ( $rows as $row ) {
if ( store_recovery_order_exists_for( $row->email, $row->captured_at ) ) {
store_recovery_mark( $row->id, 'converted' );
continue;
}
store_recovery_send( $row );
store_recovery_mark( $row->id, 'sent' );
}
} );The order check matters. Sending a recovery email to someone who completed the purchase is the fastest way to look broken. Check wc_get_orders() by billing email and creation time, and check it again immediately before sending.
Step 3: the email and the restore link
Send through a transactional email service, Postmark, Resend, Brevo or SES, not through the web server. Your domain needs SPF, DKIM and DMARC, or the reminder goes to spam and the whole exercise is wasted.
The email is plain: the items, the total, one button. The button links to a restore URL that carries the cart token; on arrival, the server rebuilds the cart from the stored items and redirects to the checkout. One reminder at an hour, optionally a second at twenty-four with a small incentive if your margins allow, and nothing after that. Every recovery email includes an unsubscribe link.
What you give up versus a full plugin
Honest list: no visual email builder, no A/B testing, no dashboard of recovered revenue unless you build a simple report, no SMS channel, no exit-intent popups. If you want those, a plugin is the right call, and you should measure its cost on checkout INP before and after.
What you keep: the recovery itself, a single small script on one page, no database writes on every cart change, no third-party tracking, and full control over the wording and timing.
Measuring it
Tag the restore link so the completed orders can be attributed, and compare recovered orders against sent emails monthly. Store owners are often surprised that the second email recovers almost nothing and the incentive costs more than it earns. The data decides.
If your store has a recovery plugin and a slow checkout, the plugin is worth profiling. If it has neither, this is a small fixed build, and it is the kind of WooCommerce work that pays for itself in the first month.