GuideJuly 15, 2026·17 min read

How to Set Up a Dunning Email Sequence That Recovers Revenue (2026)

Sinh Yang

Sinh Yang

Founder of Revova

A dark setup panel showing a dunning sequence build checklist ticking off connect, cadence, and decline-reason branching, with a green "sequence: live" toggle badge

Setting up a dunning email sequence that actually recovers revenue takes five real decisions, not a mountain of engineering: pick the event that triggers it, decide a cadence of four to five emails spread across one to three weeks, branch the copy by decline reason instead of writing one generic template, time each send to the customer's local morning instead of a 3am batch job, and test the whole thing against a sandboxed failed charge before a real customer ever sees it. Get those five right and you are most of the way toward the commonly cited 40–60% of failed charges that retries plus dunning recover industry-wide. Skip any one of them and you usually end up with a sequence that sends fine, looks fine, and quietly underperforms by half. This guide walks through the actual build: what you need connected before you start, how to wire the trigger on Stripe, Paddle, Braintree, Chargebee, or Recurly, how to decide cadence and branch by decline code, how to get timing right, how to test before launch, and — since most founders eventually ask — an honest look at what building this yourself costs in time versus letting a dedicated tool run it for you.

We spend our days building the tool that runs this exact sequence for other people's Stripe and Paddle accounts, which means we have watched a lot of founders build their first dunning setup by hand before switching to something automated — and just as many decide the DIY version was good enough and stay there. Neither path is wrong. What we have not seen work is skipping straight from "read an article about dunning" to "flip a switch" without making the handful of concrete decisions below first. This is the setup guide we wish had existed before we built our own.

Key takeaways

  • A working sequence needs five decisions, not a rebuild of your billing stack: trigger event, cadence, decline-reason branching, send timing, and a test pass.
  • Building it yourself on Stripe or Paddle plus an ESP is realistic but usually costs one to two weeks of engineering and copywriting time for a first version.
  • Branch copy by decline reason before you touch subject lines — a single generic template caps recovery well below what reason-specific copy gets.
  • Send around 8:30am in the customer's local time zone, not a fixed hour in yours.
  • Test with a real sandboxed failed charge — Stripe's test-mode decline cards make this trivial — before any live customer sees the sequence.
40–60%
of failed charges recoverable via retries + dunning combined
1–3 weeks
typical cadence length for a well-built sequence
<10 min
to connect a processor with a dedicated recovery tool

What you need in place before you build anything

Before writing a single email, four things need to be connected: read access to your processor's failed-charge events, a way to send templated transactional email on demand, a clear view of the decline code on each failure, and an authenticated sending domain so those emails land in the inbox instead of spam. Skipping any one of these turns the rest of the setup into wasted effort.

  • Processor access. An API key or webhook secret from Stripe, Paddle, Braintree, Chargebee, or Recurly with permission to read payment and invoice events — you do not need write access to charge cards, only to see when one failed and why.
  • A transactional email sender. Postmark, SendGrid, Customer.io, Braze, or Mailgun — something built for triggered, API-fired sends with per-message deliverability reporting, not a newsletter tool built for scheduled campaigns.
  • Decline-code visibility. Each processor labels the reason differently — Stripe returns a decline_code field, Braintree exposes a processorResponseText, Chargebee and Recurly attach their own failure-reason strings — but every one of them tells you enough to distinguish a soft decline from a hard one, which is the routing logic the rest of this guide depends on.
  • An authenticated sending domain. SPF, DKIM, and DMARC configured on whichever domain you send dunning email from. This is unglamorous and easy to skip, and it is also the single fastest way to have a technically perfect sequence land entirely in spam.
  • Some notion of customer time zone. Billing country, IP geolocation at signup, or browser locale are all reasonable proxies — you do not need perfect precision, just enough to bucket customers by roughly which morning they wake up in.

With those five in place, the actual build is seven concrete steps, in this order:

  1. Connect your processor and choose the event that triggers the sequence.
  2. Decide your cadence — how many emails, on which days.
  3. Branch the sequence by decline reason before writing any copy.
  4. Write the actual email copy for each branch.
  5. Set send timing and time-zone logic.
  6. Wire suppression, unsubscribe, and bounce handling.
  7. Test end-to-end with a sandboxed failure before going live.

