Dunning and Billing Recovery with AI Agents
2026-04-15
Somewhere between 10% and 20% of subscription revenue is lost to failed payments every year - not to churn, not to competitors, but to expired credit cards, insufficient funds, and bank-side declines that have nothing to do with whether the customer actually wants to keep their subscription.
Most of that revenue is recoverable. The customer didn't intend to cancel. The card on file expired. They got a new card and forgot to update their billing information. A well-timed email recovers a substantial portion of these accounts.
The problem is that dunning sequences are notoriously hard to get right. Done poorly, they compound the bad experience of a failed payment with a flood of billing emails that feel accusatory rather than helpful. Done well, they recover revenue without the customer feeling chased.
AI agents can run effective dunning sequences - but only if the email infrastructure handles the parts that make dunning specifically risky.
Why dunning is different from other automated email
Most automated email sequences have a clear trigger and a clear terminal condition: user signs up, agent sends onboarding sequence, user completes setup, sequence stops.
Dunning has a more complex lifecycle. The terminal conditions are:
- Payment succeeds - stop immediately, customer is recovered
- Customer explicitly cancels - stop, update suppression
- Grace period expires - stop, account suspended or churned
- Customer replies asking for help - stop the sequence, route to support
The mistake teams make is treating dunning as a simple time-based sequence: "send on day 1, day 3, day 7, day 14." This ignores the terminal conditions. If a customer updates their card on day 2 and payment succeeds, they should not receive the day 3 email about their outstanding balance. If they send a furious reply on day 4, the day 7 email should not go out.
An AI agent that handles dunning needs to be event-aware, not just time-aware. The sequence is not a fixed schedule - it's a series of conditional sends that each check current state before executing.
The failure modes without proper infrastructure
Before getting to implementation, it's worth cataloging what breaks when dunning is built on top of a raw send API.
Double-sends on billing events. Payment failure webhooks arrive multiple times. Stripe, for example, sends invoice.payment_failed on the initial failure and again on each automatic retry (typically at 3, 5, and 7 days). If your dunning agent fires on each event without deduplication, your customer receives multiple "payment failed" emails for the same invoice. This is confusing and damaging to trust.
Sequences that don't stop. The dunning sequence is a scheduled job. The payment success event arrives asynchronously. Unless these two systems share state about whether a recovery sequence is active, the sequence continues after payment succeeds. Customers who've already updated their card still get urgent "final notice" emails - a fast way to convert a billing hiccup into actual cancellation.
Suppression list blindness. A customer who complained about email six months ago and was suppressed globally should not receive billing emails from a dunning workflow running on a separate system that doesn't check the suppression list. The dunning sequence shouldn't have to manually implement suppression lookup - it should be enforced automatically.
No reply handling. A customer who replies "what is going on with my account?" is signaling that they want help, not another automated email. Without reply classification, the sequence keeps firing on its schedule, ignoring the response entirely.
What a correct implementation looks like
1. Fire-once with deduplication
Every dunning send should use a dedupe key tied to the specific invoice and the specific message in the sequence. When the billing event fires, the dedupe key ensures only one email goes out regardless of how many times the webhook arrives.
POST /v1/agent/outbound/request-send
Authorization: Bearer <token>
{
"to": "customer@example.com",
"mailboxId": "<dunning-mailbox-id>",
"templateId": "dunning-day-1",
"dedupeKey": "invoice_<invoice_id>_day1",
"payload": {
"invoice_id": "inv_abc123",
"amount_due": "$49.00",
"update_url": "https://app.example.com/billing"
},
"sendReason": "payment_failed_dunning"
}
The dedupeKey of invoice_<invoice_id>_day1 means that even if the payment failure webhook fires three times, only one day-1 email goes out. The second and third attempts hit the deduplication check and return a duplicate status rather than sending again.
2. Scheduled follow-ups that auto-cancel
Rather than setting up a cron job to check whether to send the day-3 email, use scheduled follow-ups that cancel automatically when the customer replies or when a condition changes.
POST /v1/agent/schedule-followup
Authorization: Bearer <token>
{
"contactEmail": "customer@example.com",
"threadRequestId": "<request_id_from_day1_send>",
"delayMinutes": 4320,
"cancelOnReply": true,
"triggerConditions": {
"requiresContext": {
"invoiceStatus": "unpaid"
}
}
}
cancelOnReply: true means if the customer responds to any message in this thread, the scheduled follow-up is automatically cancelled. You don't have to wire up reply detection - the infrastructure handles it.
When the follow-up fires, the agent re-evaluates current state before sending. If the invoice is now paid (because the customer updated their card independently), the condition check fails and the email doesn't go out. The sequence ends cleanly without a separate cancellation step.
3. Policy-enforced limits per contact
Even within a dunning sequence, there should be limits on how many emails a contact receives in a given window. A customer whose payment fails on the same day they received three other emails from your platform should not receive a fourth email the same day because dunning fired.
The policy engine enforces these limits at the infrastructure layer. You configure the cooldown window and the hourly/daily limits for the dunning mailbox. The dunning sequence doesn't need to manually check "how many emails has this customer received today" - the policy engine blocks the send if limits are exceeded and returns a structured response the agent can log and retry at the appropriate time.
4. Reply classification that stops the sequence
When a customer replies to a dunning email, the inbound intelligence pipeline classifies the intent:
{
"intent": "billing",
"confidence": 0.94,
"suggestedAction": "route_to_support",
"flags": ["needs_human_response"]
}
An intent: "billing" with suggestedAction: "route_to_support" is the signal to stop the automated sequence and create a support ticket. The agent can act on this classification:
- Cancel any pending scheduled follow-ups for this contact
- Create a support ticket or notify the team
- Suppress the contact from further dunning emails until the ticket is resolved
Without reply classification, the sequence continues firing while a customer is waiting for help. That's the scenario that turns a recoverable billing issue into a cancellation driven by customer service frustration.
5. Auto-stop on successful payment
The cleanest implementation listens for a payment_succeeded event and explicitly suppresses the dunning sequence for that invoice:
# When payment.succeeded fires
POST /v1/agent/outbound/request-send
{
"to": "customer@example.com",
"templateId": "payment-recovered",
"dedupeKey": "invoice_<invoice_id>_recovered"
}
# And cancel any pending follow-ups
DELETE /v1/agent/followups/<followup_id>
The payment-recovered email goes out once (deduplicated), any scheduled follow-ups are cancelled, and the sequence is done. The customer receives one confirmation that their payment was processed and their access continues - not three more billing emails after the fact.
The mailbox configuration for dunning
Dunning email has specific policy requirements that differ from transactional or marketing sends:
- Lower hourly limits: Dunning sequences should be paced. You want to recover the account, not make the customer feel hunted. An hourly cap of 1-2 dunning emails per contact per day is appropriate.
- Suppression list priority: Any suppression - global, domain-level, or campaign-level - should block dunning sends. A customer who has opted out of email should not receive billing email through a loophole.
- Audit trail: Every dunning send should be traceable. The decision trace for a dunning email should show the invoice ID, the sequence step, the policy evaluation, and the delivery result. When a customer disputes receiving (or not receiving) billing communication, you need this record.
These are all configurable at the mailbox level. Running dunning from a dedicated mailbox - separate from your marketing and transactional mailboxes - makes it easier to tune these settings independently and maintain separate sending reputation for billing communication.
Payment failure is not churn. Most customers who fail to pay want to keep their subscription. A well-built dunning sequence that auto-stops on recovery, respects suppression, handles replies, and doesn't over-send turns most of those failures into recovered revenue.
If you're building an agent-powered dunning workflow and want the deduplication, policy enforcement, and reply handling handled at the infrastructure layer, try Molted or read the API docs.
For context on how the scheduling and reply-cancellation features work, How AI Agents Handle Inbound Replies covers the inbound classification pipeline in more depth.