skills/customer-crm/customer-lifetime-value/SKILL.md
Calculate and track the net profit value each customer generates, then automate retention strategies for your highest-value segments using platform tools
npx skillsauth add finsilabs/awesome-ecommerce-skills customer-lifetime-valueInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Customer Lifetime Value (CLV) tells you the total net revenue expected from a customer over their relationship with your store, enabling smarter decisions on acquisition spend, retention investment, and customer tier management. Most platforms calculate historical CLV (total spent to date) natively. For predictive CLV and churn risk scoring, use a dedicated analytics app (Klaviyo, Metorik, Triple Whale) or build a custom model for stores with 10k+ customers.
| Platform | Historical CLV | Predictive CLV | |----------|---------------|----------------| | Shopify | Built-in: Admin → Analytics → Customers (shows lifetime spend) | Triple Whale, Lifetimely, or Klaviyo for predictive scoring | | WooCommerce | Metorik or WooCommerce Analytics → Customers report | Metorik Pro for churn prediction; Klaviyo integration for CLV-based flows | | BigCommerce | Built-in customer analytics; Google Analytics 4 for cohort analysis | Klaviyo or Metorik for predictive CLV | | Custom / Headless | Build SQL queries against your order database | Build a parametric or BG/NBD model against your historical data |
Historical CLV is the sum of all revenue from a customer. Platforms show this natively.
Total Spent and Number of Orders per customerMore advanced CLV reporting:
Metorik for deeper analytics:
Once you can measure CLV, create segments to target different value tiers with different messaging.
Customer → Predicted CLV is greater than $500 (Klaviyo calculates predicted CLV automatically for Shopify stores)Customer → Predicted CLV > $200 AND Last order date is more than 90 days agoKlaviyo's predictive analytics (available on paid plans):
Once segments are defined, create automated flows for each tier.
Win-back flow for high-value at-risk customers:
Klaviyo (all platforms):
VIP tier recognition:
vip automatically using Shopify Flow (Plus) or a tagging app; use the tag to show VIP-specific content on the storefrontFor stores building their own CLV calculations:
// lib/clv.ts
// Simple parametric CLV prediction — practical for most stores
export function calculatePredictedCLV(inputs: {
avgOrderValue: number;
avgOrderFrequencyPerYear: number;
avgCustomerLifespanYears: number;
grossMarginRate: number;
}): number {
const { avgOrderValue, avgOrderFrequencyPerYear, avgCustomerLifespanYears, grossMarginRate } = inputs;
return avgOrderValue * avgOrderFrequencyPerYear * avgCustomerLifespanYears * grossMarginRate;
}
// Per-customer prediction from their actual order history
export async function predictCustomerCLV(customerId: string, projectionYears = 2): Promise<number> {
const orders = await db.orders.findMany({
where: { customerId, status: { notIn: ['cancelled', 'refunded'] } },
orderBy: { createdAt: 'asc' },
});
if (orders.length < 2) return 0; // Not enough history
const dates = orders.map(o => o.createdAt.getTime());
const tenureDays = (dates[dates.length - 1] - dates[0]) / 86400000;
const avgIntervalDays = tenureDays / (orders.length - 1);
const purchasesPerYear = 365 / avgIntervalDays;
const aov = orders.reduce((sum, o) => sum + o.subtotalCents / 100, 0) / orders.length;
// Reduce projection for customers who haven't ordered recently
const daysSinceLast = (Date.now() - dates[dates.length - 1]) / 86400000;
const effectiveYears = daysSinceLast < 90 ? projectionYears : projectionYears * 0.5;
return aov * purchasesPerYear * effectiveYears * 0.50; // 50% gross margin
}
// Churn probability based on recency vs. typical purchase cadence
export async function calculateChurnProbability(customerId: string): Promise<number> {
const orders = await db.orders.findMany({
where: { customerId, status: { notIn: ['cancelled', 'refunded'] } },
orderBy: { createdAt: 'desc' },
});
if (orders.length === 0) return 0.95;
if (orders.length === 1) return 0.65;
const daysSinceLast = (Date.now() - orders[0].createdAt.getTime()) / 86400000;
const avgInterval = orders.slice(0, -1).reduce((sum, o, i) =>
sum + (o.createdAt.getTime() - orders[i + 1].createdAt.getTime()) / 86400000, 0) / (orders.length - 1);
// Sigmoid: churn probability rises as recency exceeds 2× typical interval
const recencyRatio = daysSinceLast / avgInterval;
return Math.min(0.99, Math.max(0.01, 1 / (1 + Math.exp(-2 * (recencyRatio - 2)))));
}
// Nightly job: update CLV scores and trigger win-back automation
export async function runChurnPreventionNightly() {
const activeCustomers = await db.customers.findMany({ where: { orderCount: { gte: 2 } } });
for (const customer of activeCustomers) {
const [churnProbability, predictedCLV] = await Promise.all([
calculateChurnProbability(customer.id),
predictCustomerCLV(customer.id),
]);
await db.customers.update({ where: { id: customer.id }, data: { churnProbability, predictedCLV } });
// High-value, high-churn-risk: trigger win-back
if (churnProbability > 0.70 && predictedCLV > 200) {
const alreadyTriggered = await db.winBackTriggers.findFirst({
where: { customerId: customer.id, createdAt: { gte: new Date(Date.now() - 90 * 86400000) } },
});
if (!alreadyTriggered) {
await klaviyo.triggerFlow(customer.email, 'win-back-high-value');
await db.winBackTriggers.create({ data: { customerId: customer.id } });
}
}
}
}
For stores with 100k+ customers: Use the BG/NBD probabilistic model (Python lifetimes library) for significantly more accurate predictions than the parametric approach:
from lifetimes import BetaGeoFitter, GammaGammaFitter
from lifetimes.utils import summary_data_from_transaction_data
# Build RFM summary and fit BG/NBD model
rfm = summary_data_from_transaction_data(orders_df, 'customer_id', 'created_at', 'subtotal_cents')
bgf = BetaGeoFitter(penalizer_coef=0.001)
bgf.fit(rfm['frequency'], rfm['recency'], rfm['T'])
ggf = GammaGammaFitter(penalizer_coef=0.001)
ggf.fit(rfm[rfm['frequency'] > 0]['frequency'], rfm[rfm['frequency'] > 0]['monetary_value'])
rfm['predicted_clv_12mo'] = ggf.customer_lifetime_value(bgf, rfm['frequency'], rfm['recency'], rfm['T'], rfm['monetary_value'], time=12, discount_rate=0.01)
| Problem | Solution |
|---------|----------|
| CLV model inflated by a few very large orders | Use median order value rather than mean AOV for parametric models; single outlier orders skew the mean significantly |
| Win-back emails trigger for customers who took a vacation | Set a minimum "days since last purchase" threshold of 60+ days before triggering win-back; short gaps are normal, not churn signals |
| CLV calculation includes cancelled orders | Always filter status NOT IN ('cancelled', 'refunded') — cancelled orders overstate revenue |
| Predicted CLV lower than historical for loyal customers | In BG/NBD, verify that the tenure variable (T) is measured from first purchase, not account creation date |
tools
Let shoppers save products to a wishlist, share it with friends, and get notified when saved items come back in stock or drop in price
development
Build a themeable storefront with design tokens and CSS custom properties that supports white-labeling, multi-brand variants, and dark mode
development
Speed up product discovery with instant search suggestions, fuzzy typo matching, and category-aware results powered by Algolia or Elasticsearch
development
Build a mobile-first storefront with thumb-friendly navigation, sticky add-to-cart buttons, and touch-optimized components for high mobile conversion