The rest of this guide walks through each of those in the depth that actually matters — starting with the one most DIY builds get subtly wrong.

Pick the trigger: the event that actually starts the sequence

The sequence should start the moment a charge first fails, not after your processor's automatic retries are exhausted — waiting for retries to finish can take a week or more on its own, by which point a Day 1 email is not a Day 1 email anymore. The nuance almost every DIY build gets wrong the first time is that most processors fire the same failure event again on every retry attempt, which means a naive listener restarts — or duplicates — the whole sequence each time the card is retried and fails again.

  • Stripe fires invoice.payment_failed for recurring subscription charges and charge.failed for one-off charges — on the first attempt and again on every subsequent Smart Retry attempt that also fails.
  • Braintree sends a subscription webhook (commonly surfaced as subscription_charged_unsuccessfully) each time a scheduled subscription charge fails to process.
  • Chargebee emits a payment_failed event on the invoice, with the failure reason attached to the transaction record.
  • Recurly raises a failed_invoice_notification (sometimes labeled a failed-payment notification depending on API version) when a renewal charge cannot be collected.

Deduplicate on the invoice or charge ID, not the event

The fix is simple once you know to look for it: key your sequence state off the invoice or charge ID, not the webhook event itself. The first failure for a given invoice starts the sequence and sends the Day 1 email; every subsequent failed-retry event for that same invoice ID gets checked against state you already have and is treated as "still failing," not as a brand-new trigger. Get this wrong and customers receive a duplicate Day 1 email every time a retry fails — a fast way to make an otherwise well-designed sequence feel spammy.

For the full mechanics of wiring this specifically on Stripe — including where Smart Retries fit relative to your own trigger — see how to recover failed Stripe payments. If you are unclear on what dunning is actually for versus retries and win-back in the first place, what is dunning covers that foundation.

Revova reads the failed-charge event from Stripe, Paddle, Braintree, Chargebee, or Recurly directly and handles deduplication itself — no webhook endpoint, signature verification, or idempotency logic for you to build or maintain.
$29–79/mo · free trialStart free →

Decide your cadence: how many emails, spaced how far apart

