Table of Contents
- Why Dunning Fails Without the Right Infrastructure
- Understanding the Two-Part Dunning Problem
- Step 1: Define Your Events and Attributes
- Required Events
- Required Customer Attributes
- Step 2: Build the Pre-Dunning Campaign
- Card Expiry Campaign in Customer.io Campaigns
- Step 3: Build the Post-Failure Recovery Workflow
- Setting Up the Entry Trigger
- Branching on Decline Type
- Winning Condition
- Step 4: Personalize Messaging with Liquid
- Step 5: Measure and Iterate
- Limitations of Customer.io for Dunning
- Frequently Asked Questions
- How do I prevent customers from receiving dunning emails if they already updated their card?
- Can Customer.io handle multiple subscription products with different retry logic?
- How many retry-related touchpoints is too many?
- What's the best way to handle customers who ignore all dunning messages?
Why Dunning Fails Without the Right Infrastructure
Most failed payment recovery programs underperform because they treat every failed charge the same way. A card that declined due to insufficient funds needs a different response than one that expired six months ago. Customer.io gives you the event-driven architecture to build that distinction directly into your retry and communication logic.
This guide walks through a complete dunning optimization setup in Customer.io — from pre-dunning alerts before a card fails to multi-step recovery sequences after it does.
---
Understanding the Two-Part Dunning Problem
Involuntary churn from failed payments typically falls into two categories:
- Soft declines: Temporary issues — insufficient funds, bank-side fraud flags, network errors. These often resolve with a retry 3–5 days later.
- Hard declines: Permanent issues — expired cards, closed accounts, invalid card numbers. Retrying won't help. You need the customer to take action.
Your Customer.io setup needs to handle both, and it needs to route users into the right path automatically based on the decline type coming from your payment processor.
---
Step 1: Define Your Events and Attributes
Customer.io is an event-driven platform, which means everything starts with the data you send. Before building any campaigns, map out the events your billing system needs to fire.
Required Events
Send these events from your payment processor (Stripe, Brrecurly, Chargebee, etc.) via Customer.io's Track API or a data pipeline like Segment:
- `payment_failed` — fires immediately on decline. Include properties: `decline_code`, `attempt_number`, `amount`, `plan_name`, `next_retry_date`
- `payment_succeeded` — fires on successful recovery. Ends the dunning sequence.
- `card_expiring_soon` — fires 30, 15, and 7 days before expiration for pre-dunning
- `subscription_canceled` — fires if the account lapses after all retries are exhausted
- `card_updated` — fires when a customer updates their payment method. Triggers an immediate retry notification.
Required Customer Attributes
Keep these attributes current on each customer profile:
- `subscription_status` (active, past_due, canceled)
- `payment_attempts` (integer, incremented on each failure)
- `last_decline_code` (string from your processor)
- `card_expiry_month` and `card_expiry_year`
---
Step 2: Build the Pre-Dunning Campaign
Pre-dunning prevents failures before they happen. It's the highest-ROI part of your dunning program because you're communicating with customers who are still active and engaged.
Card Expiry Campaign in Customer.io Campaigns
Use Campaigns (Customer.io's broadcast and triggered campaign builder) with a segment-based trigger:
- Create a segment: `card_expiry_month` equals the month two months from now AND `subscription_status` is `active`
- Set the campaign to re-evaluate this segment daily
- Build a 3-touch email sequence: 60 days out, 30 days out, 7 days out
- Each email links directly to your billing portal or a hosted payment update page
The goal is a single-click path to update payment. Friction kills conversion here.
---
Step 3: Build the Post-Failure Recovery Workflow
This is where Customer.io's Journeys (their visual workflow builder) does the heavy work. A Journey lets you branch logic based on event properties, time delays, and customer actions.
Setting Up the Entry Trigger
- Trigger: `payment_failed` event received
- Entry condition: `subscription_status` = `past_due`
- Prevent re-entry until the journey completes
Branching on Decline Type
Inside the Journey, add a branch node immediately after entry. Use the `decline_code` event property to split into two paths:
Getting the most out of Customer.io?
I'll audit your Customer.io setup and show you where revenue is hiding.
Path A — Soft Decline
- Wait 1 day, send email: "We had trouble processing your payment — we'll retry shortly"
- Wait for `payment_succeeded` event (up to 5 days). If received, exit the journey.
- If no success, wait until `next_retry_date`, then send a "Last attempt coming" email
- After a final failed retry, push user into Path B logic
Path B — Hard Decline
- Send email immediately with a direct link to update payment method
- Add an SMS step 48 hours later if the customer has `sms_opted_in` = true
- Day 7: send a final "Your subscription is at risk" email with urgency framing
- Day 10: fire a `subscription_lapse_warning` internal alert (optional webhook to your CRM)
Winning Condition
Set the journey's goal to the `payment_succeeded` event. Any customer who hits this goal exits the journey immediately — they don't receive additional messages.
---
Step 4: Personalize Messaging with Liquid
Customer.io supports Liquid templating, which lets you pull event properties and customer attributes directly into email copy.
Use `decline_code` to vary the call-to-action text:
- `card_expired` → "Your card on file has expired. Add a new card to continue your subscription."
- `insufficient_funds` → "We weren't able to process your payment. Update your billing information or contact your bank."
- `do_not_honor` → "Your bank declined this charge. Contact your bank or update to a different card."
This single change — specific, accurate language instead of generic "payment failed" copy — measurably improves update rates.
---
Step 5: Measure and Iterate
Track these metrics in Customer.io's Reporting dashboard by campaign and journey:
- Recovery rate: percentage of `payment_failed` events followed by `payment_succeeded` within 14 days
- Time to recovery: median days from first failure to successful charge
- Channel lift: recovery rate for email-only vs. email + SMS paths
- Drop-off point: which journey step has the highest exit rate without recovery
Run A/B tests on subject lines and send timing using Customer.io's built-in A/B testing within Campaigns.
---
Limitations of Customer.io for Dunning
Customer.io is strong on communication logic but has real gaps in the payment layer:
- No native retry scheduling. Customer.io can send a message before a retry, but it cannot trigger the retry itself. Your payment processor handles retry timing — Customer.io only reacts to the outcomes.
- No payment method update UI. You'll need a hosted billing portal (Stripe's customer portal, Chargebee's self-serve page, etc.) for the actual card update. Customer.io just drives traffic there.
- Segment refresh latency. Segment-based campaign triggers refresh on a schedule, not in real time. For time-sensitive dunning, use event-triggered journeys — not segment campaigns — as your primary mechanism.
- Limited native reporting on revenue recovered. You'll need to calculate recovered revenue externally or push that data back into Customer.io as a customer attribute.
---
Frequently Asked Questions
How do I prevent customers from receiving dunning emails if they already updated their card?
Set your journey's goal event to `card_updated` or `payment_succeeded`. When either event fires, Customer.io will exit that customer from the active journey immediately. You can also add a filter at each step checking that `subscription_status` is still `past_due` before sending.
Can Customer.io handle multiple subscription products with different retry logic?
Yes. Use the `plan_name` or `product_id` property on your `payment_failed` event to branch within a single journey, or build separate journeys per product and use entry conditions to route customers correctly. Separate journeys give you cleaner reporting.
How many retry-related touchpoints is too many?
Most high-performing dunning programs use 4–6 total touchpoints over 10–14 days. Beyond that, you're more likely to increase unsubscribes than recover revenue. If a customer hasn't updated their payment after 6 attempts, the communication problem is not your message cadence — it's your product's perceived value.
What's the best way to handle customers who ignore all dunning messages?
At the end of your journey, fire a webhook to your CRM or support platform flagging the customer for a manual outreach attempt. A single personal email from an account manager recovers a meaningful percentage of high-value accounts that ignored automated sequences entirely.