Recovering abandoned carts with n8n and AI (Shopify, WooCommerce)
Published 26 July 2026 · 5 min read
A customer adds an item to their cart, makes it to the checkout page, then disappears. Left alone, that cart is dead data: nobody follows up, the sale never happens, and nothing distinguishes that customer from a casual browser. Yet it's one of the richest commercial signals an online store gets — purchase intent already expressed, one step short of conversion. A landmark study by Kukar-Kinney and Close, published in 2010 in the Journal of the Academy of Marketing Science, identified the concrete drivers behind this abandonment (price comparison across stores, browsing for the best deal, plain hesitation before buying) and showed it's rarely a rejection of the product itself (study on Google Scholar) — one more reason to follow up rather than write the sale off. n8n lets you build this safety net without depending on a volume-priced third-party SaaS.
Why this isn't the same as order confirmation
Our guide to e-commerce order automation covers what happens after an order is confirmed: stock checks, ERP writes, customer notification. Abandoned cart recovery sits upstream, on an order that was never completed. The trigger logic is therefore completely different: instead of reacting to an orders/create event, you need to detect an absence of an event — a cart that exists but never converted within the expected window.
Step 1 — Detect the abandoned cart
On Shopify: a real dedicated webhook
Shopify exposes an Abandoned Checkouts resource in its Admin API, fed by the checkouts/create and checkouts/update events: the moment a customer enters their email at checkout without completing the purchase, that checkout shows up in this list with the cart contents, address, and, crucially, an abandoned_checkout_url field that points straight back to resuming the purchase. Configure a webhook on checkouts/update (Settings > Notifications > Webhooks, or via the Admin API) pointing to an n8n Webhook node: the exact counterpart of the orders/create used for confirmed orders, but on the unfinished-payment side.
On WooCommerce: polling in the absence of a native event
WooCommerce has no native webhook event for cart abandonment — a customer can close the tab without ever touching the server. The robust, plugin-free approach is to periodically query the WooCommerce REST API (GET /wp-json/wc/v3/orders?status=pending) via a Schedule Trigger every 15 to 30 minutes, and filter orders at pending or on-hold status whose creation date exceeds your chosen follow-up delay. A WooCommerce order in "pending" status means a customer filled out the checkout form without completing payment — a reliable abandonment proxy, even though it misses carts that never reached that step.
Step 2 — Log it before waiting
As soon as an abandoned checkout or pending order is detected, insert it into a Supabase table (abandoned_carts: checkout_id, email, items, amount, followup_status, detected_at) before even scheduling the first follow-up. This step serves two purposes: keeping a usable trail for monthly reporting, and — critically — avoiding duplicates if WooCommerce polling redetects the same "pending" order on the next cycle. Same idempotence principle covered in our guide to n8n webhooks and duplicates, applied here to a cart identifier instead of an event identifier.
Step 3 — Wait, then re-check before sending
This is where most hand-rolled implementations get it wrong: scheduling a delayed send without re-checking status at send time. A customer who completes payment 20 minutes after abandoning must never receive the follow-up email scheduled for minute 30 — nothing annoys a customer more than a recovery email for a purchase they already made.
The correct sequence in n8n:
- Wait — delay until the first follow-up (30 to 60 minutes, depending on the product).
- HTTP Request — re-check the actual checkout status (Shopify Abandoned Checkouts API) or order status (
GET /orders/{id}on WooCommerce). - IF — if the status has moved to "paid" or "completed", the workflow stops right here without sending an email; otherwise it continues to message generation.
That same Wait → re-check → IF triplet repeats at every step of a multi-touch sequence (one hour, 24 hours, 48-72 hours), each iteration assuming conversion may have happened in between.
Step 4 — Generate a personalized follow-up with AI instead of a fixed template
A generic email ("You left something in your cart!") converts, but not as well as a message that echoes the cart's actual contents and adjusts tone to its place in the sequence. A Basic LLM Chain connected to Claude or GPT receives the items, total amount, and follow-up number (1st, 2nd, or 3rd), and generates a short, tailored message:
- Follow-up 1 (30-60 min): simple factual reminder, neutral tone, direct link back to the cart.
- Follow-up 2 (24h): addresses a common objection specific to your store (shipping cost, return policy, payment security) rather than restating the first message.
- Follow-up 3 (48-72h): more direct tone, last call before the cart is written off as lost in your reporting.
As with order confirmation emails, never let the model invent the price, quantity, or product reference: that data should be injected via n8n expressions ({{ $json.amount }}, {{ $json.items }}) into both the prompt and the final template, with the AI limited to tone and phrasing. The same guardrail is described in detail in our e-commerce order automation guide.
Step 5 — Send, log, and stop the sequence on conversion
A Gmail or Email Send node closes out the step, followed by updating the Supabase row (followup_status: "sent_1", timestamp). If a later touch in the sequence detects a conversion (via the IF node from step 3), the table must be updated accordingly (followup_status: "converted") so that remaining touches — still scheduled via parallel Wait nodes — stop dead at their own re-check, never reaching the send node.
Common pitfalls
- Trusting the status seen at detection time: without a re-check before every send, the sequence ends up following up with customers who already converted — the most visible and most costly-to-brand mistake.
- Confusing a WooCommerce cart with a "pending" order: polling pending orders only catches customers who made it to the checkout form, not those who left earlier in the funnel; that's a known limitation of the plugin-free approach, worth acknowledging rather than ignoring.
- Too many follow-ups, too fast: beyond three messages over 72 hours, the marginal conversion gain no longer offsets the risk of unsubscribes or spam complaints.
- Forgetting marketing consent: unlike an order confirmation email (transactional), an abandoned cart follow-up is generally a marketing communication — check that the customer's email was collected under an appropriate legal basis before automating the send.
Going further
This architecture — webhook or polling detection, deduplication, a follow-up sequence with systematic re-checks, AI-generated messaging — reuses the same building blocks already covered in our e-commerce order automation guide and in the one on Stripe failed-payment follow-ups, which follows the same detect-an-incomplete-state-then-log-a-tracked-follow-up principle. For the emails themselves, the Inbox AI Pack (€79) already ships the same pattern for generating text constrained by structured data — directly transposable to a cart recovery sequence.
FAQ
Frequently asked questions
Do I need a paid plugin to detect abandoned carts on WooCommerce?
Not necessarily. WooCommerce doesn't fire any native event when a customer leaves the checkout, unlike Shopify. The plugin-free approach is to periodically poll the WooCommerce REST API for orders stuck at "pending" or "on-hold" status past a chosen delay: that's an order created but never paid for — a solid proxy for an abandoned cart. A dedicated plugin (WooCommerce Abandoned Cart Recovery) additionally captures carts that never reached the order step, but polling is enough to get started.
How long should I wait before sending the first follow-up?
A short delay, typically 30 minutes to an hour, gets the best results: purchase intent is still fresh and the customer remembers exactly what they were looking at. After that first message, a second email 24 hours later addressing a common objection (shipping cost, return policy, payment security) and a third at 48-72 hours with a more direct nudge round out a reasonable sequence, without tipping into harassment.
How do I make sure I don't follow up with a customer who already paid?
Before each send in the sequence, the workflow must re-check the order or checkout status against the platform rather than trusting the status seen at initial detection. An IF node comparing the current status to "paid" or "completed" right before the email-send step is enough to cut the sequence the moment conversion happens in the meantime — essential when the first email already waits an hour before going out.
Bundle FlowKit Complet
€269