
Customer Segmentation
- 66 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Segment customers by RFM, recency, and spend using Klaviyo or custom SQL to power targeted campaigns and ad suppression lists.
About
Builds RFM-style behavioral segments (champions, at-risk, lapsed) via platform tools or custom SQL scoring synced to an ESP. A developer uses it to personalize campaigns, build suppression lists, or find VIP customers.
- Key Klaviyo segment recipes for champions, at-risk, and first-time buyers
- PostgreSQL NTILE RFM scoring plus Klaviyo/Meta suppression-list sync code
Customer Segmentation by the numbers
- 66 all-time installs (skills.sh)
- Ranked #882 of 2,064 Data Science & ML 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 customer-segmentationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 66 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Segment customers by RFM, recency, and spend using Klaviyo or custom SQL to power targeted campaigns and ad suppression lists.
Files
Customer Segmentation
Overview
Customer segmentation divides your customer base into groups with similar purchase behavior so marketing campaigns, promotions, and product recommendations can be precisely targeted. Klaviyo, Omnisend, and Metorik all provide RFM-style segmentation out of the box for Shopify and WooCommerce without custom SQL. Only build a custom segmentation system if your platform's tools don't support the segment logic you need.
When to Use This Skill
- When personalizing email campaigns by lifecycle stage (new, active, at-risk, lapsed)
- When building suppression lists to avoid wasting ad spend on already-converted customers
- When identifying "champion" customers for VIP programs and early access campaigns
- When analyzing which acquisition cohort has the best 90-day retention
- When syncing behavioral segments to advertising platforms (Meta, Google)
Core Instructions
Step 1: Determine platform and choose the right segmentation tool
| Platform | Built-in Segmentation | Recommended Tool |
|---|---|---|
| Shopify | Basic: Admin → Customers → Filters; Advanced: Klaviyo or Omnisend | Klaviyo for email + SMS; Lifetimely for cohort analysis |
| WooCommerce | WooCommerce Analytics → Customers (basic filters) | Klaviyo + WooCommerce plugin; or Metorik for analytics |
| BigCommerce | Customer Groups (tier-based); Analytics → Customers | Klaviyo for behavioral segmentation |
| Custom / Headless | Build RFM scoring in SQL; sync to Klaviyo for activation | Required when platform has no segmentation tools |
---
Step 2: Platform-specific setup
---
Shopify
Option A: Shopify Admin segments (basic, free)
1. Go to Admin → Customers 2. Use the filter bar to create segments based on:
- Order count, total spent, last order date
- Email subscription status, tags, location
- Product purchased
3. Save the filter as a customer segment 4. Export the segment to CSV for use in ads or email campaigns
Option B: Klaviyo (recommended for lifecycle segmentation)
Klaviyo syncs automatically with Shopify and provides RFM-style segmentation built on real purchase data.
Key segments to create in Klaviyo:
1. Champions (high-value, frequent, recent):
- Rule:
Ordered at least 3 timesANDLast order within 60 daysANDTotal spent > $200 - Action: Early access to new products, VIP perks, referral program invites
2. At Risk — High Value:
- Rule:
Total spent > $200ANDLast order 90–180 days ago - Action: Win-back flow with personalized offer
3. Recent First-Time Buyers:
- Rule:
Order count equals 1ANDFirst order within 30 days - Action: Onboarding sequence, encourage second purchase
4. Lapsed Customers:
- Rule:
Last order more than 180 days agoANDOrder count >= 2 - Action: Re-engagement campaign with "We've missed you" messaging
5. Subscribers who never purchased:
- Rule:
Email subscriberANDOrder count equals 0 - Action: Welcome series with social proof and first-order incentive
To create segments in Klaviyo: 1. Go to Lists & Segments → Create Segment 2. Add conditions using the filter builder — Klaviyo has 150+ pre-built filter types including Shopify-specific events 3. Name the segment clearly (e.g., "At Risk High Value — 90-180 days") 4. Use the segment in a Flow trigger or as an audience for a Campaign
Klaviyo Predictive Analytics (paid plans):
- Klaviyo automatically calculates Predicted CLV, Expected Date of Next Order, and Churn Risk per customer
- Find these under Analytics → Predictive Analytics
- Use these predictions as segment filter criteria without any custom code
---
WooCommerce
Option A: Metorik (recommended analytics platform)
1. Install Metorik (connects via WooCommerce REST API) 2. Go to Metorik → Segments → Create Segment 3. Build rule-based segments using purchase history, product affinity, geography, and more 4. Metorik calculates RFM scores automatically 5. Export segment to CSV or sync directly to Klaviyo/Mailchimp
Option B: Klaviyo for WooCommerce
1. Install Klaviyo: Email Marketing for WooCommerce from WordPress.org 2. Klaviyo syncs your WooCommerce order history and creates profiles for all customers 3. Build the same lifecycle segments described in the Shopify section above
---
BigCommerce
Customer Groups for tier-based segmentation:
1. Go to Customers → Customer Groups → Add Group 2. Create groups based on purchase behavior rules:
- "VIP" — customers with lifetime spend > $500
- "Repeat Buyers" — customers with 3+ orders
3. Assign group-specific pricing, category access, or shipping rules
Klaviyo for behavioral segmentation:
- Install the Klaviyo for BigCommerce app
- Connect your BigCommerce store
- Build behavioral segments in Klaviyo using purchase history and event data
---
Custom / Headless
Build RFM scoring in SQL and sync to an ESP for activation:
-- PostgreSQL: Calculate RFM scores for all customers
WITH customer_rfm AS (
SELECT
customer_id,
EXTRACT(EPOCH FROM (NOW() - MAX(created_at))) / 86400 AS recency_days,
COUNT(id) AS frequency,
SUM(subtotal_cents) / 100.0 AS monetary
FROM orders
WHERE status NOT IN ('cancelled', 'refunded')
GROUP BY customer_id
),
rfm_scored AS (
SELECT
customer_id,
recency_days, frequency, monetary,
NTILE(5) OVER (ORDER BY recency_days DESC) AS r_score, -- Lower recency = higher score
NTILE(5) OVER (ORDER BY frequency ASC) AS f_score,
NTILE(5) OVER (ORDER BY monetary ASC) AS m_score
FROM customer_rfm
)
SELECT
customer_id, r_score, f_score, m_score,
r_score + f_score + m_score AS rfm_total,
CASE
WHEN r_score >= 4 AND f_score >= 4 AND m_score >= 4 THEN 'champions'
WHEN r_score >= 3 AND f_score >= 3 AND m_score >= 3 THEN 'loyal_customers'
WHEN r_score >= 4 AND f_score <= 2 THEN 'recent_customers'
WHEN r_score <= 2 AND f_score >= 4 AND m_score >= 4 THEN 'cannot_lose_them'
WHEN r_score <= 2 AND f_score >= 3 THEN 'at_risk'
WHEN r_score = 1 AND f_score <= 2 THEN 'lost'
ELSE 'needs_attention'
END AS segment
FROM rfm_scored;// Sync a segment to Klaviyo — batched at 100 profiles per request
export async function syncSegmentToKlaviyo(segmentCustomers: Customer[], klaviyoListId: string) {
const BATCH_SIZE = 100;
for (let i = 0; i < segmentCustomers.length; i += BATCH_SIZE) {
const batch = segmentCustomers.slice(i, i + BATCH_SIZE);
await fetch(`https://a.klaviyo.com/api/lists/${klaviyoListId}/relationships/profiles/`, {
method: 'POST',
headers: {
Authorization: `Klaviyo-API-Key ${process.env.KLAVIYO_PRIVATE_KEY}`,
'Content-Type': 'application/json',
revision: '2024-10-15',
},
body: JSON.stringify({
data: batch.map(c => ({
type: 'profile',
attributes: { email: c.email, first_name: c.firstName, last_name: c.lastName },
})),
}),
});
}
}
// Export suppression list for Meta Ads — hash emails before sending
export async function exportSuppressionListForMeta(lookbackDays = 30): Promise<string[]> {
const recentBuyers = await db.orders.findMany({
where: { createdAt: { gte: new Date(Date.now() - lookbackDays * 86400000) }, status: 'completed' },
select: { customerEmail: true },
distinct: ['customerEmail'],
});
return recentBuyers.map(({ customerEmail }) => {
const { createHash } = require('crypto');
return createHash('sha256').update(customerEmail.toLowerCase().trim()).digest('hex');
});
}---
Step 3: Map segments to actions
Every segment should have a clear marketing action:
| Segment | Recommended Action |
|---|---|
| Champions | VIP early access, referral program invite, no win-back discounts needed |
| Loyal Customers | Loyalty program enrollment, review request, upsell to next tier |
| Recent First-Time Buyers | Second-purchase email series (send 7 days after first order) |
| At Risk — High Value | Personalized win-back with acknowledgment: "It's been a while" |
| Cannot Lose Them | Direct outreach, significant offer (20% off), personal email from founder |
| Lapsed / Lost | Low-cost re-engagement (newsletter, product announcement); remove from active campaigns |
---
Step 4: Build suppression lists for paid ads
Upload your most recent buyers as suppression lists on Meta and Google to avoid wasting acquisition budget:
- Meta Business Manager: Audiences → Create Audience → Customer List → Upload hashed email CSV
- Google Ads: Audience Manager → Customer Match → Upload email list
- Refresh these suppression lists monthly
Best Practices
- Refresh segments nightly — Klaviyo and Metorik do this automatically; for custom builds, run the RFM SQL nightly
- Start with RFM, then layer behavioral signals — purchase recency/frequency/spend is the most reliable foundation; add category affinity and channel preference as you collect more data
- Always build suppression lists alongside targeting lists — sending re-engagement campaigns to active customers wastes budget and annoys them
- Validate segment sizes before campaign sends — a segment returning 0 customers usually indicates a logic error; set a minimum threshold check
- Track which segments respond best to each type of offer — champions rarely need discounts; at-risk customers may respond to free shipping more than percentage off
Common Pitfalls
| Problem | Solution |
|---|---|
| Champions segment shrinks every month | Champions require recent + high frequency — customers naturally graduate out; supplement with "Loyal Customers" for long-term relationship management |
| RFM scores biased by one very large order | Klaviyo's predicted CLV smooths this out; for custom builds, use median order value in the monetary score rather than total spend |
| Segment sync to Klaviyo creates duplicate profiles | Always match by email as the primary key when syncing; if duplicates exist, use Klaviyo's profile merge |
| Cohort analysis shows declining retention but reason unclear | Segment cohort by acquisition channel to identify whether specific channels bring lower-quality customers |
Related Skills
- @customer-lifetime-value
- @personalization-engine
- @referral-program
{
"context": "Tests whether the agent implements cohort retention SQL using monthly truncation with the correct milestone periods, builds a behavioral rules engine with all three rule types and correct operator semantics, uses parallel rule evaluation, and recommends the correct index strategy.",
"type": "weighted_checklist",
"checklist": [
{
"name": "DATE_TRUNC for cohort month",
"max_score": 10,
"description": "cohort_analysis.sql uses DATE_TRUNC('month', ...) to define the cohort month (not EXTRACT, not date casting alone)"
},
{
"name": "Four retention milestones",
"max_score": 10,
"description": "The SQL tracks retention at exactly periods 1, 3, 6, and 12 months (all four must be present as separate output columns)"
},
{
"name": "Exclude invalid orders",
"max_score": 8,
"description": "The cohort SQL excludes orders with status 'cancelled' AND 'refunded' (both statuses must be filtered out)"
},
{
"name": "Event rule type",
"max_score": 10,
"description": "The TypeScript segment rule type includes an 'event' variant with at least an event name, a count comparison (op + value), and a time window in days"
},
{
"name": "Property rule type",
"max_score": 8,
"description": "The TypeScript segment rule type includes a 'property' variant with a field name, comparison operator, and value"
},
{
"name": "Segment membership rule type",
"max_score": 8,
"description": "The TypeScript segment rule type includes a 'segment' variant that checks whether a customer is (or is not) in another segment"
},
{
"name": "AND/OR operator support",
"max_score": 8,
"description": "The BehavioralSegment type includes an 'operator' field accepting both 'AND' and 'OR' values"
},
{
"name": "Promise.all parallel evaluation",
"max_score": 10,
"description": "evaluateBehavioralSegment uses Promise.all (not sequential awaits) to evaluate all rules concurrently"
},
{
"name": "Correct operator semantics",
"max_score": 8,
"description": "The AND operator uses .every(Boolean) and the OR operator uses .some(Boolean) on the results array"
},
{
"name": "Composite index recommendation",
"max_score": 10,
"description": "db_recommendations.md recommends a composite index on the customer_events table covering customer_id, event, and created_at (all three columns)"
},
{
"name": "Pre-materialization recommendation",
"max_score": 8,
"description": "db_recommendations.md recommends pre-materializing segment membership (e.g. in a nightly job or materialized view) as a performance strategy"
},
{
"name": "AGE() for period calculation",
"max_score": 2,
"description": "cohort_analysis.sql uses AGE() or DATE_PART('month', AGE(...)) to compute the period number (months since cohort), not a simple date subtraction"
}
]
}
Customer Retention Analytics and Behavioral Targeting System
Problem Description
UrbanThreads, a direct-to-consumer apparel brand, has been running for four years and recently raised a Series A. Their new Head of Growth needs two things to plan the next marketing push: (1) a clear picture of how customers acquired in different months are retaining over their first year, and (2) a flexible system for defining targeting segments based on customer behavior and properties — so the team can create segments like "customers who viewed product pages 3+ times in the last 14 days but haven't purchased" without waiting on an engineer.
The engineering team uses PostgreSQL. The orders table has columns: customer_id, id, created_at (timestamp), status ('completed', 'cancelled', 'refunded', 'pending'), subtotal_cents. The customer_events table has columns: customer_id, event (string event name), created_at (timestamp). The customers table has columns: id, email, acquisition_channel, plan, and other profile properties.
For the retention analysis, the team wants a single SQL query they can run in their BI tool showing each acquisition cohort alongside the number of customers still purchasing at defined retention milestones.
For the behavioral segment system, the team wants a TypeScript module that evaluates whether a given customer belongs to a segment, where segments are defined as a list of rules with a combining operator. The system should handle checking event counts, customer property comparisons, and membership in other segments. Performance matters — the platform has millions of events per day — and the engineering lead wants any database recommendations documented in a db_recommendations.md file.
Output Specification
Produce the following files:
1. cohort_analysis.sql — A complete SQL query against the orders table that shows cohort retention. For each acquisition cohort, output: cohort_month, cohort_size, and columns showing how many customers from that cohort made additional purchases at each defined retention milestone. Order results with most recent cohorts first.
2. behavioral_segments.ts — A TypeScript module containing:
- Type definitions for a segment and its rules, supporting rules based on events, customer properties, and other segment membership
- An
evaluateBehavioralSegment(customerId, segment)async function that evaluates all rules for a customer and returns a boolean - The function should handle the segment operator (how rules are combined)
3. db_recommendations.md — A markdown document listing database optimizations recommended when using this behavioral segment system at scale, with the specific index/approach for the events table.
{
"context": "Tests whether the agent correctly implements RFM scoring using quantile-based scoring (NTILE), excludes invalid orders, applies the correct segment classification thresholds, and persists scores using an upsert pattern with nightly refresh.",
"type": "weighted_checklist",
"checklist": [
{
"name": "NTILE quantile scoring",
"max_score": 10,
"description": "The SQL uses NTILE(5) window functions to compute r_score, f_score, and m_score — NOT fixed numeric thresholds or CASE WHEN ranges"
},
{
"name": "Exclude cancelled/refunded",
"max_score": 8,
"description": "The SQL WHERE clause excludes orders with status 'cancelled' AND 'refunded' (both must be excluded)"
},
{
"name": "Recency scored inversely",
"max_score": 8,
"description": "NTILE for recency orders by recency_days DESC (so more recent customers get higher r_score, not lower)"
},
{
"name": "rfm_cell as concatenation",
"max_score": 8,
"description": "The SQL outputs rfm_cell as the concatenation of the three scores (e.g., CONCAT(r_score, f_score, m_score))"
},
{
"name": "Eleven named segments",
"max_score": 10,
"description": "The TypeScript segment type includes all of: champions, loyal_customers, potential_loyalists, recent_customers, promising, need_attention, about_to_sleep, at_risk, cannot_lose_them, hibernating, lost (all 11)"
},
{
"name": "Champions threshold",
"max_score": 8,
"description": "classifyRFMSegment returns 'champions' when r>=4, f>=4, AND m>=4"
},
{
"name": "cannot_lose_them threshold",
"max_score": 8,
"description": "classifyRFMSegment returns 'cannot_lose_them' when r<=2, f>=4, AND m>=4"
},
{
"name": "at_risk classification",
"max_score": 8,
"description": "classifyRFMSegment returns 'at_risk' when r<=2 AND f>=3 (distinct from cannot_lose_them which also requires m>=4)"
},
{
"name": "Upsert not insert",
"max_score": 8,
"description": "refreshRFMScores() uses an upsert operation (upsert, ON CONFLICT, or equivalent) rather than a plain insert, so existing records are updated"
},
{
"name": "rfm_total as sum",
"max_score": 8,
"description": "The SQL computes rfm_total as r_score + f_score + m_score"
},
{
"name": "Hibernating vs lost distinction",
"max_score": 8,
"description": "classifyRFMSegment distinguishes 'hibernating' (r=1, f<=2) from 'lost' (fallback for remaining cases) as two separate segments"
},
{
"name": "Segment summary with actions",
"max_score": 8,
"description": "getSegmentSummary() or SEGMENT_ACTIONS includes a recommendedAction field mapping at least 3 segment names to specific marketing actions"
}
]
}
Customer Scoring System for E-Commerce Analytics
Problem Description
GreenLeaf Commerce, an online plant and gardening retailer, has grown to over 50,000 customers over the past three years. Their marketing team currently sends the same promotional emails to all customers and is burning significant budget on customers who either just purchased last week or haven't purchased in over a year. The CTO has asked you to build a customer scoring and classification system that will allow the marketing team to target different groups of customers with appropriate messages.
The company uses PostgreSQL and their orders table has the following schema: customer_id, id (order id), created_at (timestamp), subtotal_cents (integer), status (string, e.g. 'completed', 'cancelled', 'refunded', 'pending'). They want to score every customer based on their purchase history and automatically categorize them so campaigns can be tailored by customer lifecycle stage. The scoring should work correctly even as the customer base grows or spending patterns shift over time.
The engineering team will schedule this scoring to run automatically, and they need a TypeScript function that reads the computed scores and assigns each customer to a named lifecycle segment. A CRM dashboard will display a summary of all segments with counts and average monetary scores, along with a recommended action per segment.
Output Specification
Produce the following files:
1. rfm_scoring.sql — A complete SQL query that computes per-customer RFM scores (recency, frequency, monetary) using the orders table. The query should output: customer_id, r_score, f_score, m_score, rfm_total, rfm_cell.
2. rfm_classifier.ts — A TypeScript module containing:
- A TypeScript type for all possible segment names
- A
classifyRFMSegment(r, f, m)function that maps scores to a segment name - A
refreshRFMScores()async function that runs the scoring query and persists the results (assume adbobject is available as shown in examples) - A
getSegmentSummary()function that returns segment counts, average monetary score, and recommended action per segment
3. SEGMENT_ACTIONS.md — A markdown table mapping each segment name to the recommended marketing action for that segment.
{
"context": "Tests whether the agent implements Klaviyo segment sync with correct batching, API versioning, profile field mapping, and authentication format; applies SHA-256 email hashing for Meta; creates suppression lists; validates segment sizes; and documents Klaviyo deduplication handling.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Klaviyo batch size 100",
"max_score": 10,
"description": "syncSegmentToKlaviyo splits customers into chunks/batches of 100 per request (NOT larger batches like 1000 or unbounded)"
},
{
"name": "API revision header",
"max_score": 10,
"description": "The Klaviyo API request includes a 'revision' header with value '2024-02-15'"
},
{
"name": "Klaviyo-API-Key auth format",
"max_score": 8,
"description": "The Authorization header uses the format 'Klaviyo-API-Key <key>' (NOT 'Bearer', NOT 'Token')"
},
{
"name": "Profile fields: four attributes",
"max_score": 10,
"description": "The Klaviyo profile payload includes all four attributes: email, first_name, last_name, and phone_number (all must be present)"
},
{
"name": "SHA-256 email hashing",
"max_score": 10,
"description": "exportSuppressionListForMeta applies SHA-256 hashing to email addresses before returning them"
},
{
"name": "Email normalization before hash",
"max_score": 8,
"description": "The email is normalized (lowercased AND trimmed) before hashing, not hashed as-is"
},
{
"name": "Suppression list created",
"max_score": 10,
"description": "There is a separate suppression list export (recent purchasers excluded from acquisition campaigns) distinct from the targeting list sync"
},
{
"name": "Segment size validation",
"max_score": 8,
"description": "klaviyo_sync.ts includes a guard or check for when a segment has 0 customers, preventing an empty/no-op sync from proceeding silently"
},
{
"name": "Klaviyo profiles endpoint",
"max_score": 8,
"description": "The Klaviyo API call targets the relationships/profiles sub-resource of the lists API (endpoint contains 'relationships/profiles')"
},
{
"name": "Email as dedup primary key",
"max_score": 8,
"description": "klaviyo_runbook.md states that email should be used as the primary key for profile matching/deduplication in Klaviyo"
},
{
"name": "Profile merge API mentioned",
"max_score": 5,
"description": "klaviyo_runbook.md mentions Klaviyo's profile merge API as the remedy for existing duplicate profiles"
},
{
"name": "KLAVIYO_PRIVATE_KEY env var",
"max_score": 5,
"description": "The API key is read from process.env.KLAVIYO_PRIVATE_KEY (not hardcoded, not a different env var name)"
}
]
}
Marketing Platform Sync and Audience Management
Problem Description
PeakPulse Athletics, an activewear e-commerce brand, is scaling up their paid and owned marketing. Their marketing operations team needs to connect their customer segmentation system to two external platforms: Klaviyo (for lifecycle email campaigns) and Meta (for paid social advertising). Right now, the team manually exports CSVs and uploads them — a process that's error-prone and runs once a week at best.
The engineering team has a database with a customerSegmentMemberships table that stores which customers belong to which segments (with a segmentId and customerId), a customers table with fields: id, email, firstName, lastName, phone, and an orders table with customer_id, created_at, status ('completed', 'cancelled', 'refunded', 'pending'). Each marketing segment in Klaviyo corresponds to a Klaviyo List ID.
One critical requirement: the team wastes significant ad budget on customers who already purchased recently. The Meta export must handle privacy requirements correctly. The engineering lead also warned that when the team previously synced to Klaviyo, duplicate customer profiles appeared — this must be documented with a recommended fix.
The team wants two TypeScript modules implementing the sync logic, plus a runbook explaining the duplicate profile issue and how to resolve it.
Output Specification
Produce the following files:
1. klaviyo_sync.ts — A TypeScript module with a syncSegmentToKlaviyo(segmentId, klaviyoListId) function that:
- Fetches all customer IDs in the segment and their profile data
- Syncs the customers to the specified Klaviyo list
- Uses the correct Klaviyo API endpoint structure and authentication format
- Uses
process.env.KLAVIYO_PRIVATE_KEYfor the API key - Includes logic to handle the case where a segment has 0 members (guard against empty syncs)
2. meta_suppression.ts — A TypeScript module with an exportSuppressionListForMeta() function that:
- Exports recent purchasers (last 30 days, completed orders only) as a suppression list
- Applies the required privacy transformation to email addresses before export
- Returns an array of objects suitable for Meta Custom Audiences
3. klaviyo_runbook.md — A markdown document explaining the duplicate profile problem that can occur when syncing to Klaviyo and the recommended approach to resolve it, including which field should be used as the primary key.
{
"name": "finsi/customer-segmentation",
"version": "0.1.0",
"summary": "RFM analysis, behavioral segments, and cohort-based targeting",
"skills": {
"customer-segmentation": {
"path": "SKILL.md"
}
}
}