DunningKit

← All articles · 7 min read · 2026-05

Dunning emails for B2B SaaS: timing, tone, anti-patterns

"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.

The B2B vs B2C difference

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:

  1. Stripe charge fails (Tuesday)
  2. Your dunning email goes to the billing contact, who is in Finance, not Engineering (Tuesday)
  3. Finance forwards to AP, who has a queue (Wednesday)
  4. AP raises a ticket with the cardholder, who is the CTO (Thursday)
  5. CTO is in a customer meeting; gets to it Friday (or next Monday)
  6. CTO updates the card; charge re-runs and succeeds

The whole cycle takes 3–7 business days, not 3–7 hours. Your email cadence has to respect that.

The cadence that works

DayActionRecipientTone
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:

Tone — what works

The B2B billing contact reads ~50 of these emails a week. The ones that get read:

Anti-patterns to avoid

Sending email #1 the same hour the charge fails

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."

Suspending service silently

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.

Excessive retries

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.

Ignoring decline-code semantics

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.

Same email body for every customer

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.

What DunningKit ships in the email layer

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.

Notify me when v0.1 ships: