
Churn Prevention
- 567 installs
- 23.5k repo stars
- Updated July 17, 2026
- alirezarezvani/claude-skills
churn-prevention is a Python churn impact calculator skill that models MRR saved from voluntary save-rate and involuntary payment-recovery improvements for developers evaluating subscription retention investments.
About
churn-prevention is a Python 3 churn impact calculator that accepts JSON inputs—MRR, monthly churn rate, voluntary versus involuntary churn split, current and target save rates, and current and target recovery rates—and outputs modeled revenue impact before funding retention programs. The bundled sample uses $50,000 MRR, 4.5% monthly churn, 65% voluntary churn, 8% to 20% save-rate targets, and 15% to 35% recovery targets with $150 average customer MRR. Developers reach for churn-prevention when sizing the dollar value of cancellation saves and failed-payment recovery prior to building churn tooling or lifecycle campaigns.
- Python churn impact calculator with JSON sample inputs for quick what-if runs
- Splits voluntary vs involuntary churn and models current vs target save and recovery rates
- Outputs MRR churned, saves, recoveries, and incremental retained revenue from improvements
- Uses average customer MRR to translate rate changes into dollar impact
- Suitable for prioritizing save offers vs billing recovery work
Churn Prevention by the numbers
- 567 all-time installs (skills.sh)
- Ranked #140 of 853 Sales & Marketing skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
npx skills add https://github.com/alirezarezvani/claude-skills --skill churn-preventionAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 567 |
|---|---|
| repo stars | ★ 23.5k |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 17, 2026 |
| Repository | alirezarezvani/claude-skills ↗ |
How do you model MRR impact of churn reduction?
Model MRR saved by improving voluntary save rates and involuntary payment recovery before you invest in churn programs.
Who is it for?
SaaS developers or product managers quantifying retention-program ROI before investing in save flows or dunning recovery.
Skip if: Implementing actual cancellation UI, payment webhooks, or real-time churn analytics pipelines without scenario modeling.
When should I use this skill?
A user provides MRR, churn percentages, save rates, or recovery rates and asks to estimate revenue impact of churn improvements.
What you get
JSON churn impact projections showing MRR preserved from save-rate and payment-recovery scenario changes.
- Churn impact JSON output
- MRR saved projections
By the numbers
- Sample scenario uses $50,000 MRR and 4.5% monthly churn rate
- Sample models save-rate lift from 8% to 20% and recovery from 15% to 35%
- Sample average customer MRR set at $150
Files
Churn Prevention
You are an expert in SaaS retention and churn prevention. Your goal is to reduce both voluntary churn (customers who decide to leave) and involuntary churn (customers who leave because their payment failed) through smart flow design, targeted save offers, and systematic payment recovery.
Churn is a revenue leak you can plug. A 20% save rate on voluntary churners and a 30% recovery rate on involuntary churners can recover 5-8% of lost MRR monthly. That compounds.
Before Starting
Check for context first: If .claude/product-marketing-context.md exists, read it before asking questions. Use that context and only ask for what's missing.
Gather this context (ask if not provided):
1. Current State
- Do you have a cancel flow today, or is cancellation instant/via support?
- What's your current monthly churn rate? (voluntary vs. involuntary split if known)
- What payment processor are you on? (Stripe, Braintree, Paddle, etc.)
- Do you collect exit reasons today?
2. Business Context
- SaaS model: self-serve or sales-assisted?
- Price points and plan structure
- Average contract length and billing cycle (monthly/annual)
- Current MRR
3. Goals
- Which problem is primary: too many cancellations, or failed payment churn?
- Do you have a save offer budget (discounts, extensions)?
- Any constraints on cancel flow friction? (some platforms penalize dark patterns)
How This Skill Works
Mode 1: Build Cancel Flow
Starting from scratch — no cancel flow exists, or cancellation is immediate. We'll design the full flow from trigger to post-cancel.
Mode 2: Optimize Existing Flow
You have a cancel flow but save rates are low or you're not capturing good exit data. We'll audit what's there, identify the gaps, and rebuild what's underperforming.
Mode 3: Set Up Dunning
Involuntary churn from failed payments is your priority. We'll build the retry logic, notification sequence, and recovery emails.
---
Cancel Flow Design
A cancel flow is not a dark pattern — it's a structured conversation. The goal is to understand why they're leaving and offer something genuinely useful. If they still want to cancel, let them.
The 5-Stage Flow
[Cancel Trigger] → [Exit Survey] → [Dynamic Save Offer] → [Confirmation] → [Post-Cancel]Stage 1 — Cancel Trigger
- Show cancel option clearly (no hiding it — dark patterns burn trust)
- At the moment they click cancel, begin the flow — don't take them to a dead-end form
- Mobile: make this work on touch
Stage 2 — Exit Survey (1 question, required)
- Ask ONE question: "What's the main reason you're cancelling?"
- Keep it multiple choice (6-8 reasons max) — open text is optional, not required
- This answer drives the save offer — it must be collected before showing the offer
Stage 3 — Dynamic Save Offer
- Match the offer to the reason (see Exit Survey → Save Offer Mapping below)
- Don't show a generic discount — it signals your pricing was fake
- One offer per attempt. If they decline, let them cancel.
Stage 4 — Confirmation
- Clear summary of what happens when they cancel (access, data, billing)
- Explicit confirmation button — "Yes, cancel my account"
- No pre-checked boxes, no confusing language
Stage 5 — Post-Cancel
- Immediate confirmation email with: cancellation date, data retention policy, reactivation link
- 7-day re-engagement email: single CTA, no pressure, reactivation link
- 30-day win-back if warranted (product update or relevant offer)
---
Exit Survey Design
The survey is your most valuable data source. Design it to generate usable intelligence, not just categories.
Recommended Reason Categories
| Reason | Save Offer | Signal |
|---|---|---|
| Too expensive / price | Discount or downgrade | Price sensitivity |
| Not using it enough | Usage tips + pause option | Adoption failure |
| Missing a feature | Roadmap share + workaround | Product gap |
| Switching to competitor | Competitive comparison | Market position |
| Project ended / seasonal | Pause option | Temporary need |
| Too complicated | Onboarding help + human support | UX friction |
| Just testing / never needed | No offer — let go | Wrong fit |
Implementation rule: Each reason must map to exactly one save offer type. Ambiguous mapping = generic offer = low save rate.
---
Save Offer Playbook
Match the offer to the reason. Each offer type has a right and wrong time to use it.
| Offer Type | When to Use | When NOT to Use |
|---|---|---|
| Discount (1-3 months) | Price objection | Adoption or feature issues |
| Pause (1-3 months) | Seasonal, project ended, not using | Price objection |
| Downgrade | Too expensive, light usage | Feature objection |
| Extended trial | Hasn't explored full value | Power user churning |
| Feature unlock | Missing feature that exists on higher plan | Wrong plan fit |
| Human support | Complicated, stuck, frustrated | Price objection (don't waste CS time) |
Offer presentation rules:
- One clear headline: "Before you go — [offer]"
- Quantify the value: "Save $X" not "Get a discount"
- No countdown timers unless it's genuinely expiring
- Clear CTA: "Claim this offer" vs. "Continue cancelling"
See references/cancel-flow-playbook.md for full decision trees and flow templates.
---
Involuntary Churn: Dunning Setup
Failed payments cause 20-40% of total churn at most SaaS companies. Most of it is recoverable.
Recovery Stack
1. Smart Retry Logic Don't retry immediately — failed cards often recover within 3-7 days:
- Retry 1: 3 days after failure (most recoveries happen here)
- Retry 2: 5 days after retry 1
- Retry 3: 7 days after retry 2
- Final: 3 days after retry 3, then cancel
2. Card Updater Services
- Stripe: Account Updater (automatic, enabled by default in most plans)
- Braintree: Account Updater (must enable)
- These update expired/replaced cards before the next charge — use them
3. Dunning Email Sequence
| Day | Tone | CTA | |
|---|---|---|---|
| Day 0 | "Payment failed" | Neutral, factual | Update card |
| Day 3 | "Action needed" | Mild urgency | Update card |
| Day 7 | "Account at risk" | Higher urgency | Update card |
| Day 12 | "Final notice" | Urgent | Update card + support link |
| Day 15 | "Account paused/cancelled" | Matter-of-fact | Reactivate |
Email rules:
- Subject lines: specific over vague ("Your [Product] payment failed" not "Action required")
- No guilt. No shame. Card failures happen — treat customers like adults.
- Every email links directly to the payment update page — not the dashboard
See references/dunning-guide.md for full email sequences and retry configuration examples.
---
Metrics & Benchmarks
Track these weekly, review monthly:
| Metric | Formula | Benchmark |
|---|---|---|
| Save rate | Customers saved / cancel attempts | 10-15% good, 20%+ excellent |
| Voluntary churn rate | Voluntary cancels / total customers | <2% monthly |
| Involuntary churn rate | Failed payment cancels / total customers | <1% monthly |
| Recovery rate | Failed payments recovered / total failed | 25-35% good |
| Win-back rate | Reactivations / post-cancel 90 days | 5-10% |
| Exit survey completion | Surveys completed / cancel attempts | >80% |
Red flags:
- Save rate <5% → offers aren't matching reasons
- Exit survey completion <70% → survey is too long or optional
- Recovery rate <20% → retry logic or emails need work
Use the churn impact calculator to model what improving each metric is worth:
python3 scripts/churn_impact_calculator.py---
Proactive Triggers
Surface these without being asked:
- Instant cancellation flow → Revenue is leaking immediately. Any friction saves money — flag for priority fix.
- Single generic save offer → A discount shown to everyone depresses average revenue and trains customers to wait for deals. Map offers to exit reasons.
- No dunning sequence → If payment fails and nothing happens, that's 20-40% of churn going unaddressed. Flag immediately.
- Exit survey is optional → <70% completion = bad data. Make it required (one question, fast).
- No post-cancel reactivation email → The 7-day window is the highest win-back moment. Missing it leaves money on the table.
- Churn rate >5% monthly → At this rate, the company is likely contracting. Churn prevention alone won't fix it — flag for product/ICP review alongside retention work.
---
Output Artifacts
| When you ask for... | You get... |
|---|---|
| "Design a cancel flow" | 5-stage flow diagram (text) with copy for each stage, save offer map, and confirmation email template |
| "Audit my cancel flow" | Scorecard (0-100) with gaps, save rate benchmarks, and prioritized fixes |
| "Set up dunning" | Retry schedule, 5-email sequence with subject lines and body copy, card updater setup checklist |
| "Design an exit survey" | 6-8 reason categories with save offer mapping table |
| "Model churn impact" | Run churn_impact_calculator.py with your inputs — monthly MRR saved and annual impact |
| "Write win-back emails" | 2-email win-back sequence (7-day and 30-day) with subject lines |
---
Communication
All output follows the structured communication standard:
- Bottom line first — save rate estimate or recovery potential before methodology
- What + Why + How — every recommendation has all three
- Actions have owners and deadlines — no vague suggestions
- Confidence tagging — 🟢 verified benchmark / 🟡 estimated / 🔴 assumed
---
Related Skills
- customer-success-manager: Use for health scoring, QBRs, and expansion revenue. NOT for cancel flow or dunning.
- email-sequence: Use for lifecycle nurture and onboarding emails. NOT for dunning (use this skill for dunning).
- pricing-strategy: Use when churn root cause is pricing or packaging mismatch. NOT for save offer design (use this skill).
- campaign-analytics: Use for analyzing which acquisition channels produce high-churn customers. NOT for setting up retention tracking.
- signup-flow-cro: Use for reducing drop-off at signup. NOT for post-signup retention.
Cancel Flow Playbook
Complete reference for designing, building, and auditing cancel flows.
---
The Cancel Flow Decision Tree
Customer clicks "Cancel" or "Cancel Subscription"
│
▼
[Show Exit Survey]
"What's the main reason you're cancelling?"
│
┌────┴─────────────────────────────────────────┐
│ │
Price/Value Other Reasons
│ │
▼ ▼
Discount offer Match to reason category
(1-3 month, 20-30%) (see mapping table)
│ │
▼ ▼
[Accept?]──Yes──► Charge updated [Accept?]──Yes──► Apply offer
│ │
No No
│ │
▼ ▼
[Confirm Cancel] [Confirm Cancel]
│ │
▼ ▼
[Post-cancel page + email] [Post-cancel page + email]---
Stage-by-Stage Templates
Stage 1: Pre-Cancel Intercept
When triggered: User lands on cancel/subscription page, clicks "Cancel plan", or navigates to billing settings.
What to show: Brief value reminder (not a wall of guilt) + "Tell us why" framing.
Copy template:
Headline: Before you go, we want to understand
Body: Your feedback helps us improve. Take 30 seconds to tell us why
you're cancelling — and we might have a solution you haven't tried.
CTA: Continue to cancellation →Rules:
- Don't block the cancel path
- Don't show this more than once per session
- Mobile: single screen, no scrolling required
---
Stage 2: Exit Survey
Design specs:
- Single question, required
- Radio buttons (not checkboxes)
- 6-8 options maximum
- Optional open text at bottom: "Anything else we should know?"
- Submit advances to Stage 3 — don't show offer yet
Copy template:
What's the main reason you're cancelling?
○ It's too expensive for what I get
○ I'm not using it enough to justify the cost
○ It's missing a feature I need
○ I'm switching to a different tool
○ My project or need ended
○ It's too complicated or hard to use
○ I was just testing it out
○ Other: [text field]
[Continue →]Data capture: Store the reason against the customer record. This is your product feedback goldmine.
---
Stage 3: Dynamic Save Offer
Offer-to-reason mapping (full):
| Selected Reason | Primary Offer | Secondary (if declined) | Skip Offer |
|---|---|---|---|
| Too expensive | 30% off for 3 months | Downgrade plan | — |
| Not using it enough | 1-month pause | Usage coaching call | — |
| Missing a feature | Feature roadmap share + workaround | Human support call | If feature genuinely doesn't exist and won't exist soon |
| Switching to competitor | Competitive comparison one-pager | — | If they've clearly made the decision |
| Project ended | 2-month pause | — | — |
| Too complicated | Free onboarding session | 1:1 support call | — |
| Just testing | — | — | Always skip — wrong fit, let them go |
| Other | Human support call | — | — |
Offer presentation template:
[For price objection:]
Headline: Keep [Product] for less
Body: We'd hate to see you go over price. Here's what we can do:
Get 30% off your next 3 months — that's [calculated dollar amount] saved.
After 3 months, your plan returns to [original price].
CTA (accept): Claim my discount →
CTA (decline): No thanks, continue cancelling →
[For not using it enough:]
Headline: No charge for 60 days — pause your account
Body: Life gets busy. Put [Product] on hold for up to 60 days.
Your data stays intact, and you can resume any time. No charge during pause.
CTA (accept): Pause my account →
CTA (decline): No thanks, continue cancelling →Offer rules:
- One offer per cancel attempt — never show multiple
- If they decline, go straight to Stage 4
- Don't re-show the same offer if they return to cancel within 30 days
- Track which offer was shown and whether it was accepted
---
Stage 4: Cancellation Confirmation
What to include:
- Explicit confirmation of what will happen
- Access end date (specific date, not "end of billing period")
- Data retention policy (how long data is kept)
- Support contact in case they change their mind
- Confirmation button with clear copy
Copy template:
Your subscription will be cancelled.
Here's what happens next:
• Access continues until [specific date]
• Your data is retained for 90 days after cancellation
• After 90 days, your account data is deleted
• You can reactivate any time before [90-day date]
If you change your mind, contact [email] or reactivate at [reactivation URL].
[Confirm Cancellation] [Go back]---
Stage 5: Post-Cancel Sequence
Immediate: Cancellation Confirmation Email
Subject: Your [Product] subscription has been cancelled
Hi [Name],
Your [Product] subscription has been cancelled as requested.
What happens next:
- Access continues until [date]
- Your data is saved for 90 days (until [date])
- To reactivate, visit: [reactivation link]
If this was a mistake or you have questions, reply to this email or visit [support link].
[Product] TeamDay 7: Re-engagement Email
Subject: Your [Product] account is still here
Hi [Name],
It's been a week since you cancelled. Your account and data are still intact
until [date].
If anything changed, you can reactivate in one click — no re-setup required.
[Reactivate my account →]
No pressure — just wanted to make sure you knew the door's open.
[Product] TeamDay 30: Win-Back Email (send only if triggered by product update or relevant offer)
Subject: [Product] update: [specific feature they mentioned or relevant improvement]
Hi [Name],
Since you left, we shipped [specific update relevant to their cancel reason].
[2-3 sentence description of what changed.]
If [their specific problem] was why you left, it might be worth another look.
[See what's new →] or [Reactivate →]
[Product] Team---
Cancel Flow Audit Scorecard
Rate your existing flow on each dimension (0-10):
| Dimension | 0 pts | 5 pts | 10 pts |
|---|---|---|---|
| Accessibility | Cancel requires support ticket | Cancel in settings, but buried | Cancel clearly visible in billing settings |
| Exit survey | None | Optional, multi-question | Required, single question, maps to offers |
| Save offers | None or generic discount | Offers exist but not mapped | Offers matched to exit reasons |
| Confirmation clarity | Confusing terms | Mentions access end date | Clear date, data policy, reactivation path |
| Post-cancel sequence | Nothing | One generic email | Immediate confirmation + 7-day re-engagement |
| Dunning | None | Basic retry only | Retry + email sequence + card updater |
| Analytics | No tracking | Basic cancellation count | Reason tracking, save rate, recovery rate |
Score interpretation:
- 60-70: Solid foundation. Fix the 0-5 rated dimensions.
- 40-59: Material revenue leaking. Prioritize survey + offer mapping.
- <40: Major opportunity. Build from scratch using this playbook.
---
Platform Implementation Notes
Stripe
- Use Customer Portal for cancel flow (customizable via Stripe Dashboard)
- Enable Stripe Billing webhooks:
customer.subscription.deleted,invoice.payment_failed - Stripe Radar helps filter bot-initiated failures from real card failures
- Account Updater: enabled by default on most plans — verify in Dashboard > Settings
Chargebee / Recurly
- Both have native cancel flow builders with reason collection
- Dunning sequences configurable in-product
- Connect to your email provider (Intercom, Customer.io, etc.) via webhook
Custom / Homegrown Billing
- Build cancel flow as a separate route (not inline in settings)
- Store
cancel_reason,save_offer_shown,save_offer_acceptedper customer - Retry logic: implement as a background job with delay queue
Dunning Guide
Payment recovery strategies, retry logic, and email sequences for involuntary churn.
---
Why Involuntary Churn Matters
At most SaaS companies, 20-40% of all churn comes from failed payments — not customer decisions. The customer didn't choose to leave. Their card expired, got replaced, hit a limit, or was flagged by their bank. Most of these situations are recoverable within 7-14 days.
The math:
- 1,000 active customers
- 3% monthly churn rate = 30 churned per month
- If 30% of that is involuntary = 9 customers/month from failed payments
- Recovery rate of 40% = 3.6 customers saved/month
- At $100 MRR: $360/month recovered, $4,320/year — from just fixing dunning
That's before touching voluntary churn.
---
Failure Mode Taxonomy
Not all payment failures are equal. Categorize before deciding how to retry:
| Failure Type | Decline Code | Recovery Approach |
|---|---|---|
| Insufficient funds | insufficient_funds | Retry in 3-5 days (balance usually replenishes) |
| Card expired | expired_card | Card updater first; email to update card |
| Card replaced | card_not_supported, network updated | Card updater handles this automatically |
| Do not honor | do_not_honor | Retry once in 3 days; email to contact bank |
| Fraud flagged | fraudulent | Email immediately; don't retry — let customer resolve |
| Card lost/stolen | lost_card, stolen_card | Email immediately; do not retry |
| Generic decline | generic_decline | Retry 2x over 7 days; then email |
Rule: Never retry fraudulent, lost, or stolen card declines. It increases chargeback risk.
---
Retry Schedule
Optimal timing based on card network research:
Day 0: Payment fails (initial charge)
Day 3: Retry 1 — highest recovery rate (3-7 days is the sweet spot)
Day 8: Retry 2 — catches monthly paycycle refills
Day 13: Retry 3 — final automated attempt
Day 16: Cancel subscription (if not recovered)Stripe-specific configuration:
In Stripe Billing settings (Dashboard > Billing > Subscriptions and emails):
Smart Retries: Enable (Stripe uses ML to pick retry timing)
OR
Manual schedule: 3 days, 5 days, 7 days
Subscription behavior after all retries: Cancel subscriptionAlternative for maximum recovery: If using Smart Retries (Stripe), disable manual schedule — they conflict. Smart Retries uses real-time card network data and typically outperforms fixed schedules.
---
Card Updater Services
These services update card details automatically when banks issue new cards:
| Provider | Service Name | Config Required |
|---|---|---|
| Stripe | Account Updater | Enabled by default. Verify in Dashboard > Settings > Card account updater |
| Braintree | Account Updater | Must enable in Control Panel > Processing > Account Updater |
| Recurly | Account Updater | Available on Professional and above |
| Chargebee | Smart Dunning | Bundled with Chargebee; enable in configuration |
Expected impact: 15-25% of involuntary churn prevented before dunning emails are needed.
---
Dunning Email Sequence
Five emails. Each one escalates slightly in urgency. No guilt, no shame — these are operational communications.
---
Email 1 — Day 0: "Payment failed"
Goal: Inform, make it easy to fix.
Subject: Your [Product] payment didn't go through
Hi [Name],
We weren't able to process your [Product] subscription payment of [amount].
This happens sometimes — an expired card, a temporary issue with your bank,
or a card limit. Easy to fix.
Update your payment details here:
[Update payment method →]
If you need help, reply to this email.
[Product] Team
---
Payment amount: [amount]
Billing date: [date]
Next retry: [date + 3 days]Notes:
- Send within 1 hour of failure
- Include specific amount and date — vague emails get ignored
- Mention the next retry date — some customers will wait for the retry to see if it clears
---
Email 2 — Day 3: "Retry coming up"
Goal: Catch people before the retry so they can update the card first.
Subject: [Product] — we'll try your payment again tomorrow
Hi [Name],
We're going to attempt your [Product] payment of [amount] again tomorrow
([specific date]).
If your card details have changed, update them now so the retry goes through:
[Update payment method →]
If the retry fails, we'll reach out again.
[Product] TeamNotes:
- Send day before the retry, not day of
- Short email — one job, one CTA
- Some payment processors let you trigger a manual retry immediately after card update — mention this if yours does
---
Email 3 — Day 7: "We tried again — still failing"
Goal: Add urgency, soften tone, offer help.
Subject: [Product] payment still failing — action needed
Hi [Name],
We've attempted to process your [Product] subscription twice now,
and the payment hasn't gone through.
Your account is still active, but we'll need to resolve this soon to
avoid any interruption.
A few common fixes:
• Check if your card has expired and update it
• Contact your bank if the card is being declined unexpectedly
• Use a different card if this one is no longer working
Update payment details:
[Update payment method →]
Still having trouble? Reply to this email and we'll help you sort it out.
[Product] TeamNotes:
- Shift from notification to problem-solving
- List common causes — helps customers self-diagnose
- Offer human help — some people have legitimate confusion
---
Email 4 — Day 12: "Final notice"
Goal: Create urgency without being threatening. Be clear about what happens.
Subject: [Product] account at risk — payment needed by [specific date]
Hi [Name],
We've made multiple attempts to process your [Product] subscription,
and we haven't been able to reach your card.
Your account will be cancelled on [specific date] if we don't receive payment.
Here's what you'll lose access to:
• [Key feature / data point]
• [Key feature / data point]
• Your [X] months of [data/history/usage]
This is our last reminder before cancellation.
Update payment now:
[Update payment method →]
Need to talk to someone? [Book a call] or reply here.
[Product] TeamNotes:
- Use a specific date — "soon" doesn't create urgency, "March 15" does
- List what they lose — tangible is more motivating than abstract
- Offer human escalation — some churn at this stage is recoverable by a support person
---
Email 5 — Day 16: "Account cancelled"
Goal: Inform, leave the door open, make reactivation easy.
Subject: Your [Product] account has been cancelled
Hi [Name],
We've cancelled your [Product] subscription as of today. Your card could
not be charged for [amount] after multiple attempts.
Your data is saved for 90 days (until [date]).
To reactivate:
[Reactivate my account →]
You'll be able to pick up where you left off — all your data will be intact.
If you think this was an error, reply to this email and we'll sort it out.
[Product] TeamNotes:
- No blame, no guilt — this is a notification, not a scolding
- Make reactivation frictionless — one click, not a new signup flow
- Data retention timeline gives them a reason to act within 90 days
---
Dunning Metrics to Track
| Metric | What it measures | Target |
|---|---|---|
| Recovery rate | Failed payments recovered / total failed | 25-40% |
| Recovery by email | Which email in the sequence converts most | Track per email |
| Recovery by retry | Which retry attempt succeeds most | Usually retry 1 (day 3) |
| Time to recovery | Days from first failure to payment | <10 days is good |
| Card updater hit rate | Cards auto-updated before manual outreach | 15-25% of failures |
---
Third-Party Dunning Tools
For teams who want plug-and-play dunning without building it:
| Tool | Best For | Pricing Model |
|---|---|---|
| Churnkey | Stripe users, full cancel flow + dunning | Revenue share |
| ProfitWell Retain | Stripe + Braintree, analytics-heavy | % of recovered revenue |
| Stunning | Stripe-native, email-focused | Flat monthly |
| Recurly | Already on Recurly | Built-in |
| Chargebee Smart Dunning | Already on Chargebee | Built-in |
When to use a third-party tool: If you're <$500k MRR and don't have engineering bandwidth to build retry logic + email sequences, a tool pays for itself quickly. Above that threshold, build it in-house for more control.
#!/usr/bin/env python3
"""Churn impact calculator — models revenue impact of churn reduction improvements."""
import json
import sys
SAMPLE_INPUT = {
"mrr": 50000,
"monthly_churn_rate_pct": 4.5,
"voluntary_churn_pct": 65,
"current_save_rate_pct": 8,
"target_save_rate_pct": 20,
"current_recovery_rate_pct": 15,
"target_recovery_rate_pct": 35,
"avg_customer_mrr": 150
}
def calculate(inputs):
mrr = inputs["mrr"]
churn_rate = inputs["monthly_churn_rate_pct"] / 100
voluntary_pct = inputs["voluntary_churn_pct"] / 100
involuntary_pct = 1 - voluntary_pct
current_save = inputs["current_save_rate_pct"] / 100
target_save = inputs["target_save_rate_pct"] / 100
current_recovery = inputs["current_recovery_rate_pct"] / 100
target_recovery = inputs["target_recovery_rate_pct"] / 100
avg_customer_mrr = inputs["avg_customer_mrr"]
# Total MRR churned per month
total_churned_mrr = mrr * churn_rate
voluntary_churned_mrr = total_churned_mrr * voluntary_pct
involuntary_churned_mrr = total_churned_mrr * involuntary_pct
# Current saves/recoveries
current_saves_mrr = voluntary_churned_mrr * current_save
current_recoveries_mrr = involuntary_churned_mrr * current_recovery
current_total_saved = current_saves_mrr + current_recoveries_mrr
# Target saves/recoveries
target_saves_mrr = voluntary_churned_mrr * target_save
target_recoveries_mrr = involuntary_churned_mrr * target_recovery
target_total_saved = target_saves_mrr + target_recoveries_mrr
# Incremental gains
incremental_monthly = target_total_saved - current_total_saved
incremental_annual = incremental_monthly * 12
# Customer counts
voluntary_churned_customers = voluntary_churned_mrr / avg_customer_mrr
involuntary_churned_customers = involuntary_churned_mrr / avg_customer_mrr
additional_saves = (target_save - current_save) * voluntary_churned_customers
additional_recoveries = (target_recovery - current_recovery) * involuntary_churned_customers
total_additional_customers = additional_saves + additional_recoveries
# LTV impact (assuming 24-month average tenure at current churn rate)
implied_ltv_months = 1 / churn_rate
ltv_per_customer = avg_customer_mrr * implied_ltv_months
ltv_impact = total_additional_customers * ltv_per_customer
return {
"baseline": {
"mrr": mrr,
"monthly_churn_rate_pct": inputs["monthly_churn_rate_pct"],
"total_churned_mrr_monthly": round(total_churned_mrr, 0),
"voluntary_churned_mrr": round(voluntary_churned_mrr, 0),
"involuntary_churned_mrr": round(involuntary_churned_mrr, 0),
},
"current_performance": {
"save_rate_pct": inputs["current_save_rate_pct"],
"recovery_rate_pct": inputs["current_recovery_rate_pct"],
"monthly_saved_mrr": round(current_total_saved, 0),
"annual_saved_mrr": round(current_total_saved * 12, 0),
},
"target_performance": {
"save_rate_pct": inputs["target_save_rate_pct"],
"recovery_rate_pct": inputs["target_recovery_rate_pct"],
"monthly_saved_mrr": round(target_total_saved, 0),
"annual_saved_mrr": round(target_total_saved * 12, 0),
},
"improvement_impact": {
"incremental_mrr_monthly": round(incremental_monthly, 0),
"incremental_mrr_annual": round(incremental_annual, 0),
"additional_customers_saved_monthly": round(total_additional_customers, 1),
"implied_ltv_per_customer": round(ltv_per_customer, 0),
"ltv_impact_of_saved_customers": round(ltv_impact, 0),
},
"priorities": _prioritize(
voluntary_churned_mrr, involuntary_churned_mrr,
current_save, target_save,
current_recovery, target_recovery
)
}
def _prioritize(vol_mrr, inv_mrr, cur_save, tgt_save, cur_rec, tgt_rec):
save_opportunity = vol_mrr * (tgt_save - cur_save)
rec_opportunity = inv_mrr * (tgt_rec - cur_rec)
if save_opportunity > rec_opportunity * 1.5:
primary = "cancel-flow-and-save-offers"
secondary = "dunning"
elif rec_opportunity > save_opportunity * 1.5:
primary = "dunning-and-payment-recovery"
secondary = "cancel-flow"
else:
primary = "both-roughly-equal"
secondary = "start-with-dunning-easier-to-implement"
return {
"voluntary_save_opportunity_mrr": round(save_opportunity, 0),
"involuntary_recovery_opportunity_mrr": round(rec_opportunity, 0),
"recommendation": primary,
"note": secondary
}
def print_report(result):
b = result["baseline"]
cur = result["current_performance"]
tgt = result["target_performance"]
imp = result["improvement_impact"]
pri = result["priorities"]
print("\n" + "="*60)
print(" CHURN IMPACT CALCULATOR")
print("="*60)
print(f"\n📊 BASELINE")
print(f" MRR: ${b['mrr']:,.0f}")
print(f" Monthly churn rate: {b['monthly_churn_rate_pct']}%")
print(f" Total MRR churned/mo: ${b['total_churned_mrr_monthly']:,.0f}")
print(f" └─ Voluntary: ${b['voluntary_churned_mrr']:,.0f}")
print(f" └─ Involuntary: ${b['involuntary_churned_mrr']:,.0f}")
print(f"\n📉 CURRENT PERFORMANCE")
print(f" Save rate: {cur['save_rate_pct']}%")
print(f" Payment recovery rate: {cur['recovery_rate_pct']}%")
print(f" MRR saved monthly: ${cur['monthly_saved_mrr']:,.0f}")
print(f" MRR saved annually: ${cur['annual_saved_mrr']:,.0f}")
print(f"\n🎯 TARGET PERFORMANCE")
print(f" Save rate: {tgt['save_rate_pct']}%")
print(f" Payment recovery rate: {tgt['recovery_rate_pct']}%")
print(f" MRR saved monthly: ${tgt['monthly_saved_mrr']:,.0f}")
print(f" MRR saved annually: ${tgt['annual_saved_mrr']:,.0f}")
print(f"\n💰 INCREMENTAL IMPACT")
print(f" Additional MRR/month: ${imp['incremental_mrr_monthly']:,.0f}")
print(f" Additional MRR/year: ${imp['incremental_mrr_annual']:,.0f}")
print(f" Customers saved/month: {imp['additional_customers_saved_monthly']}")
print(f" Implied LTV/customer: ${imp['implied_ltv_per_customer']:,.0f}")
print(f" LTV impact: ${imp['ltv_impact_of_saved_customers']:,.0f}")
print(f"\n🔍 PRIORITY RECOMMENDATION")
print(f" Voluntary opportunity: ${pri['voluntary_save_opportunity_mrr']:,.0f}/mo")
print(f" Involuntary opportunity: ${pri['involuntary_recovery_opportunity_mrr']:,.0f}/mo")
print(f" Focus on: {pri['recommendation'].replace('-', ' ').title()}")
if pri['note']:
print(f" Note: {pri['note'].replace('-', ' ')}")
print("\n" + "="*60 + "\n")
def main():
import argparse
parser = argparse.ArgumentParser(
description="Churn impact calculator — models revenue impact of churn reduction improvements."
)
parser.add_argument(
"input_file", nargs="?", default=None,
help="JSON file with churn metrics (default: run with sample data)"
)
parser.add_argument(
"--json", action="store_true",
help="Output results as JSON"
)
args = parser.parse_args()
if args.input_file:
with open(args.input_file) as f:
inputs = json.load(f)
else:
print("No input file provided. Running with sample data...\n")
print("Sample input:")
print(json.dumps(SAMPLE_INPUT, indent=2))
inputs = SAMPLE_INPUT
result = calculate(inputs)
print_report(result)
if args.json:
print(json.dumps(result, indent=2))
if __name__ == "__main__":
main()
Related skills
FAQ
What inputs does churn-prevention require?
churn-prevention accepts JSON with MRR, monthly_churn_rate_pct, voluntary_churn_pct, current and target save_rate_pct, current and target recovery_rate_pct, and avg_customer_mrr to project retained revenue.
Does churn-prevention separate voluntary and involuntary churn?
Yes. churn-prevention splits churn into voluntary and involuntary portions, modeling save-rate gains on cancellations and recovery-rate gains on failed payments independently.
Is Churn Prevention safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.