WooCommerce abandoned-cart recovery
SkillCommunicationIntegrate with or audit WooCommerce 11.0's experimental abandoned-cart recovery email. Covers feature and email gates, manual versus two-hour automatic sends, eligible order statuses, duplicate-provider suppression, recovery URL filtering, unsubscribe/privacy behavior, Action Scheduler boundaries, HPOS-safe order access, and safe tests. Use when an extension already sends cart recovery mail, needs to suppress Woo duplicates, changes recovery eligibility/URLs, observes recovery sends, or audits pending and checkout-draft orders.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the WooCommerce abandoned-cart recovery skill
What this skill tells your AI
The instructions your AI receives, as published by lonsdale201/wp-agent-skills in woocommerce/wc-abandoned-cart-recovery/SKILL.md and read by ahel’s review.
WooCommerce 11.0 adds an experimental abandoned_cart_recovery feature. It is disabled by default. When enabled, it registers a customer email, an order-edit manual action, an optional Action Scheduler send, and an unsubscribe endpoint.
This is an email around an existing order, not a generic serialized cart-recovery store. Work through WC_Order and public hooks.
Activation layers
Three independent gates matter:
FeaturesUtil::feature_is_enabled( 'abandoned_cart_recovery' )must be true.- The
customer_abandoned_cart_recoveryemail must be enabled. - Its
automatedsetting must beyesfor automatic scheduling; manual send can remain available when automation is off.
Do not enable the experimental feature or merchant email settings silently from a distributed extension. If your plugin already owns recovery messages, suppress Woo's flow to prevent duplicates.
Suppress duplicate recovery providers
WooCommerce detects selected known providers for its settings default, but partner integrations should use the public runtime filter:
add_filter( 'woocommerce_abandoned_cart_recovery_suppress', '__return_true' );
Scope the return value to your own recovery feature's enabled state if it can be turned off. Suppression is checked for scheduling, manual availability, and sending. Do not cancel Woo actions or edit its private order meta directly.
Default lifecycle
Automatic behavior in WooCommerce 11.0:
- listens when a new checkout-created order already has an eligible status;
- defaults scheduling eligibility to
pending; - schedules one send two hours after order creation;
- requires the email and its automation setting to be enabled and not suppressed;
- cancels a pending send when the order leaves the eligible statuses or is trashed/deleted;
- re-checks recipient, status, one-hour minimum age, unsubscribe preference, prior send, and email settings at execution time;
- records the send only after the mail transport reports success.
Manual order actions allow both pending and checkout-draft by default after the one-hour threshold. Store API orders are usually born as checkout-draft, so automatic block-checkout recovery is not implied merely because created_via can be store-api.
Action Scheduler due time is not a delivery SLA. Queue delay, mail transport failure, state changes, and suppression can prevent or postpone a send.
Eligible status filter
add_filter(
'woocommerce_abandoned_cart_recovery_eligible_statuses',
static function ( array $statuses, ?WC_Order $order ): array {
// Preserve the caller's defaults unless a documented business rule
// explicitly makes another unpaid status recoverable.
return array_values( array_unique( array_map( 'sanitize_key', $statuses ) ) );
},
10,
2
);
The filter runs in different contexts with different defaults: scheduling starts with pending; send/manual eligibility starts with pending and checkout-draft. Do not replace the array blindly. Adding a paid, canceled, failed, refunded, renewal, admin-created, or privacy-sensitive status can email the wrong customer or leave cancellation semantics surprising.
Use status slugs without the wc- prefix. Keep the callback deterministic and side-effect free.
Recovery URL
Core defaults to the order's checkout-payment URL:
add_filter(
'woocommerce_abandoned_cart_recovery_url',
static function ( string $url, WC_Order $order ): string {
return $url;
},
10,
2
);
Treat the URL as a bearer-like customer link because it contains order payment context/key material. Never log it, put it in analytics parameters, cache it publicly, or send it to another customer. A replacement URL must be HTTPS, scoped to that order/recipient, tamper-evident, and time/state constrained; a public cart URL with only an order ID is not equivalent.
Observe, do not take over internal state
The scheduled action hook is woocommerce_send_abandoned_cart_recovery_notification and receives the order ID. Core owns its handler. Observers must not send a second message from the same hook.
Manual sends reuse:
woocommerce_before_resend_order_emails( WC_Order $order, string $email_type )
woocommerce_after_resend_order_email( WC_Order $order, string $email_type )
Check $email_type === 'customer_abandoned_cart_recovery' before handling. These manual resend hooks are not a reliable event for every automatic attempt.
Do not depend on _abandoned_cart_recovery_scheduled_at, _abandoned_cart_recovery_email_sent_at, wc_email_unsubscribes, or classes under Automattic\WooCommerce\Internal. They are implementation details. Keep your provider's idempotency, send state, consent, and suppression decision in your own model.
Unsubscribe and privacy boundary
Core recovery templates include an HMAC-signed unsubscribe URL. The address is normalized and SHA-256 hashed before entering the URL/table; the email kind is signature-bound. Invalid and successful endpoints both return HTTP 200 to reduce existence disclosure. Core also registers a personal-data eraser.
- Preserve the unsubscribe link in template overrides.
- Never remove or bypass an unsubscribe during manual send.
- Do not copy core's salt/signature or private table as your plugin's consent system.
- If your extension sends independently, own a compliant preference/retention model and honor it before enqueue and again before send.
Safe test matrix
Use a mail-capture transport and disposable orders. Never send to real customers.
- Feature off, email off, automation off, and suppression on each produce no automatic send.
- Eligible classic pending order schedules once; duplicate new-order observation does not duplicate it.
- Moving out of the eligible set, trashing, or deleting cancels the pending action.
- Orders too young, lacking a valid billing email, already sent, or unsubscribed do not send.
- Manual pending and checkout-draft behavior respects capability, age, suppression, and unsubscribe gates.
- Recovery URLs never leak to logs and cannot be swapped between orders/users.
- HPOS and legacy compatibility modes use
WC_OrderCRUD only. - Queue replay cannot produce duplicate provider mail in an extension-owned flow.
Cross-references
wc-emails-classicfor email class/templates and send observability.wc-action-scheduler-jobsfor delivery and idempotency semantics.wc-order-lifecycle-and-itemsfor status behavior.wc-hpos-compatibilityfor order storage.
References
includes/emails/class-wc-email-customer-abandoned-cart-recovery.phpsrc/Internal/AbandonedCartRecovery/Scheduler.php(implementation evidence only)src/Internal/AbandonedCartRecovery/ManualSendHandler.php(implementation evidence only)src/Internal/Email/Unsubscribes/*(implementation evidence only)src/Internal/Features/FeaturesController.php
Signals
- GitHub stars
- 22
- Forks
- 2
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
wc-abandoned-cart-recovery- Source
- github.com/lonsdale201/wp-agent-skills