
Email List Segmentation
- 63 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Create dynamic email segments by purchase behavior, RFM, and engagement with automated rebalancing and list hygiene.
About
Builds behavioral and RFM email segments in Klaviyo, AutomateWoo, or Mailchimp plus suppression logic to protect deliverability. A developer uses it when open rates drop or when moving from blast sends to targeted campaigns.
- Per-platform segmentation tool table
- Essential five-segment starter framework plus list hygiene
Email List Segmentation by the numbers
- 63 all-time installs (skills.sh)
- Ranked #531 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 email-list-segmentationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 63 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Create dynamic email segments by purchase behavior, RFM, and engagement with automated rebalancing and list hygiene.
Files
Email List Segmentation
Overview
Sending the same email to your entire list is one of the most expensive mistakes in ecommerce marketing. ISPs throttle senders with low engagement, unsubscribe rates spike for irrelevant content, and you leave revenue on the table by treating a VIP customer identically to someone who has never purchased. Klaviyo (for Shopify/BigCommerce) and AutomateWoo or Mailchimp (for WooCommerce) include RFM and behavioral segmentation built-in — no custom code needed for most stores.
When to Use This Skill
- When email open rates drop below 20% and you suspect low engagement dragging deliverability
- When migrating from blast-and-pray sends to targeted campaign logic
- When building a suppression system to protect high-value subscribers from over-messaging
- When launching a win-back program and need to identify the "at-risk" vs. "lapsed" cohort
- When analyzing email revenue and needing to attribute performance to specific segments
Core Instructions
Step 1: Choose the right segmentation tool
| Platform | Recommended Tool | Built-in Segmentation Capabilities |
|---|---|---|
| Shopify | Klaviyo | Predictive CLV, churn risk, RFM scoring, purchase behavior segments — all built-in |
| WooCommerce | Klaviyo (via plugin) or AutomateWoo | Klaviyo plugin syncs order data; AutomateWoo has native WooCommerce RFM reports |
| BigCommerce | Klaviyo | Native BigCommerce integration with full order history sync |
| All platforms | Klaviyo (via API) | Use Klaviyo's Track/Identify API to send behavioral events from any platform |
Step 2: Set up the essential segments — start with five
Build these five segments first. They cover 80% of the value. Add more only when you have campaigns ready for each.
---
Klaviyo (Shopify, BigCommerce, or API-connected)
Segment 1: Champions (VIP buyers) 1. Go to Klaviyo → Segments → Create Segment 2. Conditions:
Has placed orderat least 5 times ever- AND
Total Customer Valuegreater than $500 - AND
Opened emailin the last 60 days
3. Save as "Champions — VIP Buyers"
Segment 2: Active buyers (engaged, not yet VIP) 1. Create segment with conditions:
Has placed orderat least 2 times ever- AND
Has placed order in the last 90 days
2. Save as "Active Buyers"
Segment 3: At-risk (used to buy, now quiet) 1. Create segment using Klaviyo's predictive analytics:
Predicted Churn RiskequalsHigh- AND
Has placed orderat least 1 time ever
2. Save as "At-Risk Customers"
Segment 4: Email-engaged non-buyers 1. Create segment with conditions:
Has placed 0 orders- AND
Opened emailin the last 30 days
2. Save as "Engaged Subscribers — No Purchase"
Segment 5: Unengaged (to suppress) 1. Create segment with conditions:
Has NOT opened emailin the last 180 days- AND
Has NOT clicked emailin the last 180 days - AND
Email subscription statusisSubscribed
2. Save as "Unengaged — Suppression List"
Suppress the unengaged segment before every campaign send:
- When creating a campaign in Klaviyo, under Excluded Segments, add "Unengaged — Suppression List"
- This protects deliverability by not sending to cold contacts who will mark you as spam
---
WooCommerce with Mailchimp or Klaviyo
Mailchimp: 1. Go to Mailchimp → Audience → Segments → Create Segment 2. Mailchimp's ecommerce segments require the WooCommerce + Mailchimp plugin for purchase data sync 3. Common conditions: "Purchased" / "Has not purchased" / "Total spent is greater than" / "Last purchase was more than 90 days ago"
AutomateWoo (for WooCommerce automation, not campaigns): 1. Go to AutomateWoo → Reports → RFM Analysis to view your customers plotted on an RFM grid 2. Export customer lists from each RFM quadrant and import into your ESP for targeted campaigns
---
WooCommerce with Klaviyo plugin
1. Install the Klaviyo plugin from the WordPress plugin directory 2. In Klaviyo, all WooCommerce orders and customers sync automatically 3. Follow the same segment setup as the Shopify/Klaviyo instructions above
---
Step 3: Set up behavioral segments for campaign targeting
Beyond RFM, create segments that map to specific campaigns:
Sale shoppers (always use discounts — suppress from full-price launches):
- Klaviyo condition:
Has used a discount codemore than 2 times ever
High AOV customers (show premium products):
- Klaviyo condition:
Average Order Valuegreater than $150
Category-specific buyers (for product launches):
- Klaviyo condition:
Ordered product in category= "Skincare" (use your Shopify product type or Klaviyo collection data)
Recently subscribed non-buyers (in welcome flow):
- Klaviyo condition:
Subscribed to emailin the last 30 days ANDHas placed 0 orders
Step 4: Apply segments to campaigns
In Klaviyo, when creating a campaign: 1. Under Recipient Selection, choose your target segment (e.g., "Active Buyers") 2. Under Excluded Segments, always add "Unengaged — Suppression List" 3. Preview the segment size before sending — avoid sending to segments under 200 contacts (can trigger spam filters on some providers)
Frequency rules by segment:
- Champions: max 2 emails/week (they are engaged; maintain relationship but don't over-message)
- Active Buyers: 1–2 emails/week
- At-Risk: 1 email/week (retention focus)
- Engaged Subscribers (no purchase): 1–2 emails/week (nurture toward first purchase)
Step 5: List hygiene — run quarterly
1. In Klaviyo, go to Segments → [Unengaged list] and check the size 2. If unengaged segment exceeds 20% of your total list, run a sunset campaign:
- Send one final email: "We've noticed you haven't engaged lately — still want to hear from us?"
- Wait 7 days
- Suppress anyone who did not open the sunset email
3. Never delete unsubscribed contacts — suppress them. Klaviyo retains the unsubscribe signal to prevent accidental re-subscription.
Best Practices
- Start with 5 segments, not 50 — champion, active, at-risk, engaged non-buyer, unengaged is enough to see 80% of the benefit
- Suppress before you send — always build a suppression list (unengaged + unsubscribed) and apply it to every campaign
- Protect champions — never include your champions segment in sales/discount campaigns unless you want to train VIPs to wait for promotions
- Engagement tier beats purchase history for deliverability — for inbox placement, behavioral engagement signals matter more than how much someone has spent
- Process unsubscribes immediately — Klaviyo handles this automatically via webhooks; ensure your ESP is connected to your platform's unsubscribe events
- GDPR: for EU contacts, track consent separately and never rely on legitimate interest for marketing emails — require explicit opt-in
Common Pitfalls
| Problem | Solution |
|---|---|
| Open rates drop after adding segmentation | Ensure segments are correctly excluding unsubscribed contacts; check Klaviyo's suppression list under Audience → Suppressions |
| Klaviyo lists go out of sync with Shopify | Confirm the Klaviyo–Shopify integration is connected under Klaviyo → Integrations → Shopify; enable "Sync historical data" |
| Champions segment shrinks after every send | You are emailing them too frequently — apply Klaviyo's Smart Sending limit (once every 16 hours per person) |
| GDPR violations from imported list | Validate lawful basis for all contacts before import; only import contacts who have explicitly opted in |
| Segments overlap and contacts receive duplicate emails | In Klaviyo, use "Send to unique recipients" option and check for segment overlap before sending |
Related Skills
- @email-marketing-automation
- @win-back-reactivation
- @lifecycle-marketing-automation
- @customer-retention-engine
- @first-party-data-collection
{
"context": "Tests whether the agent correctly builds a pre-campaign suppression system that excludes unengaged, bounced, spam-complaint, and unsubscribed contacts, enforces GDPR consent rules, protects the champions segment from discount campaigns, and validates minimum segment size.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Suppress unengaged (totalSent >= 5)",
"max_score": 9,
"description": "Suppression logic excludes subscribers classified as 'unengaged' who have received at least 5 emails (totalSent >= 5), not just any unengaged subscriber"
},
{
"name": "Suppress bounced",
"max_score": 8,
"description": "Suppression logic excludes subscribers flagged as bounced"
},
{
"name": "Suppress spam complaints",
"max_score": 8,
"description": "Suppression logic excludes subscribers with a spam complaint flag"
},
{
"name": "Suppress unsubscribed",
"max_score": 8,
"description": "Suppression logic excludes subscribers where unsubscribedAt is set (not null)"
},
{
"name": "Champions excluded from discount",
"max_score": 10,
"description": "The solution explicitly excludes the 'champions' segment from any discount or promotional campaign audience"
},
{
"name": "Minimum segment size check",
"max_score": 9,
"description": "Code includes a guard or validation that prevents sending to segments with fewer than 200 contacts"
},
{
"name": "marketingConsent filter",
"max_score": 9,
"description": "All segment queries filter on marketingConsent: true and exclude records where unsubscribedAt is set"
},
{
"name": "GDPR consent basis enforcement",
"max_score": 10,
"description": "For EU contacts, the solution uses 'consent' as the lawful basis for marketing emails and does NOT accept 'legitimate-interest' as a valid basis for sending marketing emails"
},
{
"name": "Engagement tier thresholds",
"max_score": 10,
"description": "Engagement tier classification uses openRate > 0.3 with daysSinceClick < 60 for 'champion', and openRate > 0.15 with daysSinceOpen < 90 for 'active'"
},
{
"name": "Suppression applied before send",
"max_score": 10,
"description": "The suppression list is built and deduplicated against the target segment list before producing the final sendable audience — not as an afterthought"
},
{
"name": "Deliverable output artifact",
"max_score": 9,
"description": "The solution produces a file or output (e.g., sendable list, suppression report, or validation results) that shows the final audience after suppression is applied"
}
]
}
Pre-Campaign Audience Validation System
Problem Description
A fashion ecommerce brand is launching a 20%-off storewide sale and wants to send the promotional email to their most engaged subscribers. Their list has grown organically over several years and contains a mix of highly loyal customers, occasional shoppers, and contacts who have not interacted in years. The brand also has European customers, which introduces GDPR obligations they must respect.
Before the campaign goes out, the marketing operations team needs a script that takes the full subscriber list and the proposed target segment and produces a clean, validated send list. In past campaigns, they accidentally emailed contacts who had unsubscribed, triggered spam filters due to sending to disengaged contacts, and received GDPR complaints from EU customers whose consent records were not properly validated. They also discovered that their most valuable loyal customers started holding off purchases until promotional emails arrived — a problem they want to avoid repeating.
The team needs a TypeScript (or JavaScript) module that validates and cleans a proposed campaign audience before delivery. It should produce a report showing how many contacts were removed and why, and the final list of addresses approved for sending.
Output Specification
Produce:
campaign-validator.ts(or.js) — the validation module with the core logicvalidation-report.json— output when the module is run against the provided input, showing suppression counts by reason and the final approved address list (or count)
The following files are provided as inputs. Extract them before beginning.
=============== FILE: inputs/subscribers.json =============== [ { "email": "alice@example.com", "rfmSegment": "champions", "engagementTier": "champion", "openRate": 0.75, "lastOpenDate": "2026-03-10", "lastClickDate": "2026-03-08", "totalSent": 24, "marketingConsent": true, "unsubscribedAt": null, "bounced": false, "spamComplaint": false, "gdprLawfulBasis": "consent", "region": "EU" }, { "email": "bob@example.com", "rfmSegment": "new-customers", "engagementTier": "active", "openRate": 0.20, "lastOpenDate": "2026-03-01", "lastClickDate": null, "totalSent": 3, "marketingConsent": true, "unsubscribedAt": null, "bounced": false, "spamComplaint": false, "gdprLawfulBasis": null, "region": "US" }, { "email": "claire@example.com", "rfmSegment": "loyal", "engagementTier": "active", "openRate": 0.18, "lastOpenDate": "2026-02-20", "lastClickDate": "2026-02-15", "totalSent": 30, "marketingConsent": true, "unsubscribedAt": null, "bounced": false, "spamComplaint": false, "gdprLawfulBasis": "consent", "region": "EU" }, { "email": "david@example.com", "rfmSegment": "potential-loyal", "engagementTier": "active", "openRate": 0.22, "lastOpenDate": "2026-02-10", "lastClickDate": "2026-02-05", "totalSent": 15, "marketingConsent": true, "unsubscribedAt": null, "bounced": false, "spamComplaint": false, "gdprLawfulBasis": "legitimate-interest", "region": "EU" }, { "email": "eva@example.com", "rfmSegment": "hibernating", "engagementTier": "unengaged", "openRate": 0.00, "lastOpenDate": null, "lastClickDate": null, "totalSent": 10, "marketingConsent": true, "unsubscribedAt": null, "bounced": false, "spamComplaint": false, "gdprLawfulBasis": null, "region": "US" }, { "email": "frank@example.com", "rfmSegment": "cant-lose", "engagementTier": "dormant", "openRate": 0.05, "lastOpenDate": "2025-06-01", "lastClickDate": null, "totalSent": 40, "marketingConsent": true, "unsubscribedAt": null, "bounced": false, "spamComplaint": false, "gdprLawfulBasis": null, "region": "US" }, { "email": "grace@example.com", "rfmSegment": "at-risk", "engagementTier": "at-risk", "openRate": 0.10, "lastOpenDate": "2025-11-20", "lastClickDate": null, "totalSent": 20, "marketingConsent": false, "unsubscribedAt": null, "bounced": false, "spamComplaint": false, "gdprLawfulBasis": null, "region": "US" }, { "email": "henry@example.com", "rfmSegment": "loyal", "engagementTier": "champion", "openRate": 0.80, "lastOpenDate": "2026-03-05", "lastClickDate": "2026-03-04", "totalSent": 20, "marketingConsent": true, "unsubscribedAt": null, "bounced": false, "spamComplaint": false, "gdprLawfulBasis": "consent", "region": "EU" }, { "email": "irene@example.com", "rfmSegment": "lost", "engagementTier": "unengaged", "openRate": 0.02, "lastOpenDate": "2024-01-01", "lastClickDate": null, "totalSent": 8, "marketingConsent": true, "unsubscribedAt": "2025-05-01", "bounced": false, "spamComplaint": false, "gdprLawfulBasis": null, "region": "US" }, { "email": "james@example.com", "rfmSegment": "potential-loyal", "engagementTier": "active", "openRate": 0.35, "lastOpenDate": "2026-03-01", "lastClickDate": "2026-02-25", "totalSent": 18, "marketingConsent": true, "unsubscribedAt": null, "bounced": true, "spamComplaint": false, "gdprLawfulBasis": null, "region": "US" }, { "email": "kate@example.com", "rfmSegment": "hibernating", "engagementTier": "unengaged", "openRate": 0.01, "lastOpenDate": "2023-12-01", "lastClickDate": null, "totalSent": 50, "marketingConsent": true, "unsubscribedAt": null, "bounced": false, "spamComplaint": true, "gdprLawfulBasis": null, "region": "US" }, { "email": "liam@example.com", "rfmSegment": "champions", "engagementTier": "champion", "openRate": 0.65, "lastOpenDate": "2026-03-11", "lastClickDate": "2026-03-10", "totalSent": 22, "marketingConsent": true, "unsubscribedAt": null, "bounced": false, "spamComplaint": false, "gdprLawfulBasis": "consent", "region": "EU" } ]
=============== FILE: inputs/campaign.json =============== { "name": "Spring Sale 20% Off", "type": "promotional_discount", "targetSegments": ["champions", "loyal", "potential-loyal", "active"], "targetRegions": ["US", "EU"] }
{
"context": "Tests whether the agent builds a Klaviyo segment sync service using the correct package, API method, and batching, handles real-time unsubscribe events without waiting for nightly sync, and uses engagement tier signals (not just RFM) when determining deliverability-sensitive segment membership.",
"type": "weighted_checklist",
"checklist": [
{
"name": "klaviyo-api package used",
"max_score": 10,
"description": "Code imports from 'klaviyo-api' package (not a different Klaviyo client such as 'klaviyo', 'klaviyo-node', or a raw HTTP call library)"
},
{
"name": "KLAVIYO_PRIVATE_KEY env var",
"max_score": 8,
"description": "API key is sourced from the KLAVIYO_PRIVATE_KEY environment variable (not hardcoded or from a differently named variable)"
},
{
"name": "Batch size of 1000",
"max_score": 10,
"description": "Profiles are sent to Klaviyo in batches of exactly 1000 per API call (not a different batch size)"
},
{
"name": "subscribeProfiles API method",
"max_score": 10,
"description": "Uses profiles.subscribeProfiles() method with type 'profile-subscription-bulk-create-job' (not a list import or other method)"
},
{
"name": "Segment property on profiles",
"max_score": 8,
"description": "Each profile sent to Klaviyo includes a segment property in its attributes to identify which segment it belongs to"
},
{
"name": "Nightly sync scheduled",
"max_score": 9,
"description": "Solution includes a nightly scheduled job (cron, scheduler, or documentation of schedule) to sync all segments"
},
{
"name": "Event-triggered sync",
"max_score": 9,
"description": "Sync is also triggered on key events (at minimum: order placed and/or unsubscribe/opt-out), not only on the nightly schedule"
},
{
"name": "Unsubscribe processed immediately",
"max_score": 10,
"description": "Unsubscribe webhook handler processes the opt-out synchronously/immediately (updates the record right away) rather than deferring to the nightly batch"
},
{
"name": "Engagement tier for deliverability",
"max_score": 8,
"description": "When building the audience for deliverability-sensitive or inbox-placement segments, the solution filters or prioritizes by engagementTier (champion or active) rather than exclusively by RFM segment"
},
{
"name": "Behavioral tags in segmentation",
"max_score": 9,
"description": "Solution defines or uses at least one behavioral tag (e.g., 'sale-shopper', 'high-aov', 'frequent-buyer') as a segment criterion alongside RFM/engagement fields"
},
{
"name": "marketingConsent and unsubscribedAt filter",
"max_score": 9,
"description": "All segment queries applied before syncing to Klaviyo filter to marketingConsent: true and unsubscribedAt: null"
}
]
}
Email Segment Sync Service for Klaviyo
Problem Description
A growing DTC (direct-to-consumer) apparel brand uses Klaviyo as their email service provider. They have recently built an internal segmentation database that classifies subscribers by purchase behavior and email engagement, and now need to keep Klaviyo's list management in sync with their internal data.
Currently, their segments in Klaviyo are stale because updates only happen manually. Campaigns are going to the wrong audiences: lapsed customers are receiving retention emails meant for active buyers, and the internal team cannot trust the Klaviyo segment counts when planning campaigns. Additionally, several customers have complained that they received emails after opting out, because the unsubscribe event was only reflected in Klaviyo the following night during a bulk sync.
The engineering team wants a TypeScript service that: 1. Pushes their classified subscriber lists to Klaviyo on a regular automated schedule so segments stay current 2. Also updates Klaviyo immediately when specific real-time events occur (like a customer completing an order or opting out)
The solution should be built to handle their list size of up to 80,000 subscribers efficiently, and must respect Klaviyo's API constraints.
Output Specification
Produce:
klaviyo-sync.ts— the sync service implementation including scheduled sync and event-triggered sync functionswebhook-handler.ts— an HTTP handler (Express route or equivalent) that processes incoming ESP/platform webhook events and triggers the appropriate sync actionsREADME.md— brief explanation of how to configure and run the service, including required environment variables and schedule
The service should use the following pre-classified subscriber data. Extract the input file before beginning.
=============== FILE: inputs/classified-subscribers.json =============== [ { "email": "alice@example.com", "rfmSegment": "champions", "engagementTier": "champion", "openRate": 0.75, "totalRevenue": 1450.00, "avgOrderValue": 120.83, "orderCount": 12, "tags": ["frequent-buyer", "high-aov"], "marketingConsent": true, "unsubscribedAt": null }, { "email": "bob@example.com", "rfmSegment": "new-customers", "engagementTier": "active", "openRate": 0.20, "totalRevenue": 85.00, "avgOrderValue": 85.00, "orderCount": 1, "tags": ["new-subscriber-30d"], "marketingConsent": true, "unsubscribedAt": null }, { "email": "claire@example.com", "rfmSegment": "loyal", "engagementTier": "active", "openRate": 0.18, "totalRevenue": 620.00, "avgOrderValue": 88.57, "orderCount": 7, "tags": ["category-apparel"], "marketingConsent": true, "unsubscribedAt": null }, { "email": "david@example.com", "rfmSegment": "at-risk", "engagementTier": "at-risk", "openRate": 0.10, "totalRevenue": 540.00, "avgOrderValue": 90.00, "orderCount": 6, "tags": ["sale-shopper"], "marketingConsent": true, "unsubscribedAt": null }, { "email": "eva@example.com", "rfmSegment": "cant-lose", "engagementTier": "dormant", "openRate": 0.05, "totalRevenue": 3200.00, "avgOrderValue": 213.33, "orderCount": 15, "tags": ["high-aov", "frequent-buyer"], "marketingConsent": true, "unsubscribedAt": null }, { "email": "frank@example.com", "rfmSegment": "hibernating", "engagementTier": "unengaged", "openRate": 0.01, "totalRevenue": 45.00, "avgOrderValue": 22.50, "orderCount": 2, "tags": [], "marketingConsent": true, "unsubscribedAt": null }, { "email": "grace@example.com", "rfmSegment": "potential-loyal", "engagementTier": "active", "openRate": 0.22, "totalRevenue": 310.00, "avgOrderValue": 103.33, "orderCount": 3, "tags": ["review-submitter"], "marketingConsent": true, "unsubscribedAt": null }, { "email": "henry@example.com", "rfmSegment": "champions", "engagementTier": "champion", "openRate": 0.80, "totalRevenue": 880.00, "avgOrderValue": 97.78, "orderCount": 9, "tags": ["frequent-buyer", "mobile-opener"], "marketingConsent": true, "unsubscribedAt": null } ]
{
"context": "Tests whether the agent implements an RFM scoring pipeline using the correct data model, quintile-based scoring algorithm, score inversion for recency, and the prescribed segment classification logic and labels.",
"type": "weighted_checklist",
"checklist": [
{
"name": "SubscriberProfile data model",
"max_score": 8,
"description": "Defines a SubscriberProfile (or equivalent) type/interface that includes all three RFM-related fields: lastOrderDate (or equivalent recency), orderCount (or equivalent frequency), and totalRevenue (or equivalent monetary)"
},
{
"name": "Engagement fields in model",
"max_score": 7,
"description": "Data model includes openRate computed as totalOpened / totalSent (or equivalent), and clickRate computed as totalClicked / totalSent"
},
{
"name": "Quintile breakpoint computation",
"max_score": 10,
"description": "Scoring uses quintile breakpoints (percentiles at 20%, 40%, 60%, 80%) rather than manually chosen absolute thresholds to split the distribution"
},
{
"name": "R score inversion",
"max_score": 10,
"description": "Recency score is inverted so that a lower number of days since last order yields a higher score (e.g., score 5 for most recent)"
},
{
"name": "Scores on 1-5 scale",
"max_score": 8,
"description": "R, F, and M dimensions are each scored on a 1–5 integer scale"
},
{
"name": "Champions classification rule",
"max_score": 7,
"description": "Segment 'champions' is assigned when r >= 4 AND f >= 4 AND m >= 4"
},
{
"name": "cant-lose classification rule",
"max_score": 7,
"description": "Segment 'cant-lose' (or equivalent label) is assigned for subscribers with low recency score (r <= 2) but medium-high monetary score (m >= 3)"
},
{
"name": "All 8 RFM segment labels present",
"max_score": 8,
"description": "Code defines or uses all 8 segment labels: 'champions', 'loyal', 'potential-loyal', 'new-customers', 'at-risk', 'cant-lose', 'hibernating', 'lost'"
},
{
"name": "marketingConsent filter",
"max_score": 8,
"description": "RFM computation only processes subscribers where marketingConsent is true and unsubscribedAt is null/absent"
},
{
"name": "Currency normalization",
"max_score": 9,
"description": "Monetary values are normalized to a single base currency before computing the M score (e.g., the code handles or documents multi-currency normalization)"
},
{
"name": "Nightly recompute scheduling",
"max_score": 9,
"description": "The solution includes or documents a scheduled/nightly job to recompute RFM scores (e.g., cron, scheduled task, or comment specifying daily refresh)"
},
{
"name": "Bulk upsert output",
"max_score": 9,
"description": "Computed RFM scores are written to a persistent store (database upsert, output file, or returned collection) rather than just logged to console"
}
]
}
Subscriber RFM Scoring Engine
Problem Description
A mid-size ecommerce retailer has grown their email list to 80,000 subscribers across several countries and currencies, but all campaigns currently go to the entire list. Their email deliverability has been declining and their marketing team suspects they are sending irrelevant content to disengaged subscribers while also over-messaging their most loyal customers.
The engineering team has been asked to build a TypeScript module that takes raw subscriber and order data and classifies each subscriber into a meaningful segment based on their purchase behavior. The module will run as a nightly job to keep segments fresh. A key challenge is that the store operates in USD, EUR, and GBP, so revenue figures in the source data may be in different currencies and must be handled consistently before any value-based classification is applied.
The team wants the classification logic captured as reusable TypeScript functions so it can be reviewed, tested, and integrated into their existing Node.js backend. No specific algorithm is mandated in the brief — use whatever approach produces well-calibrated, fair segmentation across the subscriber population.
Output Specification
Produce a TypeScript implementation file rfm-scoring.ts that:
- Defines the subscriber data model as a TypeScript type or interface
- Implements the scoring and segment classification functions
- Exports a main function (e.g.
computeRfmSegments) that processes an array of subscriber records and returns classified results - Includes a brief
README.mdexplaining the scoring approach, how multi-currency input is handled, and how/when the job should be scheduled
The following files are provided as inputs. Extract them before beginning.
=============== FILE: inputs/subscribers.json =============== [ { "email": "alice@example.com", "customerId": "C001", "orderCount": 12, "totalRevenue": 1450.00, "currency": "USD", "lastOrderDate": "2026-02-15", "totalSent": 24, "totalOpened": 18, "totalClicked": 10, "lastOpenDate": "2026-02-20", "lastClickDate": "2026-02-18", "subscribedAt": "2024-01-10", "marketingConsent": true }, { "email": "bob@example.com", "customerId": "C002", "orderCount": 1, "totalRevenue": 85.00, "currency": "USD", "lastOrderDate": "2026-02-28", "totalSent": 5, "totalOpened": 1, "totalClicked": 0, "lastOpenDate": "2026-02-28", "lastClickDate": null, "subscribedAt": "2026-02-01", "marketingConsent": true }, { "email": "claire@example.com", "customerId": "C003", "orderCount": 7, "totalRevenue": 620.00, "currency": "EUR", "lastOrderDate": "2025-09-10", "totalSent": 30, "totalOpened": 6, "totalClicked": 1, "lastOpenDate": "2025-10-01", "lastClickDate": "2025-09-15", "subscribedAt": "2023-06-01", "marketingConsent": true }, { "email": "david@example.com", "customerId": "C004", "orderCount": 3, "totalRevenue": 210.00, "currency": "GBP", "lastOrderDate": "2025-12-01", "totalSent": 15, "totalOpened": 4, "totalClicked": 1, "lastOpenDate": "2025-12-10", "lastClickDate": "2025-12-10", "subscribedAt": "2024-04-20", "marketingConsent": true }, { "email": "eva@example.com", "customerId": "C005", "orderCount": 0, "totalRevenue": 0, "currency": "USD", "lastOrderDate": null, "totalSent": 10, "totalOpened": 0, "totalClicked": 0, "lastOpenDate": null, "lastClickDate": null, "subscribedAt": "2025-01-15", "marketingConsent": true }, { "email": "frank@example.com", "customerId": "C006", "orderCount": 15, "totalRevenue": 3200.00, "currency": "USD", "lastOrderDate": "2024-06-01", "totalSent": 40, "totalOpened": 35, "totalClicked": 20, "lastOpenDate": "2024-06-10", "lastClickDate": "2024-06-10", "subscribedAt": "2022-01-01", "marketingConsent": true }, { "email": "grace@example.com", "customerId": "C007", "orderCount": 2, "totalRevenue": 95.00, "currency": "GBP", "lastOrderDate": "2026-01-20", "totalSent": 8, "totalOpened": 5, "totalClicked": 3, "lastOpenDate": "2026-02-10", "lastClickDate": "2026-02-10", "subscribedAt": "2025-11-01", "marketingConsent": true }, { "email": "henry@example.com", "customerId": "C008", "orderCount": 9, "totalRevenue": 880.00, "currency": "EUR", "lastOrderDate": "2026-03-01", "totalSent": 20, "totalOpened": 16, "totalClicked": 9, "lastOpenDate": "2026-03-05", "lastClickDate": "2026-03-04", "subscribedAt": "2023-09-15", "marketingConsent": true }, { "email": "irene@example.com", "customerId": "C009", "orderCount": 4, "totalRevenue": 320.00, "currency": "USD", "lastOrderDate": "2025-07-14", "totalSent": 22, "totalOpened": 3, "totalClicked": 0, "lastOpenDate": "2025-08-01", "lastClickDate": null, "subscribedAt": "2024-05-10", "marketingConsent": false }, { "email": "james@example.com", "customerId": "C010", "orderCount": 6, "totalRevenue": 540.00, "currency": "USD", "lastOrderDate": "2025-11-20", "totalSent": 18, "totalOpened": 7, "totalClicked": 2, "lastOpenDate": "2025-12-01", "lastClickDate": "2025-11-25", "subscribedAt": "2023-12-01", "marketingConsent": true } ]
{
"name": "finsi/email-list-segmentation",
"version": "0.1.0",
"summary": "Create dynamic email segments based on purchase behavior, RFM scores, engagement signals, and lifecycle stage with automated rebalancing and list hygiene",
"skills": {
"email-list-segmentation": {
"path": "SKILL.md"
}
}
}