I used to treat a failed Stripe charge like a support ticket I would "get to later."
With under 50 paying customers that is a bad habit. One expired card is not noise. It is often 2–4% of monthly revenue walking out the door because your bank declined a $29 charge and you sent nothing human for a week.
Voluntary cancel is a different conversation. Failed payments are involuntary churn: the person still wanted the product, the card just said no. Fixing the email sequence is cheaper than building a retention dashboard.
Why tiny SaaS gets this wrong
Big SaaS buys Chargebee Smart Dunning and a Customer Success seat. You have Stripe default emails, a spreadsheet, and maybe Loops.
Stripe's stock "payment failed" mail is fine as a receipt of failure. It is not a recovery conversation. It sounds like a robot, it does not tell them what stops working on which day, and it does not give a one-click update path that feels safe.
I tracked this for a month across a few early products. Rough pattern:
- ~30–40% of failed renewals recovered if I emailed within 24 hours with a clear "update card" link
- Almost nobody recovered after day 7 if the only touch was Stripe's default
- Soft declines (insufficient funds, temporary hold) bounced back more than hard declines (stolen card, account closed) — treat them differently if you can see the decline code
Your numbers will differ. The point is: under 50 customers, you can literally open Stripe, filter failed invoices, and write three people by hand before lunch.
The three emails I actually send
Keep it short. No "we value your business" fluff.
Email 1 — same day (or within 24h)
Subject: Your [Product] payment didn't go through
Body shape:
- One sentence: we tried to charge $X on the card ending ••42 and the bank declined it
- What still works today (usually everything)
- What breaks on date Y if nothing changes (login ok / exports pause / workspace locks — be honest)
- One button: Update payment method
- One line: reply to this email if the charge looks wrong
Email 2 — day 3
Same facts, shorter. Add the decline reason if Stripe gives you one ("insufficient funds" vs "card expired"). People fix expired cards fast when you say it plainly.
Email 3 — day 6 or the day before access changes
Last clear warning. No guilt. "Access pauses tomorrow morning. Update the card anytime and you're back in within a few minutes."
If they still don't update, pause gracefully. Do not delete their data on day 8. Give them 14–30 days of read-only or a warm "come back" note. Deleting a workspace because of a declined $19 charge is how you get angry tweets and chargebacks.
What to put in the email (and what to skip)
Put:
- Exact amount and last 4 digits
- The date access changes
- A link that lands on the billing portal, not the homepage
- A reply-to that hits your real inbox
Skip:
- Three CTAs
- A feature changelog stuffed into a dunning email
- "Exclusive 20% off if you stay" on the first failed charge — that teaches people to fail on purpose
- Fake urgency ("account deleted in 2 hours") when you know you keep data for a month
If you use Stripe Customer Portal, point there. If you still have a custom billing page with three dead links, fix that before you write prettier copy.
Manual is fine under 50
You do not need a dunning product yet.
My starter loop for a week:
- Every morning, open Stripe → Payments → Failed (or Invoices → Open/Past due)
- Sort by amount, ignore $0 and weird test customers
- For each real customer, check: already emailed? decline code? how many days past due?
- Send Email 1 / 2 / 3 from your own address or a simple Loops/Resend template
- Log date + which email in a sheet column next to their name
When you pass ~50–80 active subscriptions and failed charges show up daily, automate the same three templates. Until then, the sheet beats a half-configured lifecycle tool you never open.
Measure one ugly number
Forget fancy churn cohorts for a second. Track:
Failed → recovered within 7 days
If that rate is under ~25% and you are sending nothing beyond Stripe defaults, the emails are the leak. If recovery is already high, stop obsessing and go fix activation instead — I wrote more about why early churn % lies when you have under 50 customers and the longer playbook on reducing early SaaS churn under 50 customers.
A note on tone
Write like a person who wants them back, not a collections agency.
"Hey — bank declined the renewal. Here's the link. Ping me if something looks off." beats five paragraphs of policy.
Failed payments are awkward. Clear dates and one link make them less awkward. That is the whole product for year one.
Top comments (2)
Two additions that sit before and after your three emails. Before: a heads-up a few weeks ahead when a card is about to expire. Stripe can send it for you (Billing settings, emails about expiring cards), and it catches the most common hard decline before it happens. After: link to the invoice's hosted payment page rather than a billing page behind your login. Someone whose card just failed is often reading on a phone where they aren't signed in, and every extra sign-in step loses a few. Fully agree on not deleting at day 8; a few weeks of read-only costs you almost nothing.
Treating failed charges as involuntary churn instead of a ticket queue is exactly right at that size. Stripe's default failed-payment mail is a receipt, not a recovery conversation. On the product side I keep a short grace window tied to real subscription status (past_due vs unpaid vs canceled), and I reconcile Stripe subscription plus latest invoice against the entitlement row on a schedule, not only when invoice.payment_failed lands. That way a soft decline plus a late webhook does not leave access wrong in both directions while you are still writing the human email.