Four to five emails spread across one to three weeks is the effective default for monthly subscriptions — fewer than that undersends and leaves recoverable customers uncontacted; many more than that chases people who have already made up their mind and mostly just adds unsubscribes. If you are deciding your own cadence rather than inheriting one, three questions settle it quickly.

  • Monthly or annual?A monthly subscriber gets another billing cycle a few weeks later even if this one is missed, so four emails over roughly two weeks (Revova's Starter plan runs Day 1, 3, 7, 14) is usually enough. An annual renewal is worth far more per failure and has no near-term retry to fall back on, so it is worth stretching the cadence further and leaning harder on a pre-dunning warning before the renewal date even fires.
  • What is the account worth?Higher-value or B2B accounts justify a longer runway and an extra channel — Revova's Pro plan adds a fifth email on Day 21 plus SMS specifically because a single decision-maker being on vacation for two weeks is common and worth waiting out.
  • How aggressive is your grace period? If access pauses immediately on first failure, your cadence needs to front-load urgency into the first two emails. If you can afford a week or two of continued access while the sequence runs, you can let the early emails stay softer and save the direct, dated language for later in the sequence.

Whatever cadence you land on, write it down and keep it fixed once live — a sequence that changes shape every few weeks makes it much harder to trust the open-rate and click-rate trends you are watching, which matters once you get to the monitoring section below.

Branch by decline reason before you write a single subject line

Routing by decline code is the single highest-leverage decision in the entire build — higher than subject-line wording, higher than exact send time. A charge that failed because of insufficient_funds often clears on its own within days and deserves a patient, low-pressure nudge; a charge that failed because the card is expired_cardwill never clear no matter how many times it is retried and needs a direct, unambiguous ask for a new number. Averaging those two into one generic "your payment failed" template gets both of them slightly wrong.

lost_cardHard · needs new card

The cardholder has reported this card lost. It will never succeed on retry and the customer likely already knows the card is gone.

What to do: Skip any framing that implies the card might still work — go straight to asking for a different card number, without alarming language about fraud.

stolen_cardHard · needs new card

The issuer has flagged the card as stolen. Like lost_card, no amount of retrying changes the outcome.

What to do: Same direct ask for a replacement card — this is a routine, common decline reason, not a signal to treat the customer as suspicious.

incorrect_cvcHard · needs new card

The security code submitted did not match what the issuer has on file — often a typo rather than a real problem with the card itself.

What to do: Ask the customer to re-enter their card details rather than assuming the card is bad; a simple 'double-check the 3-digit code' framing recovers this quickly.

processing_errorSoft · retry

A generic, temporary failure on the processor or issuer's side unrelated to the card or the cardholder's funds.

What to do: Worth a soft retry-friendly message — 'this looked like a temporary glitch, here's a link just in case' — rather than treating it as urgent.

A fifth reason worth naming separately is currency_not_supported — rare, but common enough for businesses selling across borders that it is worth a dedicated branch rather than silently failing into a generic template; the fix is usually routing the customer to a different card or a supported currency, not retrying at all. For the complete reference beyond these five, see Stripe decline codes explained.

Not every decline reason is worth building a bespoke branch for on day one, especially if you are doing this by hand. A quick way to prioritize is to plot each reason by how often it shows up against how much work a dedicated branch takes to build well:

QUICK WINSBIG BETSFILL-INSDESELECTEffort →Impact →insufficient_fundsexpired_carddo_not_honorauthentication_required (SCA)lost_card / stolen_cardcurrency_not_supported
Where to spend your first hours of copywriting effort — build the quick-win branches (insufficient_funds, expired_card) before the rarer, higher-effort ones.

insufficient_funds and expired_card alone typically account for the majority of failures on most subscription businesses, so a manual build that only gets those two branches right before launch is already capturing most of the available lift — the remaining reasons can be layered in afterward without holding up your launch date.

Revova's Pro plan ($79/month) routes every failed charge by decline reason automatically — soft declines get a patient nudge, hard declines get a direct ask, SCA-related declines get a re-authentication link — with no branching logic for you to write or maintain.
$29–79/mo · free trialStart free →

Write copy for each branch

Once the branches are decided, the copy itself should read like a short, specific note from a real person — not a templated notice. Here is what the same sequence typically looks like for a soft decline versus a hard decline, assuming both are sent as the first email in their branch:

Soft decline branch — insufficient_funds
Subject line
Your card should clear on its own — but here's a backup just in case

Hi [First Name],

Your last payment for [Product] didn't go through — this is usually just a timing thing with your bank and often clears on its own within a few days.

No action needed right now, but if you'd rather not wait, you can update your card here: [Update payment method].

Thanks for being a customer.

Why it works: Soft declines often resolve themselves within days, so leading with reassurance rather than urgency avoids making an easy case feel like an emergency, while still giving a one-click fallback for anyone who wants to act now.
Hard decline branch — expired_card
Subject line
We need an updated card for your [Product] subscription

Hi [First Name],

The card on file for your [Product] subscription has expired, so we weren't able to process your last payment.

Updating it takes about 30 seconds and keeps everything running without interruption: [Update payment method].

Let us know if you run into any trouble — happy to help.

Why it works: Hard declines never clear on retry, so the copy skips the soft framing entirely and asks directly for the one thing that actually fixes it — a new card number — while staying friendly rather than alarming.

For a much larger library of ready-to-use subject lines and full bodies across every stage of a sequence — not just the first email in each branch — see our dedicated dunning email examples and templates guide.

Get the timing right: send hour and time-zone logic

Send around 8:30am in the customer's local time zone, not a single fixed hour tied to your own office or your server's default clock — an email that lands at 3am local gets buried under a full overnight inbox by the time anyone reads it, no matter how good the copy is. Building this yourself means solving two smaller problems: getting a time zone for each customer, and actually sending at the right local moment rather than in one batch.

For the time zone itself, perfect precision is not required — billing country, IP geolocation captured at signup, or browser locale at checkout are all reasonable proxies, and most businesses only need country-level granularity to get meaningfully closer to a local morning than a single global send time does. For the actual scheduling, some ESPs (Customer.io and Braze both support this natively) can queue a send for "8:30am recipient-local" directly; without that feature, the common workaround is a nightly job that buckets customers by time zone offset and queues each bucket's send for the right hour in turn.

Don't forget annual-plan renewals need a different clock, not just a different cadence

A monthly failure and an annual renewal failure should not necessarily fire at the same hour logic if your business serves customers heavily concentrated in one region for annual plans and globally for monthly ones — worth checking your own customer distribution before assuming one timing rule fits every plan type.

Test the sequence before a real customer ever sees it

Testing a dunning sequence is easier than it sounds because Stripe's test mode ships card numbers built specifically to simulate exact decline reasons on demand — you do not need to wait for a real card to fail. 4000000000009995 always declines with insufficient_funds; 4000000000000341 attaches successfully at first but fails the next time it is charged, which is useful for testing renewal-failure flows specifically. Run every branch of your sequence against these before launch, not just the happy path.

  • Confirm the trigger fires exactly once per invoice, not once per retry attempt (see the deduplication note above).
  • Check every merge field resolves correctly — customer name, product name, last four digits of the card, and any renewal or access-pause date.
  • Click every link in every branch, including the unsubscribe link and the card-update link, and confirm both actually work.
  • Verify the send lands at the expected local hour for at least two different time zones, not just your own.
  • Confirm the sequence stops immediately once a test payment succeeds — a customer who fixes their card should never receive the next email in the sequence.

That last check is the one DIY builds most often skip, and it is also the one most likely to make an otherwise well-built sequence feel broken to a real customer who already paid.


DIY vs Revova: what setup actually costs you

Everything above is buildable by hand on top of Stripe, Paddle, Braintree, Chargebee, or Recurly plus an ESP like Postmark or Customer.io — plenty of teams do exactly that, and for a simple, single-branch sequence it is a reasonable weekend project. The honest comparison is less about whether it is possible and more about what it costs in time, versus what a dedicated tool replaces outright.

Setup stepDIY (processor + ESP)Revova
Webhook wiring & dedupBuild and host an endpoint, verify signatures, key state off invoice ID yourselfNot required — Revova reads processor events directly
Decline-reason routingWrite and maintain your own branching logic in codeBuilt-in hard/soft/SCA routing (Pro)
Email copyWrite and update templates per branch yourselfAI-written per decline reason, in 8 languages on Pro
Timezone-aware send timeRequires a timezone lookup plus a scheduler or ESP featureBuilt in — sends ~8:30am customer-local by default
Deliverability (SPF/DKIM/DMARC, suppression)Your responsibility to configure and monitor on your ESPHandled; bounced or spam-flagged addresses are auto-suppressed
Historical failuresNot covered unless you build a separate backfill jobLost Revenue Finder scans 90 days (Starter) or 12 months (Pro)
Typical time to first live send1–2 weeks of engineering + copywritingUnder 10 minutes — paste a read-only key
Ongoing costESP subscription plus your own maintenance timeFlat $29–$79/month, no commission on recovered revenue

Some businesses are well served by the DIY column, especially if they already have engineering time budgeted and only need one or two decline-reason branches. Tools with a percentage-of-recovery pricing model, or well-funded scale-ups evaluating something like Churnkey, are also reasonable fits depending on volume and team size. What a dedicated flat-fee tool like Revova mainly buys back is the maintenance burden — timezone logic, deliverability monitoring, and keeping up with each processor's event payloads as they change — plus the one thing DIY genuinely cannot do at all: recovering failures that already happened before you built anything. Full plan details, including the 14-day free trial and 30-day money-back guarantee, are on the Revova pricing page.

Skip the webhook wiring entirely — connect Stripe, Paddle, Braintree, Chargebee, or Recurly with a read-only key and get a working sequence live in minutes. Starter is $29/month, Pro is $79/month, both with a 14-day free trial and no credit card required.
$29–79/mo · free trialStart free →

What to watch once the sequence is live

Six numbers matter once your sequence is actually sending, and they matter roughly in this order: delivery rate first, because nothing else means anything if the email never arrives.

  • Delivery rate. Bounces and blocks show up here first — a sudden drop usually points to a deliverability problem (SPF/DKIM/DMARC misconfiguration, or a domain that just got flagged), not a copy problem.
  • Open rate. Industry benchmarks commonly put dunning open rates around 30–40% — noticeably higher than typical marketing email, because the recipient has a direct, self-interested reason to open it.
  • Click-through rate. Commonly 10–15% through to the card-update link. A big gap between a healthy open rate and a weak click rate usually means the copy or the CTA needs work, not the timing.
  • Recovery rate. What share of failed charges actually get fixed within the sequence window — the number that ultimately matters most, and the one worth reporting on weekly rather than daily.
  • Unsubscribe and spam-complaint rate. A rising trend here is a signal about tone or frequency, not proof that dunning itself is the wrong approach.
  • Dollars actually recovered. The number that ties everything above back to revenue — worth sizing against your total exposure using the calculator in how much revenue you're losing to failed payments.
Before you optimize the sequence further, see what you've already lost. Revova's free Lost Revenue Finder connects read-only and scans your real payment history — no card required, no commitment.
$29–79/mo · free trialStart free →

Check these weekly at first rather than daily — a few days of noise in a small dataset will send you chasing signals that aren't there, especially on a business with fewer than a few dozen failures a month. Revova's Pro plan ships a weekly digest specifically so this monitoring does not become its own chore.

Setup mistakes that quietly cap recovery

Most underperforming sequences are not broken in any way that shows up as an error — they send, they deliver, and the copy reads fine at a glance. The problems that actually cap recovery tend to be structural, in the setup itself, rather than in any individual email:

  • No deduplication on the trigger. Restarting the sequence on every retry-attempt webhook instead of keying state off the invoice ID, which sends duplicate Day 1 emails and reads as spam.
  • Skipping domain authentication. Launching without SPF, DKIM, and DMARC configured, which routes a technically perfect sequence straight into spam folders before anyone reads it.
  • Hardcoding one send hour. Treating "8:30am" as a single global time rather than per-customer local time, which quietly halves the advantage of good timing for half your customer base.
  • Never testing the "already paid" case. Failing to confirm the sequence stops the moment a customer's card succeeds, so a customer who already fixed the problem keeps getting emails about it.
  • One template for every decline reason. Averaging insufficient_funds and expired_card into the same generic copy, which gets both branches slightly wrong.
  • No suppression list. Continuing to send to addresses that bounced or filed spam complaints, which damages sender reputation for every email you send afterward, not just the dunning sequence.
  • Ignoring the backlog. Building a sequence that only applies to failures going forward and never sweeping the failed invoices that piled up before setup — usually the single largest one-time recovery available.

A useful gut check before calling the build "done": could you explain, in one sentence each, why your Day 1 email differs from your Day 14 email, and why your insufficient_funds branch differs from your expired_cardbranch? If either answer is "it doesn't, really," that gap is usually the fastest, cheapest fix available on the whole list above.


For Stripe's own reference on webhook delivery, retries, and signature verification, see Stripe's webhooks documentation, and for the technical standard behind sender-domain authentication, see the DMARC.org overview.

Frequently asked questions

Do I need to write code to set up a dunning sequence?

Not necessarily. Stripe, Chargebee, and Recurly all expose enough dashboard-level configuration to get a basic, single-template sequence running with zero code. What does need engineering is anything beyond the basics: branching copy by decline reason, sending at the customer’s local hour instead of a fixed time, suppressing bounced addresses automatically, and recovering failures that happened before you set anything up. A tool like Revova gets you all of that — decline-reason routing, timezone-aware sends, suppression, and a historical scan — by pasting a read-only processor key, no webhook endpoint or code required.

Which ESP should I use if I’m building this myself?

You want a transactional email provider, not a marketing-newsletter tool — Postmark, SendGrid, Customer.io, Braze, and Mailgun are the common choices, because they’re built for triggered, API-fired sends rather than scheduled campaigns and they report deliverability metrics (bounces, spam complaints) per message. Customer.io and Braze both support delivering at the recipient’s local time natively, which saves you from building your own timezone-bucketing job. Whichever you pick, get SPF, DKIM, and DMARC configured on the sending domain before your first real send — an unauthenticated domain routinely lands dunning emails in spam, which defeats the entire sequence regardless of how good the copy is.

How long does setup actually take?

Realistically, a DIY build — wiring the webhook, writing decline-reason branches, setting up timezone-aware sends, and testing — runs one to two weeks of combined engineering and copywriting time for a first version, then ongoing maintenance as processors change their event payloads. Connecting Revova to Stripe, Paddle, Braintree, Chargebee, or Recurly takes under 10 minutes: paste a read-only API key, and the AI-written sequence, decline-reason routing, and timezone logic are already built.

Can I test the sequence before real customers see it?

Yes, and you should treat this as non-negotiable. Stripe’s test mode ships specific card numbers that simulate exact decline reasons — 4000000000009995 always declines with insufficient_funds, 4000000000000341 attaches successfully but fails on the next charge attempt — so you can trigger every branch of your sequence on demand instead of waiting for a real customer’s card to fail. Send yourself a full run of every branch, check that merge fields (name, product, card’s last four digits, renewal date) resolve correctly, and click every link, including unsubscribe, before flipping it on for real customers.

What metrics should I watch once the sequence is live?

Six numbers matter, roughly in this order: delivery rate (are emails actually arriving), open rate (industry benchmarks commonly run 30–40% for dunning specifically), click-through rate to the card-update link (commonly 10–15%), recovery rate (what share of failed charges get fixed), unsubscribe and spam-complaint rate (a rising trend here usually means tone or frequency is off, not that dunning itself is a bad idea), and dollars actually recovered. Watch these weekly at first, not daily — a few days of noise in a small dataset will send you chasing signals that aren’t there.

Does Revova replace my existing ESP or CRM?

No, and it isn’t trying to. Revova is a dedicated recovery layer that reads decline events from your payment processor and sends the dunning, pre-dunning, and win-back sequences itself — it doesn’t touch your marketing newsletter, your transactional receipts, or your CRM. Most customers run Revova alongside whatever they already use for general email, the same way they’d run a dedicated analytics tool alongside a general-purpose dashboard.

What happens if my webhook endpoint goes down for a few hours — do I lose those failed charges?

On Stripe specifically, no — failed webhook deliveries are retried on an exponential backoff for up to three days, and the Dashboard’s Events log lets you manually resend anything that was missed, so a short outage on your end rarely means a permanently lost event. It’s still worth building in a daily reconciliation job that polls recent charges directly as a backstop, because relying entirely on webhook delivery with no fallback is the single most common way DIY sequences silently miss failures. This is exactly the kind of reliability work a dedicated tool like Revova takes off your plate.

Do I need a different setup for Stripe vs Paddle, Braintree, Chargebee, or Recurly?

The concept is identical across all five — listen for a failed-payment event, branch by decline reason, send on a cadence, time it to the customer’s local hour — but the trigger event name and the decline-code vocabulary differ per processor, so a webhook integration built for Stripe’s invoice.payment_failed won’t work unmodified against Chargebee’s payment_failed event or Recurly’s failed_invoice_notification. If you run more than one processor, or expect to add one later, a tool that already normalizes all five (Revova does) avoids rebuilding this integration from scratch each time.

Build the sequence in minutes, not weeks

Revova connects to Stripe, Paddle, Braintree, Chargebee, or Recurly with a read-only key and handles the trigger, the decline-reason routing, the timezone-aware timing, and the deliverability work for you. Start with a free scan of what you've already lost, then let the sequence recover the rest. 14-day free trial, no credit card, 30-day money-back guarantee.

Start my 14-day free trial →

No credit card · Free Lost Revenue scan · 30-day money-back guarantee