
Win Back Reactivation
- 71 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Re-engages lapsed customers with automated win-back email sequences using comeback offers based on purchase history and inactivity windows.
About
Builds automated win-back campaigns that identify lapsed customers and send personalized comeback offers by LTV and inactivity. A developer uses it to reactivate customers who have not purchased in 90-180 days.
- Lapsed-customer identification via churn/RFM segments
- Three-step sequence with offers by customer LTV
Win Back Reactivation by the numbers
- 71 all-time installs (skills.sh)
- Ranked #512 of 853 Sales & Marketing 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 win-back-reactivationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 71 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Re-engages lapsed customers with automated win-back email sequences using comeback offers based on purchase history and inactivity windows.
Files
Win-Back Reactivation
Overview
Lapsed customers — those who have not purchased within 2× their typical repurchase cycle — represent a high-ROI recovery opportunity because they already know your brand. Win-back campaigns targeting these customers typically yield 5–15% reactivation rates, compared to 1–3% for cold prospecting. For Shopify, Klaviyo's predictive churn risk segment automates lapsed customer identification without manual RFM scoring. For WooCommerce, AutomateWoo provides a dedicated "Win Back" automation trigger. The strategic work is structuring a three-step email sequence, personalizing offers by customer LTV, and sunsetting contacts who remain unresponsive.
When to Use This Skill
Note: For proactive churn prevention before customers lapse, see @customer-retention-engine. This skill focuses on re-engaging customers who have already become inactive.
- When a large portion of your customer base has not purchased in 90–180 days
- When overall repeat purchase rate is declining year-over-year
- When you have never systematically targeted lapsed customers before
- When lifecycle marketing is in place but there is no specific win-back workflow
- When wanting to identify which lapsed customers are worth discounting vs. sunsetting
Core Instructions
Step 1: Choose your win-back automation tool
| Platform | Recommended Tool | Why | Price |
|---|---|---|---|
| Shopify | Klaviyo | Predictive churn risk segment built-in; syncs Shopify orders natively | Free up to 500 contacts; $20+/mo |
| WooCommerce | AutomateWoo | Native "Win Back" trigger based on days since last order | $99/yr |
| BigCommerce | Klaviyo | Same as Shopify — Klaviyo has a native BigCommerce integration | Free up to 500 contacts; $20+/mo |
| Any platform | Drip | Ecommerce-focused automation with built-in win-back workflow templates | $39+/mo |
Recommendation: If you already use Klaviyo for cart abandonment or post-purchase flows, add win-back there — no additional tool needed. If you are on WooCommerce without Klaviyo, AutomateWoo is the most cost-effective option.
Step 2: Set up lapsed customer segments
---
Shopify / BigCommerce with Klaviyo
Klaviyo includes a Predictive Churn Risk property on every customer profile, calculated automatically from purchase history. No manual RFM setup required.
1. In Klaviyo, go to Lists & Segments → Create Segment 2. Create three segments using Properties about someone:
Early Lapsed (60–120 days):
- Predictive Churn Risk
equalsHigh - AND Date of last order
is between120 days agoand60 days ago - AND Email marketing consent
issubscribed
Mid-Lapsed (121–180 days):
- Predictive Churn Risk
equalsHigh - AND Date of last order
is between180 days agoand121 days ago
Deep-Lapsed (181–365 days):
- Predictive Churn Risk
equalsHigh - AND Date of last order
is between365 days agoand181 days ago
3. Alternatively, use Klaviyo's pre-built Winback flow template: go to Flows → Create Flow → Browse Templates → search "Win Back" → the template creates segments and email sequence automatically
---
WooCommerce with AutomateWoo
1. Go to WordPress Admin → AutomateWoo → Workflows → Add Workflow 2. Set the trigger to Win Back Inactive Customer 3. Configure trigger settings:
- Days since last order: set to
60for early-lapsed, create a second workflow at120for deeper lapsed - Order status:
completed
4. Add a Send Email action for each step in the sequence 5. AutomateWoo automatically excludes customers who have placed an order since the workflow started — no manual cancellation needed
---
Manual segmentation (any platform)
If you do not use an automation tool, export your customer list filtered by last order date:
- Shopify: go to Customers → All customers → use the date filter "Last order date is before [date]" → export as CSV → import into your email platform
- WooCommerce: go to WooCommerce → Reports → Customers → filter by last active date
- BigCommerce: go to Customers → Export and filter by last order date in your spreadsheet
Step 3: Build the win-back email sequence
A three-email sequence performs better than a single message. The sequence escalates from warm reconnection to an explicit offer to a last-chance message:
| Step | Timing | Goal | Discount |
|---|---|---|---|
| Email 1: "We've missed you" | Immediately | Warm reconnect, no hard sell | None — highlight new arrivals |
| Email 2: "Here's something for you" | Day 5 | Product highlights + offer | 10–15% off or free shipping |
| Email 3: "Last chance" | Day 12 | Create urgency — offer expires soon | Same code, "expires in 48 hours" |
Subject line examples that work:
- Email 1: "It's been a while, [first name]" / "We've been thinking about you"
- Email 2: "Still thinking about [last purchased category]?" / "A little something for your return"
- Email 3: "Your offer expires tomorrow" / "This is our last email — we mean it"
Klaviyo sequence setup: 1. Go to Flows → Create Flow → Start from Scratch 2. Set the trigger to Segment → [your lapsed segment] 3. Add Email → Time Delay (5 days) → Email → Time Delay (7 days) → Email 4. Add a Conditional Split after Email 1: if the customer has placed an order since the flow started, exit them from the flow 5. For the discount: go to Content → Coupon Codes → create a unique dynamic coupon for each recipient (Klaviyo generates unique single-use codes automatically)
Step 4: Personalize offers by customer LTV
Not all lapsed customers deserve the same discount. Tailor the offer based on historical spend:
In Klaviyo:
- Use the Historic Customer Lifetime Value property in your email template
- Create two versions of Email 2 using A/B test or conditional content blocks:
- High LTV (over $300 lifetime): free shipping + 15% off
- Standard LTV (under $300): 10% off
In AutomateWoo:
- Add a Check Customer Total Spent condition to your workflow
- If total spent
>300: proceed to the high-value email variation - Otherwise: proceed to the standard email variation
Step 5: Cancel win-back sequence on purchase
This step prevents sending a discount email to a customer who already bought.
In Klaviyo: Klaviyo handles this automatically — any profile that exits the lapsed segment (by making a purchase) immediately exits all active flows for that segment.
In AutomateWoo: the "Win Back Inactive Customer" trigger natively cancels pending workflow steps when an order is placed — no additional configuration needed.
Manual check (custom setups): Before sending each email in your sequence, verify the customer's last order date is still beyond your lapse threshold. Cancel the remaining sequence if they have purchased.
Step 6: Sunset unresponsive contacts
Continuing to email completely unresponsive lapsed contacts hurts your sending domain's deliverability.
In Klaviyo: 1. Go to Lists & Segments → Create Segment:
- Email marketing consent
issubscribed - AND Last opened email
more than90 days ago - AND Last clicked email
more than90 days ago - AND Date of last order
more than180 days ago
2. Send a single Re-permission email: "We'll stop sending you emails unless you click to stay subscribed" 3. Anyone who does not click within 14 days: go to the segment → Manage Members → Suppress All — this removes them from all future sends without deleting them 4. Suppression is reversible — they can re-subscribe at any time
In AutomateWoo:
- Add a final step to your win-back workflow: after 30 days of no engagement, add the customer to an "unsubscribe" list or use the Unsubscribe Customer action
Best Practices
- Lead with connection, not desperation — the first message should feel warm ("We've missed you") rather than transactional; aggressive discounts on message 1 train customers to wait for the win-back offer
- Personalize with previous purchase context — referencing what the customer previously bought increases open rates by 25–35% vs. generic "we miss you" subject lines; Klaviyo's
{{ event.extra.product_name }}pulls last purchased product name automatically - Respect the email sunset — suppressing non-engagers after 90 days of inactivity protects your domain reputation and improves deliverability for everyone else
- Vary offer levels by LTV — a 15% discount that wins back a $500 LTV customer is excellent ROI; the same discount on a $40 LTV customer barely covers the cost
- Set a 60-day cooldown — after a completed win-back cycle (converted or suppressed), do not re-enter the customer into another win-back flow for at least 60 days
- Use SMS for mid-lapsed only — SMS has high engagement but also high unsubscribe rates when used for lapsed contacts; reserve SMS for the mid-lapsed tier (121–180 days) where the customer relationship is still warm
Common Pitfalls
| Problem | Solution |
|---|---|
| Win-back email sent the day after a purchase | Ensure exit conditions are set in Klaviyo flows; add a last-order-date check as a conditional split at every step |
| Discount codes being shared publicly | Create all win-back codes as single-use in Klaviyo (dynamic coupon codes); Klaviyo generates a unique code per recipient automatically |
| Win-back sequence triggering on customers who just unsubscribed | Klaviyo excludes suppressed profiles from all flows automatically; for manual setups, check consent status before every send |
| Repeat win-back campaigns on the same customer every 30 days | Set a 60-day cooldown segment condition; exclude customers who have exited any win-back flow in the last 60 days |
| Low reactivation rate on deep-lapsed (180+ days) | Try a "we're sorry, is everything okay?" empathetic tone for deep-lapsed; include a preference center link so they can choose email frequency rather than unsubscribing entirely |
Related Skills
- @customer-retention-engine
- @lifecycle-marketing-automation
- @email-marketing-automation
- @email-list-segmentation
- @loyalty-program-optimization
{
"context": "Tests whether the agent correctly defines three lapsed customer segments with the right day thresholds, offer strengths, and channel assignments, and queries customers with the correct filters and ordering.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Early-lapsed range",
"max_score": 8,
"description": "The early-lapsed segment uses minDaysSince=60 and maxDaysSince=120"
},
{
"name": "Mid-lapsed range",
"max_score": 8,
"description": "The mid-lapsed segment uses minDaysSince=121 and maxDaysSince=180"
},
{
"name": "Deep-lapsed range",
"max_score": 8,
"description": "The deep-lapsed segment uses minDaysSince=181 and maxDaysSince=365"
},
{
"name": "Early-lapsed channels",
"max_score": 7,
"description": "The early-lapsed segment has channel set to email only (no sms, no paid-retargeting)"
},
{
"name": "Mid-lapsed channels",
"max_score": 7,
"description": "The mid-lapsed segment has channel set to email and sms (no paid-retargeting)"
},
{
"name": "Deep-lapsed channels",
"max_score": 7,
"description": "The deep-lapsed segment has channel set to email, sms, and paid-retargeting"
},
{
"name": "Offer strength mapping",
"max_score": 8,
"description": "early-lapsed → soft, mid-lapsed → medium, deep-lapsed → strong"
},
{
"name": "emailVerified filter",
"max_score": 8,
"description": "The customer query filters for emailVerified: true"
},
{
"name": "SMS opt-in conditional",
"max_score": 10,
"description": "smsMarketingOptIn: true is applied only when the segment channel includes 'sms'; not applied for email-only segments"
},
{
"name": "Order by totalSpend",
"max_score": 10,
"description": "Customers are ordered by totalSpend descending (highest-value customers first)"
},
{
"name": "Date window calculation",
"max_score": 9,
"description": "The date window uses subDays(new Date(), maxDaysSince) as minDate and subDays(new Date(), minDaysSince) as maxDate, filtering lastOrderAt between them"
},
{
"name": "Three segments only",
"max_score": 10,
"description": "Exactly three segments are defined (early-lapsed, mid-lapsed, deep-lapsed) — no additional or missing segments"
}
]
}
Customer Lapse Audit Tool
Problem Description
The growth team at a mid-sized e-commerce company has noticed that repeat purchase rates have been declining steadily. Leadership suspects a large chunk of the customer base has quietly gone inactive, but there's no current tooling to measure or categorize this. Before they can launch any re-engagement efforts, they need a reliable way to identify and group lapsed customers based on how long it has been since their last order.
The company wants a TypeScript module that queries their customer database and returns structured segments of lapsed customers. The output should help the marketing team understand the urgency and appropriate communication strategy for each group, and prioritize the most commercially valuable customers within each group. Some customers are reachable via SMS for more urgent contact, but SMS permissions must always be respected.
Output Specification
Write a TypeScript file lapsed-segments.ts that:
- Defines the lapsed segment configuration (the distinct groups, their day thresholds, offer intensity, and eligible channels)
- Implements a function
getLapsedCustomers(segment)that queries the database and returns matching customers
Also write a short README.md explaining the segment definitions (names, day ranges, offer strengths, and channels) in a table.
Assume the following are available via imports:
dbfrom'./db'(withdb.customers.findAll(...))subDaysfrom'date-fns'- A
Customertype from'./types'
Do not implement the database layer itself — focus only on the segment configuration and query logic.
{
"context": "Tests whether the agent correctly cancels win-back jobs on purchase and marks them converted, implements the 30-day sunset check with correct suppression fields, sends a resubscription notice before suppression, and applies proper cooldown and pre-send safety checks.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Cancel steps 0–3",
"max_score": 10,
"description": "onOrderPaid removes all four possible win-back queue jobs (step 0 through step 3) using job IDs matching `winback-${customerId}-step${n}`"
},
{
"name": "Mark job converted",
"max_score": 10,
"description": "onOrderPaid updates the active win-back job to status 'converted', setting convertedAt and convertedOrderId"
},
{
"name": "Sunset: 30-day window",
"max_score": 9,
"description": "runSunsetWorkflow targets win-back jobs with status 'active' and createdAt older than 30 days"
},
{
"name": "Engagement check",
"max_score": 9,
"description": "For each job, the workflow queries email events for opens or clicks (type in ['open', 'click']) created after the job's start time"
},
{
"name": "Suppression fields",
"max_score": 10,
"description": "Non-engaged customers are updated with emailMarketingStatus: 'suppressed', suppressedAt, and suppressionReason: 'win-back-no-engagement'"
},
{
"name": "Resubscription notice sent",
"max_score": 10,
"description": "Before suppressing, the workflow sends an 'email-suppression-notice' email warning the customer that emails will stop unless they click to stay subscribed, including a resubscribeUrl"
},
{
"name": "Job marked completed",
"max_score": 8,
"description": "After processing (whether engaged or not), the win-back job is marked as status 'completed'"
},
{
"name": "Email status pre-send check",
"max_score": 9,
"description": "The process notes or code describes/implements checking the customer's emailMarketingStatus before each send to skip unsubscribed customers"
},
{
"name": "Last purchase pre-send check",
"max_score": 8,
"description": "The process notes or code describes/implements verifying the customer has not purchased recently before sending each win-back message (in the worker, not just at enqueue time)"
},
{
"name": "60-day cooldown noted",
"max_score": 9,
"description": "The process notes or code mentions a 60-day cooldown period before re-enrolling a customer in a new win-back cycle after a completed campaign"
},
{
"name": "Correct suppression reason string",
"max_score": 8,
"description": "The suppressionReason is set to the exact string 'win-back-no-engagement'"
}
]
}
Win-Back Campaign Cleanup and Deliverability Protection
Problem Description
The engineering team at an online retailer has been running win-back email campaigns for six months. Two problems have emerged. First, a handful of customers have complained that they received promotional emails the same day they made a purchase, which damaged trust and generated unsubscribe complaints. The team suspects pending campaign jobs aren't being cancelled when an order is placed. Second, the company's email deliverability scores have been declining — investigation points to a growing list of completely unresponsive contacts who are still receiving campaign emails month after month. Industry best practice recommends removing these contacts from the active mailing list, but the legal team insists on giving them one final opt-in opportunity before suppression.
The team wants two pieces of functionality implemented to address these issues: a robust order-completion handler that terminates any in-flight win-back activity when a purchase happens, and a periodic cleanup job that identifies contacts who have never engaged with win-back emails and gracefully sunsets them.
Output Specification
Write a TypeScript file win-back-lifecycle.ts that implements: 1. onOrderPaid(order) — handles the cleanup when a customer makes a purchase 2. runSunsetWorkflow() — runs periodically to suppress chronically unresponsive contacts
Also write a short process-notes.md describing:
- What the order handler does step by step
- The criteria used to identify unresponsive contacts and what actions are taken
Assume the following are available via imports:
dbfrom'./db'winBackQueuefrom'./queue'sendEmailfrom'./mailer'subDaysfrom'date-fns'- Types
Orderfrom'./types'
{
"context": "Tests whether the agent correctly implements offer selection based on LTV, product recommendation filtering, deduplication logic, discount code configuration, and a properly timed three-step email sequence with the right templates.",
"type": "weighted_checklist",
"checklist": [
{
"name": "LTV threshold $300",
"max_score": 9,
"description": "High-value classification uses ltv >= 300, where LTV is the sum of the last 5 orders' subtotals"
},
{
"name": "Soft offer for high-LTV",
"max_score": 7,
"description": "Soft + high-LTV customer gets { type: 'free_shipping', value: 0 }; soft + low-LTV gets null (no offer)"
},
{
"name": "Medium offer values",
"max_score": 7,
"description": "Medium + high-LTV gets 15% off; medium + low-LTV gets 10% off"
},
{
"name": "Strong offer values",
"max_score": 7,
"description": "Strong + high-LTV gets 20% off; strong + low-LTV gets 15% off"
},
{
"name": "Recommendation filters",
"max_score": 9,
"description": "Recommendations are drawn from the last 10 order line items, filtered to same categories, excluding already-purchased product IDs, active and in-stock only"
},
{
"name": "Recommendation ordering",
"max_score": 7,
"description": "Recommended products are ordered by unitsSold30d descending and limited to 3"
},
{
"name": "Deduplication check",
"max_score": 9,
"description": "triggerWinBackSequence checks for an existing active win-back job created within the last 30 days and returns early if one exists"
},
{
"name": "Discount: single-use, 14-day expiry",
"max_score": 9,
"description": "Discount codes are created with singleUse: true and expiresAt set to 14 days from now"
},
{
"name": "Step 0: no discount on first touch",
"max_score": 9,
"description": "Step 0 uses template 'winback-reconnect' with discountCode: null (no discount in the first message)"
},
{
"name": "Step 1: delay and template",
"max_score": 9,
"description": "Step 1 uses template 'winback-offer' with a delay of 5 days (5 * 86400000 ms) and includes both recommendations and the discount code"
},
{
"name": "Step 2: conditional and timing",
"max_score": 10,
"description": "Step 2 is only scheduled if a discountCode exists, uses template 'winback-last-chance' with a delay of 12 days (12 * 86400000 ms) and passes expiresInDays: 2"
},
{
"name": "Job ID naming",
"max_score": 8,
"description": "Queue jobs use IDs in the format `winback-${customerId}-step${n}` (e.g. winback-abc123-step0)"
}
]
}
Automated Win-Back Campaign Engine
Problem Description
A DTC (direct-to-consumer) brand has identified several thousand customers across different lapsed segments and wants to automatically enroll each of them in a targeted re-engagement campaign. The marketing team has found through past experiments that simply blasting all lapsed customers with the same coupon code results in low conversion and erodes brand trust. They want a smarter approach: tailoring both the offer given and the product recommendations shown based on each customer's purchase history, and rolling out the outreach in a considered sequence rather than all at once.
A key concern is duplicate enrollments — customers must not be re-enrolled in a campaign if they were already targeted recently. The team also wants to ensure incentives are earned and meaningful: discounts should be valid for a limited window and non-transferable, and the opening message should focus on reconnection rather than an immediate push to buy.
Output Specification
Write a TypeScript file win-back-engine.ts that implements: 1. selectWinBackOffer(customerId, offerStrength) — returns the appropriate offer based on the customer's purchase history 2. getWinBackRecommendations(customerId) — returns personalized product suggestions 3. triggerWinBackSequence(customerId, segment) — orchestrates the full campaign enrollment including deduplication, offer creation, product recommendations, and job scheduling
Assume the following are available via imports:
dbfrom'./db'winBackQueuefrom'./queue'createUniqueDiscountfrom'./discounts'subDays,addDaysfrom'date-fns'- Types
Customer,Product,LapsedSegmentfrom'./types'
Do not implement database or queue internals — just the campaign orchestration logic.
{
"name": "finsi/win-back-reactivation",
"version": "0.1.0",
"summary": "Re-engage lapsed customers with automated win-back campaigns using personalized comeback offers based on purchase history and inactivity windows",
"skills": {
"win-back-reactivation": {
"path": "SKILL.md"
}
}
}