Table of Contents
- What Dunning Optimization Actually Requires
- The Core Architecture: Two Tracks, One Goal
- Setting Up Pre-Dunning Alerts in Workflow Studio
- Step 1: Configure the Entry Trigger
- Step 2: Build the Channel Sequence
- Step 3: Personalize with User Profile Fields
- Building the Post-Failure Dunning Sequence
- Step 1: Entry Trigger and Deduplication
- Step 2: The Multi-Channel Sequence
- Step 3: Channel Branching by User Behavior
- Suppression and Exit Logic
- Limitations of Iterable for Dunning Workflows
- Frequently Asked Questions
- How do I pass payment status data from Stripe into Iterable?
- Can I run both pre-dunning and post-dunning workflows simultaneously without messaging conflicts?
- What SMS copy length works best for dunning messages?
- How do I measure whether my dunning workflow is actually working?
What Dunning Optimization Actually Requires
Failed payments are not a billing problem. They are a communication problem. When a card declines, you have a narrow window to reach the right customer, on the right channel, at the right moment — before they churn without ever intending to.
Iterable gives you the cross-channel infrastructure to run this well. Workflow Studio handles the sequencing logic. Native email, SMS, and push sit inside the same platform. You can branch on behavior, delay on conditions, and suppress when payment recovers. That combination covers most of what a dunning system needs.
This guide walks through exactly how to build it.
---
The Core Architecture: Two Tracks, One Goal
Dunning optimization splits into two distinct workflows. Conflating them is the most common mistake.
Track 1 — Pre-dunning: Triggered before a payment attempt fails. You notify the customer that their card is expiring or that a charge is coming. This is the highest-conversion touchpoint in the entire sequence.
Track 2 — Post-failure dunning: Triggered after a payment fails. You're now working against the clock with a customer who may not even know there's a problem.
Build these as two separate workflows in Iterable's Workflow Studio. They share some message content but run on entirely different triggers and timing logic.
---
Setting Up Pre-Dunning Alerts in Workflow Studio
Pre-dunning works because customers respond to warnings, not failures. A message that says "your card expires in 7 days" converts at roughly 3–5x the rate of a failed-payment notice.
Step 1: Configure the Entry Trigger
Your entry trigger depends on what your billing system exposes. You need one of two data points pushed into Iterable as a custom event or updated on the user profile:
- Card expiration date (for expiring card alerts)
- Upcoming renewal date (for pre-charge notifications)
Use a List-Based trigger in Workflow Studio if you're batching daily exports from Stripe or your billing system. Use an Event-Based trigger if you're pushing real-time webhooks — which is the better approach if your stack supports it.
Step 2: Build the Channel Sequence
Inside the workflow, add a delay node set to fire 7 days before the expiration or renewal date. Then sequence your channels:
- Email — send a card-update prompt with a direct link to your billing portal
- Wait 48 hours, check if the card was updated (via a profile field update event)
- If not updated: Push notification (for mobile apps) or a second email with higher urgency copy
- If updated: exit the workflow immediately using an Exit Condition tied to the card-updated event
The exit condition is critical. Nothing damages trust faster than continuing to send "update your card" messages after a customer already did.
Step 3: Personalize with User Profile Fields
Pull the card's last four digits, the expiration month, and the customer's first name directly from Iterable's user profile data. Use Handlebars templating in the message body to populate these dynamically. Specificity — "your Visa ending in 4821 expires in October" — consistently outperforms generic copy.
---
Building the Post-Failure Dunning Sequence
Once a payment fails, your billing system should fire a custom event into Iterable immediately. Name it something explicit: `payment_failed`.
Step 1: Entry Trigger and Deduplication
Set the workflow entry to trigger on the `payment_failed` event. Add a frequency cap or an entry condition that prevents a user from entering the same workflow more than once per billing cycle. Without this, customers with multiple failed retries will receive duplicate sequences.
Step 2: The Multi-Channel Sequence
Structure your post-failure sequence across a 14-day window. A tested cadence that works across subscription businesses:
- Day 0: Email — payment failed, update card now
- Day 1: Push notification — account access reminder (if applicable)
- Day 3: SMS — short, direct, link to billing portal
- Day 6: Email — second notice, escalate urgency, mention service impact
- Day 10: Email — final notice before account pause
- Day 14: Email — account paused, here's how to reactivate
Getting the most out of Iterable?
I'll audit your Iterable setup and show you where revenue is hiding.
At each step, add a conditional wait node that checks for a `payment_recovered` event. If that event fires at any point, route the user to an exit path that sends a single confirmation email. Stop all further outreach immediately.
Step 3: Channel Branching by User Behavior
Not every customer has push enabled. Not every customer is responsive to email. Use Workflow Studio's conditional branching to route based on what you know:
- If `push_opted_in = true` → include push in the sequence
- If `sms_opted_in = true` → include SMS
- If email open rate over last 90 days is below 5% → prioritize SMS over email
Iterable stores behavioral data at the user level. You can reference these fields in branch conditions without needing to export data to a separate tool.
---
Suppression and Exit Logic
Suppression is where most dunning builds break down. You need at least three exit conditions active throughout both workflows:
- Payment recovered — `payment_recovered` event fires, exit immediately
- User cancels voluntarily — `subscription_cancelled` event fires, exit and do not re-enter
- Account already churned — if your billing system marks an account as closed, update a profile field and use it as a workflow exit condition
Set these as workflow-level exit conditions in Iterable, not just branch logic inside the flow. Exit conditions run continuously and will pull users out even if they're mid-sequence.
---
Limitations of Iterable for Dunning Workflows
Iterable is strong on communication sequencing. It is not a billing system, and a few gaps are worth acknowledging before you build.
No native payment retry logic. Iterable cannot trigger or schedule payment retries directly. Your billing platform — Stripe, Recurly, Chargebee — handles the retry schedule. Iterable only handles the communication layer. You'll need webhooks flowing both directions to keep state in sync.
Limited real-time profile updates from billing events. If your billing system doesn't support real-time webhooks, you may be working with stale data. Daily batch syncs introduce lag that can cause messages to fire after a payment has already recovered.
No built-in billing portal integration. You'll generate payment update links externally and pass them in as user profile fields or event properties. Iterable will not generate tokenized billing links natively.
Workflow complexity caps. Very large dunning sequences with many conditional branches can become difficult to maintain in Workflow Studio's visual interface. Document your logic externally and use clear node naming conventions from the start.
---
Frequently Asked Questions
How do I pass payment status data from Stripe into Iterable?
Use Stripe webhooks to push events directly to Iterable's Events API. When Stripe fires `invoice.payment_failed`, your backend receives it, transforms it into an Iterable custom event called `payment_failed`, and sends it via a POST request to `https://api.iterable.com/api/events/track`. Include relevant metadata — invoice amount, retry count, next retry date — as event data fields so you can reference them in message personalization.
Can I run both pre-dunning and post-dunning workflows simultaneously without messaging conflicts?
Yes, but you need explicit suppression logic. If a user is mid-way through a pre-dunning workflow and a payment failure fires, use an entry condition on the post-dunning workflow to check if the user is already in the pre-dunning flow, or simply allow both to run and rely on frequency capping at the channel level to prevent same-day message collisions. The cleaner approach is to exit the pre-dunning workflow as soon as a `payment_failed` event fires, then let the post-failure workflow take over.
What SMS copy length works best for dunning messages?
Keep it under 160 characters to avoid multi-part message fees and delivery degradation. The highest-performing format is direct: account name, problem statement, and a shortened billing portal link. Example: "Your [Brand] payment failed. Update your card to keep your account active: [link]." Include your brand name at the start — SMS lacks the visual context of email.
How do I measure whether my dunning workflow is actually working?
Track three metrics in Iterable's analytics dashboard: workflow conversion rate (users who recover payment divided by users who enter the sequence), time-to-recovery (how many days into the sequence recovery happens on average), and channel attribution (which step in the sequence preceded recovery). Set up a custom conversion event tied to `payment_recovered` and map it back to the workflow in Iterable's reporting. Anything above a 20–30% recovery rate on involuntary churners is a strong result for a well-tuned sequence.