"Dunning" is the polite word for chasing failed payments. The B2B SaaS variant is harder than B2C because the buyer (Finance) and the user (Engineering) are usually different people, the spend is bigger, and "we'll just churn" isn't an option for the customer's team — they need the service to keep working. Here's what works, what doesn't, and the cadence to use.
For consumer subscriptions, dunning is roughly: card fails → email user → user updates card → recovered. The user has total agency.
For B2B SaaS, dunning routes through several real-world steps:
The whole cycle takes 3–7 business days, not 3–7 hours. Your email cadence has to respect that.
| Day | Action | Recipient | Tone |
|---|---|---|---|
| Day 0 (charge fails) | Internal log only — don't email yet | — | — |
| Day 0 (Stripe Smart Retries window) | Stripe retries; no email needed yet | — | — |
| Day +1 (next morning) | Email #1: "Heads up — your last payment didn't go through" | Billing contact | Helpful, low-pressure. Card-update link. |
| Day +3 | Email #2: "Quick reminder — card-update link" | Billing contact + CC technical contact | Slightly firmer. Mention service continuity. |
| Day +5 | Email #3: "Service paused in 48 hours unless we hear back" | All contacts | Direct. Show date the service degrades. Slack to high-value customers. |
| Day +7 | Email #4: "Service paused" + offer alternatives (ACH, wire, deferred billing) | All contacts + Customer Success rep | Final notice. Recovery rate for charges still unrecovered after this point is <15%. |
| Day +14 | Cancellation | — | — |
Variants for plan size:
The B2B billing contact reads ~50 of these emails a week. The ones that get read:
billing@yourcompany.com. Failed-payment emails from no-reply addresses get marked as spam more often.Stripe Smart Retries hasn't run yet. Half the failures recover within 24 hours from the retry alone — your email at hour 0 just creates noise and a customer "did you get my payment? I just checked, the card has money."
If the customer logs in and finds the dashboard greyed out without warning, you've created a support ticket and damaged the relationship. The day-5 email needs to be explicit: "Service will pause [date] unless we hear back." Then the day-7 email confirms the suspension actually happened.
Retrying a card 8+ times triggers issuer fraud-flags and damages your Stripe account's risk score. Two retries (24h, 72h) is sufficient for most codes; beyond that, the customer-facing flow does the work.
fraudulent is not insufficient_funds. Retrying a fraud-flagged card aggressively makes things worse — the issuer now thinks you're the bad actor. Recovery rates by decline code covers what to do per code.
A $99/month customer and a $5,000/month customer get the same form letter? The latter should get an email signed by their actual CSM, not the billing template. The line is around $500/mo or so — above that, manual touch on day 3 is meaningfully more recovery.
The free CLI generates per-decline-code email templates that you can customise and pipe into your existing email tooling (Postmark, Resend, SES). The hosted tier connects directly to your Stripe and runs the cadence above without you having to wire it up. Apache 2.0 on GitHub; pip install dunningkit on PyPI.