
Cart Abandonment Recovery
- 83 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Win back abandoned carts with timed email, SMS, and push sequences that escalate from reminders to social proof to a last-resort discount.
About
A skill for building multi-channel cart abandonment recovery flows across email, SMS, and push with escalating incentives. A developer uses it to recover lost checkout revenue without training customers to abandon on purpose.
- 2-4 message sequences across email, SMS, and push
- Discount only as last resort; cancels on checkout completion
Cart Abandonment Recovery by the numbers
- 83 all-time installs (skills.sh)
- Ranked #1,173 of 1,879 Marketing & SEO skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill cart-abandonment-recoveryAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 83 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Win back abandoned carts with timed email, SMS, and push sequences that escalate from reminders to social proof to a last-resort discount.
Files
Cart Abandonment Recovery
Overview
Cart abandonment averages 70% across e-commerce, making recovery flows one of the highest-ROI automations available. A good recovery sequence sends 2–4 messages across email, SMS, and push — starting with a simple reminder, then adding social proof or urgency, and only offering a discount as a last resort. The sequence cancels immediately when the customer completes checkout.
This skill guides you through setting up cart abandonment recovery on your specific platform, choosing the right tools, and configuring the sequence for maximum revenue recovery without training customers to abandon on purpose.
When to Use This Skill
- When more than 60% of initiated checkouts are not completed
- When launching a new store and prioritizing quick revenue recovery
- When adding SMS or push channels to an existing email-only abandonment flow
- When the current flat-discount abandonment email is training customers to abandon intentionally
- When needing to differentiate recovery strategy by cart value or customer segment
- When A/B testing incentive timing (immediate vs. 24h vs. 48h discount reveal)
Core Instructions
Step 1: Determine the merchant's platform and recommend the right tool
Ask which platform the store runs on. The setup is completely different depending on the platform:
| Platform | Recommended Tool | Why |
|---|---|---|
| Shopify | Klaviyo (free up to 250 contacts) or Shopify's built-in abandoned checkout emails | Klaviyo has deep Shopify integration, tracks cart events automatically, and supports email + SMS in one tool |
| WooCommerce | AutomateWoo ($99/yr) or CartFlows + FluentCRM | AutomateWoo hooks directly into WooCommerce cart events; CartFlows adds funnel recovery |
| BigCommerce | Klaviyo or Omnisend | Both integrate natively with BigCommerce cart events |
| Magento | Dotdigital (bundled with Adobe Commerce) or Klaviyo | Dotdigital is the default marketing automation for Magento; Klaviyo works for open-source Magento |
| Custom / Headless | Klaviyo (via API) or build a custom sequence with BullMQ + SendGrid | Use Klaviyo's Track API to send cart events, then build flows in their visual editor |
Step 2: Set up the recovery sequence
The optimal sequence follows this pattern — start without a discount and escalate only if needed:
Message 1 — Reminder (1 hour after abandonment)
- Channel: Email
- Content: Show the cart items with images + prices, a "Complete your purchase" button
- No discount — most recoveries happen here without incentive
Message 2 — Social proof or urgency (4–6 hours)
- Channel: Push notification or email
- Content: "X people are viewing this item" or "Low stock — only Y left"
- Still no discount
Message 3 — Small incentive (24 hours)
- Channel: Email
- Content: Free shipping (if cart > $75) or 10% off
- Include a unique, single-use discount code that expires in 48 hours
Message 4 — Final push (48 hours)
- Channel: SMS (only if opted in)
- Content: "Last chance — your cart expires tomorrow" + discount reminder
- This is the last message. Stop after this.
Never send more than 4 messages. Beyond that, you damage brand perception more than you recover revenue.
Step 3: Platform-specific setup
---
Shopify
Option A: Built-in abandoned checkout emails (free, basic)
1. Go to Settings → Checkout → Abandoned checkouts 2. Check "Automatically send abandoned checkout emails" 3. Set the delay (recommend: 1 hour for first email, 10 hours for second) 4. Customize the email template under Notifications → Abandoned checkout
Limitations: Shopify's built-in recovery only supports one email (or two on Plus), no SMS, no conditional incentives. Move to Klaviyo when you need a full sequence.
Option B: Klaviyo (recommended for multi-step flows)
1. Install Klaviyo from the Shopify App Store 2. Klaviyo automatically syncs with Shopify and tracks Checkout Started events 3. In Klaviyo, go to Flows → Create Flow → Abandoned Cart (use their pre-built template) 4. Configure the sequence:
Trigger: "Checkout Started" event
↓
Wait 1 hour → Check: "Has Placed Order since starting flow?" → If no:
↓
Email 1: Cart reminder (no discount)
↓
Wait 4 hours → Check: "Has Placed Order?" → If no:
↓
Email 2: Urgency/social proof
↓
Wait 20 hours → Check: "Has Placed Order?" → If no:
↓
Email 3: Include discount code (use Klaviyo's coupon block — creates unique single-use Shopify discount codes)
↓
Wait 24 hours → Check: "Has Placed Order?" → If no:
↓
SMS 4 (only to SMS-consented contacts): Final reminder with discount5. To segment by cart value: add a Conditional Split on $value of the checkout event:
- Cart ≥ $100: offer free shipping
- Cart < $100: offer 10% off
6. To protect VIP customers from discounts: add a Split on Customer Lifetime Value or Total Orders — skip the discount step for repeat buyers
Shopify Flow (Plus only) — auto-tag for advanced routing:
Trigger: Checkout abandoned
Condition: Cart total > 200
Action: Add customer tag "high-value-abandoner"Then use this tag in Klaviyo to route high-value abandoners to a VIP recovery sequence.
---
WooCommerce
Option A: AutomateWoo (recommended)
1. Install and activate the AutomateWoo plugin 2. Go to AutomateWoo → Workflows → Add Workflow 3. Set the trigger to "Cart Abandoned" 4. Configure timing and actions:
Workflow 1: Cart Reminder
Trigger: Cart Abandoned
Timing Rule: Wait 1 hour
Action: Send Email (template: cart contents with images)
Workflow 2: Urgency Email
Trigger: Cart Abandoned
Timing Rule: Wait 6 hours
Action: Send Email (template: "Items selling fast")
Workflow 3: Discount Email
Trigger: Cart Abandoned
Timing Rule: Wait 24 hours
Action: Generate Coupon (10% off, single-use, expires in 48 hours)
Action: Send Email (template: include generated coupon code)
Workflow 4: SMS Last Chance (requires Twilio add-on)
Trigger: Cart Abandoned
Timing Rule: Wait 48 hours
Opt-in Rule: Customer has SMS marketing consent
Action: Send SMS via Twilio5. Set a Rule on each workflow: "Customer has not purchased since workflow started" — this auto-cancels the sequence on conversion 6. For VIP segmentation: add a Rule checking Customer → Total Spent > $500 to skip the discount workflow
Option B: CartFlows + FluentCRM (free alternative)
1. Install CartFlows (cart abandonment tracking) and FluentCRM (email automation) 2. CartFlows captures abandoned checkouts and syncs to FluentCRM as contacts 3. Build the email sequence in FluentCRM's automation builder
---
BigCommerce
1. Install Klaviyo or Omnisend from the BigCommerce app marketplace 2. Both platforms automatically track BigCommerce abandoned cart events 3. Build the flow using the same 4-step sequence described above 4. BigCommerce also has a built-in abandoned cart saver (Marketing → Abandoned Cart Emails) but it's limited to 3 emails with no SMS or conditional logic
---
Custom / Headless
For headless storefronts, you have two paths:
Path A: Use Klaviyo's API (recommended)
Send cart events to Klaviyo, then build the flow in Klaviyo's visual editor:
// When a cart is updated, send the event to Klaviyo
async function trackCartUpdate(cart: Cart) {
await fetch('https://a.]klaviyo.com/api/events/', {
method: 'POST',
headers: {
'Authorization': `Klaviyo-API-Key ${process.env.KLAVIYO_PRIVATE_KEY}`,
'Content-Type': 'application/json',
'revision': '2024-10-15',
},
body: JSON.stringify({
data: {
type: 'event',
attributes: {
metric: { data: { type: 'metric', attributes: { name: 'Checkout Started' } } },
profile: { data: { type: 'profile', attributes: { email: cart.customerEmail } } },
properties: {
value: cart.totalValue,
items: cart.items.map(i => ({
ProductName: i.name,
ProductURL: i.url,
ImageURL: i.imageUrl,
Price: i.price,
Quantity: i.quantity,
})),
checkout_url: `${process.env.STORE_URL}/checkout?cart=${cart.id}`,
},
},
},
}),
});
}Then build the abandoned cart flow in Klaviyo's UI — same 4-step sequence, no need to manage timers or queues yourself.
Path B: Build it yourself (only if you need full control)
If you need custom logic that Klaviyo can't handle (custom discount rules, proprietary channels, etc.), build the sequence with a job queue:
// Detect abandoned carts — run every 5 minutes via cron
async function findAbandonedCarts() {
const cutoff = new Date(Date.now() - 60 * 60000); // 1 hour inactive
const carts = await db.carts.findWhere({
lastActiveAt: { lt: cutoff },
status: 'active',
recoveryTriggered: false,
customerEmail: { not: null },
});
for (const cart of carts) {
await db.carts.update(cart.id, { recoveryTriggered: true });
await recoveryQueue.add('send-step-1', { cartId: cart.id }, { delay: 0 });
await recoveryQueue.add('send-step-2', { cartId: cart.id }, { delay: 4 * 3600000 });
await recoveryQueue.add('send-step-3', { cartId: cart.id }, { delay: 24 * 3600000 });
await recoveryQueue.add('send-step-4', { cartId: cart.id }, { delay: 48 * 3600000 });
}
}
// Cancel on conversion
async function onOrderCompleted(orderId: string) {
const order = await db.orders.findById(orderId);
if (!order.cartId) return;
const jobs = await recoveryQueue.getJobs(['delayed']);
for (const job of jobs) {
if (job.data.cartId === order.cartId) await job.remove();
}
}Step 4: Configure incentive strategy
Regardless of platform, follow these rules for discounts:
1. Never offer a discount on the first message — most carts recover without one 2. Use single-use codes — prevents sharing and reuse 3. Set a 48-hour expiry on all discount codes — urgency converts 4. Segment by customer value:
- New customers (0 orders): 10% off after 24 hours
- Returning customers (1–5 orders): free shipping only
- VIP customers (6+ orders or $500+ lifetime): no discount, just a reminder — they'll come back
5. Track discount abuse — if a customer has abandoned and recovered with a discount 3+ times, remove them from discount flows
Step 5: Measure and optimize
Track these metrics weekly:
| Metric | Good Benchmark | How to Find It |
|---|---|---|
| Recovery rate | 5–15% of abandoned carts | Klaviyo: Flow analytics. AutomateWoo: Reports → Workflows |
| Revenue recovered | Track with UTM params (?utm_source=recovery&utm_medium=email) | Google Analytics → Campaigns |
| Unsubscribe rate from recovery emails | < 0.5% per send | Your email provider's analytics |
| Discount usage rate | < 30% of recoveries should use a discount | Compare discount vs. non-discount recovery orders |
If more than 30% of recovered orders use a discount, you're revealing the discount too early. Push it to step 3 or 4, or increase the delay.
Best Practices
- Capture email before payment — place the email field as the first checkout step so you can identify abandoners even if they don't finish
- Include cart item images in every message — a visual reminder outperforms "you forgot something" text
- Respect SMS opt-in — only send SMS recovery to customers who explicitly opted in; violations of TCPA/GDPR carry heavy fines
- Suppress for 7 days after a prior sequence — if a customer abandoned again right after a recovery sequence, give them space
- A/B test the first email's delay — try 30 min vs. 1 hour vs. 2 hours and measure recovery rate for each
- Don't include discount codes in subject lines — it trains customers to look for them and damages brand perception
Common Pitfalls
| Problem | Solution |
|---|---|
| Recovery email sent after order placed | Ensure your flow checks "Has Placed Order" before each step (Klaviyo does this with flow filters; AutomateWoo uses rules) |
| Anonymous cart abandonment not captured | Require email at the first checkout step, before payment details |
| Customers learn to abandon for discounts | Never offer discount on message 1; skip discounts for repeat customers; track and exclude serial abandoners |
| SMS sends to customers who didn't opt in | Gate SMS behind explicit opt-in; use your platform's consent tracking (Klaviyo tracks SMS consent separately from email) |
| High unsubscribe rate from recovery emails | Reduce to 2–3 messages instead of 4; ensure one-click unsubscribe link is prominent |
| Recovery emails land in spam | Use your platform's authenticated sending domain (DKIM/SPF); avoid spam trigger words like "FREE" in caps |
Related Skills
- @email-marketing-automation
- @push-notifications
- @sms-marketing
- @conversion-rate-optimization
- @exit-intent-popups
{
"context": "Tests whether the agent correctly designs the multi-channel recovery sequence with the right channel order, timing, and cart-value-based incentive escalation, avoids early discounts, applies segment-based logic for VIP customers, handles SMS opt-in, and keeps the sequence to the right number of touchpoints.",
"type": "weighted_checklist",
"checklist": [
{
"name": "4-step max sequence",
"max_score": 8,
"description": "Recovery sequence contains exactly 4 steps (or fewer) — does NOT exceed 4 touchpoints"
},
{
"name": "Channel order",
"max_score": 10,
"description": "Sequence follows the order: email first, then push, then email, then SMS (email → push → email → sms)"
},
{
"name": "Step 1 timing",
"max_score": 7,
"description": "First step (email) is delayed by 1 hour (60 minutes / 3600000ms) from abandonment"
},
{
"name": "Step 2 timing",
"max_score": 7,
"description": "Second step (push) is delayed by 4 hours (14400000ms) from abandonment"
},
{
"name": "Step 3/4 timing",
"max_score": 7,
"description": "Third step is at 24 hours and fourth step is at 48 hours from abandonment"
},
{
"name": "No discount on steps 1-2",
"max_score": 10,
"description": "First two steps (email and push) have null incentive — no discount or free shipping offered"
},
{
"name": "High-value step 3 incentive",
"max_score": 8,
"description": "For carts ≥ $100, step 3 offers free_shipping (not a % discount)"
},
{
"name": "Low-value step 3 incentive",
"max_score": 7,
"description": "For carts < $100, step 3 offers percent_off with value 10"
},
{
"name": "High-value step 4 escalation",
"max_score": 7,
"description": "For carts ≥ $100, step 4 escalates to percent_off with value 15"
},
{
"name": "VIP free shipping only",
"max_score": 8,
"description": "VIP customers (≥ $500 lifetime spend) receive free_shipping incentive and never a percent_off discount"
},
{
"name": "First-time abandoner no incentive",
"max_score": 8,
"description": "Customers with zero prior recovery sequences receive null incentive (no offer withheld)"
},
{
"name": "SMS opt-in gate",
"max_score": 8,
"description": "SMS step is gated behind opt-in check — either via `smsMarketingOptIn` field or equivalent guard that defaults to NOT sending"
},
{
"name": "One-time discount codes",
"max_score": 5,
"description": "STRATEGY.md or code documents/implements that discount codes are one-time use (not reusable/shareable)"
}
]
}
Abandoned Cart Recovery Messaging Sequence
Problem/Feature Description
A mid-size home decor e-commerce company is launching its first automated cart recovery program. Their head of revenue noted that flat-discount approaches at competitors have conditioned shoppers to abandon intentionally to wait for a coupon. The company wants a smarter multi-step approach that contacts customers across multiple channels over two days, applies incentives strategically based on what the data says about cart value, and respects customer preferences — particularly around SMS.
The engineering team needs to implement the recovery sequence logic in TypeScript. The sequence must cover multiple channels in a specific order, apply incentives at the right moments (but not too early), and differentiate the offer based on what the customer has in their cart. The system should also handle segment edge cases: VIP customers who spend heavily should never feel like they need to bargain, while first-time abandoners should be given a chance to return without being trained to expect discounts.
You are given the outline of the Cart and Customer types in the input files. Implement the sequence configuration and the incentive selection logic, and produce a short write-up explaining the strategy choices made.
Output Specification
Produce:
recovery-sequence.ts— exportsgetRecoverySequence(cartValue: number): RecoveryStep[]andgetIncentiveForCustomer(customerId: string, cartValue: number): Promise<Incentive | null>STRATEGY.md— a short document (bullet points are fine) explaining:- Channel order and timing rationale
- When and why incentives are withheld vs. offered
- How VIP vs. new customers are treated differently
- SMS opt-in handling approach
Input Files
The following files are provided as inputs. Extract them before beginning.
=============== FILE: inputs/types.ts =============== export interface RecoveryStep { channel: 'email' | 'push' | 'sms'; delayFromAbandonMs: number; incentive: Incentive | null; template: string; }
export interface Incentive { type: 'free_shipping' | 'percent_off'; value: number; }
export interface Cart { id: string; customerId: string; totalValue: number; customerEmail: string | null; customerPhone: string | null; smsMarketingOptIn: boolean; items: CartItem[]; }
export interface CartItem { productId: string; name: string; price: number; quantity: number; }
export interface Customer { id: string; email: string; totalLifetimeSpend: number; }
{
"context": "Tests whether the agent implements the recovery link endpoint with correct redirect handling for expired/converted states, marks tokens as used, fires the correct analytics event, adds attribution parameters to recovery links, and captures a price snapshot at abandonment time.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Expired token redirect",
"max_score": 9,
"description": "Handler redirects to `/cart?expired=true` (or equivalent path containing 'expired') when the token is not found or has passed its expiry date"
},
{
"name": "Converted cart redirect",
"max_score": 9,
"description": "Handler redirects to `/account/orders` (or equivalent order history path) when the cart status is already 'converted'"
},
{
"name": "Session cart restoration",
"max_score": 8,
"description": "Handler restores the cart to the user's session (e.g. sets `req.session.cartId`) before redirecting"
},
{
"name": "Token marked used",
"max_score": 8,
"description": "Handler calls a markUsed (or equivalent) method on the token record after a successful recovery"
},
{
"name": "Analytics event fired",
"max_score": 9,
"description": "Handler fires an analytics tracking event named 'Cart Recovery Link Clicked' (or similar) including cartId and customerId"
},
{
"name": "Checkout redirect",
"max_score": 8,
"description": "After restoring the session, handler redirects to `/checkout` (not homepage or cart page)"
},
{
"name": "UTM parameters on recovery URL",
"max_score": 9,
"description": "Recovery URL (used in emails/messages) includes UTM parameters (utm_source, utm_medium, utm_campaign or similar) to track recovery traffic"
},
{
"name": "recovered flag",
"max_score": 9,
"description": "Recovery URL or redirect destination includes a `recovered=true` query parameter (or equivalent flag) to distinguish recovery conversions"
},
{
"name": "Snapshot price capture",
"max_score": 9,
"description": "snapshotCart captures item prices from the product at abandonment time (not a live/current lookup), storing them in the snapshot"
},
{
"name": "Snapshot image capture",
"max_score": 7,
"description": "snapshotCart captures at least one product image URL per item in the snapshot"
},
{
"name": "Snapshot total value",
"max_score": 7,
"description": "snapshotCart stores the cart's total value at time of snapshot"
},
{
"name": "Snapshot persistence",
"max_score": 8,
"description": "snapshotCart persists the snapshot to the database (upsert or create on a snapshots table/collection)"
}
]
}
Cart Recovery Link Handler and Analytics
Problem/Feature Description
A fashion retailer's analytics team has noticed that cart recovery revenue is being lumped into organic revenue, making it impossible to measure the ROI of the recovery program. Separately, customer complaints have surfaced about prices shown in recovery emails not matching the checkout price — a side effect of product price changes between when a customer abandons and when they click the recovery link.
The engineering team needs to implement two things: the server-side endpoint that handles recovery link clicks (restoring the customer's cart and routing them to checkout), and a cart snapshot mechanism that captures the state of the cart at the moment of abandonment. The recovery links themselves should carry enough metadata for the analytics team to separate recovery-driven conversions from organic ones.
The app is built with Express and TypeScript. A db object, analytics tracking service, and req.session are available in scope. The token was already stored in db.cartRecoveryTokens when the sequence started.
Output Specification
Produce:
recover-endpoint.ts— exports therecoverCart(req, res)Express handler forGET /cart/recover/:tokencart-snapshot.ts— exportssnapshotCart(cartId: string): Promise<CartSnapshot>which captures the cart state at abandonment timeATTRIBUTION.md— describes how recovery conversions are attributed and tracked separately from organic revenue, including which parameters/flags are added to links and what analytics events are fired
Input Files
The following files are provided as inputs. Extract them before beginning.
=============== FILE: inputs/types.ts =============== export interface CartRecoveryToken { id: string; cartId: string; token: string; expiresAt: Date; usedAt: Date | null; }
export interface Cart { id: string; customerId: string; status: 'active' | 'converted' | 'expired'; totalValue: number; customerEmail: string | null; items: CartItem[]; }
export interface CartItem { productId: string; quantity: number; product: { name: string; price: number; images: { url: string }[]; }; }
export interface CartSnapshot { cartId: string; snapshotAt: Date; items: { productId: string; name: string; image: string | undefined; price: number; quantity: number; }[]; totalValue: number; }
{
"context": "Tests whether the agent implements server-side abandonment detection correctly, uses BullMQ for job scheduling with deterministic job IDs, generates cryptographically secure recovery tokens with the right expiry, and prevents duplicate sequences.",
"type": "weighted_checklist",
"checklist": [
{
"name": "No beforeunload detection",
"max_score": 10,
"description": "Implementation does NOT use `beforeunload` events for abandonment detection — relies solely on server-side timer/cron logic"
},
{
"name": "lastActiveAt stamping",
"max_score": 8,
"description": "onCartUpdated sets `lastActiveAt` to current timestamp on every cart mutation"
},
{
"name": "recoveryTriggered reset",
"max_score": 7,
"description": "onCartUpdated resets `recoveryTriggered` to false on every cart mutation"
},
{
"name": "Cron-based detection",
"max_score": 8,
"description": "findAbandonedCarts is designed to be run on a recurring schedule (documented as cron, interval, or equivalent), not triggered by user events"
},
{
"name": "1-hour inactivity cutoff",
"max_score": 7,
"description": "Abandonment is declared after 1 hour (60 minutes) of inactivity, not a shorter or longer window"
},
{
"name": "recoveryTriggered guard",
"max_score": 8,
"description": "findAbandonedCarts filters for carts where `recoveryTriggered` is false, and sets it to true before starting the sequence"
},
{
"name": "BullMQ queue",
"max_score": 10,
"description": "startRecoverySequence uses BullMQ (Queue from 'bullmq') to schedule recovery jobs, not setTimeout or direct invocation"
},
{
"name": "Deterministic job IDs",
"max_score": 10,
"description": "Each recovery job is added with a deterministic jobId in the format `recovery-${cartId}-step${N}` (or equivalent pattern using cart ID and step index)"
},
{
"name": "randomBytes token",
"max_score": 8,
"description": "Recovery token is generated using Node's `crypto.randomBytes` (20 bytes or similar), not Math.random or UUID"
},
{
"name": "7-day token expiry",
"max_score": 7,
"description": "Recovery token is stored with an expiry of 7 days from creation"
},
{
"name": "Job cancellation on conversion",
"max_score": 9,
"description": "onOrderCompleted retrieves and removes all pending recovery jobs by their deterministic jobIds when an order is placed"
},
{
"name": "Cart status update",
"max_score": 8,
"description": "onOrderCompleted updates the cart status to 'converted' (or equivalent) after cancelling recovery jobs"
}
]
}
Cart Recovery Infrastructure
Problem/Feature Description
An online sporting goods retailer has been losing roughly $40,000 per month from carts that go silent mid-checkout. Their current approach watches for page close events to flag an abandonment, but support has noticed customers are being tagged as abandoners even while actively browsing — and others slip through entirely. The engineering team has been asked to rebuild the abandonment detection system from scratch and wire it into a reliable, delayed messaging pipeline.
The back-end stack is TypeScript with a PostgreSQL database (accessed via a db object) and Redis already running in the environment. The team wants the system to be resilient: no double-sending, no sequences triggered twice for the same cart, and the ability to cancel in-flight jobs cleanly if a customer places an order.
Your job is to implement the core infrastructure: the abandonment detection mechanism, the job scheduling layer that dispatches the sequence of recovery messages, and the secure one-time token used to generate personalised recovery links. You do not need to implement the actual email/SMS sending — focus on the detection, scheduling, and token generation logic.
Output Specification
Produce a TypeScript module cart-recovery.ts that exports:
onCartUpdated(cartId: string)— called on every cart changefindAbandonedCarts()— the periodic check function (document how it should be invoked)startRecoverySequence(cart: Cart)— schedules all recovery jobs and creates the tokenonOrderCompleted(orderId: string)— cancels any in-flight recovery jobs for the related cart
Include a brief DESIGN.md explaining:
- How abandonment is detected (trigger mechanism and timing)
- How duplicate sequences are prevented
- How jobs are identified and cancelled on conversion
- The token generation approach and its expiry
{
"name": "finsi/cart-abandonment-recovery",
"version": "0.1.0",
"summary": "Multi-channel abandonment recovery with timing sequences and incentive escalation",
"skills": {
"cart-abandonment-recovery": {
"path": "SKILL.md"
}
}
}