Part 4 of 7 · Dunning recovery series ~7 min read

How a dunning email gets sent

The retry does the recovering; the email does the asking. This post is about the message that goes out at each attempt — branded, in your voice, gentle on the first nudge and clear about the consequence by the last — with a one-tap link to update the card, sent through SES, and with bounces and complaints fed back so the system stops emailing a dead address.

Key takeaways

  • A branded dunning email goes out at every retry attempt, from your verified SES sender — not a generic decline notice.
  • One Bedrock Haiku 4.5 call writes the copy in your voice, escalating from a gentle nudge to a clear consequence.
  • Every email carries a one-tap update-card link — a signed, single-use URL into the processor’s hosted card page.
  • Bounces and complaints are fed back through an SES configuration set, so the system stops emailing a dead address.
  • The email only ever asks and informs; clicking nothing in it charges anything — the retry does the charging.

The retry charges; the email asks

It’s worth being precise about the division of labour. The retry (Part 3) is what actually recovers the money — it asks the processor to charge the saved card again. The email does something different and just as important: it tells the customer what’s happening and gives them a way to help. Most failed renewals recover not because the third automatic retry finally cleared, but because somewhere between retries the customer read a polite email and updated their card. The email is the human-facing half of the recovery.

That’s why a generic, processor-default decline email is a wasted opportunity. It looks like spam, often lands in promotions, and gives no sense that a real business is behind it. The dunning email here goes out from your verified domain, with your logo, in your voice — so the customer recognises it as you and acts on it.

Copy that escalates gently

The four emails are not the same message sent four times. They escalate — carefully. The first is reassuring: cards fail all the time, no drama, here’s the link. The middle two are a touch more direct. The last one is clear about the consequence without being a threat: if this final attempt doesn’t go through, access will pause, and here’s exactly how to avoid that. Getting that tone right four times, in your brand voice, is the one place a model earns its place in this system.

dunr-mailer reads a short HTML template and a tone brief from the dunr-assets S3 bucket, then makes a single Bedrock Claude Haiku 4.5 call — a few hundred tokens — passing the attempt number, the plan name, the amount, and the retry date, and asking for subject and body copy at the right escalation level. The model writes the words; it does not decide anything. The amount, the dates, the link, and whether to send at all are all fixed by the deterministic code around it. If the Bedrock call fails or times out, the mailer falls back to a plain templated version with the same facts — the customer always gets the email, even if it’s the unstyled-copy version.

How a branded dunning email is assembled and sent at each attempt A left-to-right flow inside a dotted AWS account container. On the left, a trigger box labelled "Retry attempt N" feeds the dunr-mailer Lambda in the centre. Above dunr-mailer, an S3 bucket labelled dunr-assets supplies the HTML template, the logo, and the tone brief; an arrow runs down into the mailer. To the upper right of the mailer, a Bedrock box labelled "Claude Haiku 4.5, Global cross-Region inference" receives the attempt number, plan, amount, and retry date, and returns subject and body copy in the brand voice; a dotted note beside it reads "falls back to plain template if it fails". Below the mailer, a Secrets Manager box supplies the HMAC signing key used to build a signed, single-use update-card link. The mailer assembles the email and sends it through SES, drawn as a box on the right labelled "SES, configuration set dunr-mail". From SES an arrow leaves the container to a "Customer inbox" box outside, showing a branded email with an Update card button. A feedback arrow returns from SES through an SNS topic into a small "suppression list" store, labelled "bounces and complaints stop future sends". A note at the bottom reads: the email only asks and informs — nothing in it moves money; the retry does the charging. AWS account · eu-west-2 Retry attempt N dunr-mailer assembles email, signs card link dunr-assets (S3) template, logo, tone Bedrock Claude Haiku 4.5 subject + body copy Secrets Manager link-signing key SES config set dunr-mail Customer inbox branded email [Update card] Suppression list via SNS feedback bounce / complaint The email only asks and informs — nothing in it moves money; the retry does the charging.
Fig 4. Assembling a dunning email. The mailer pulls the template from S3, gets brand-voice copy from one Bedrock Haiku call, signs a single-use update-card link, and sends through SES. Bounces and complaints feed a suppression list so the system stops mailing dead addresses.

The single most useful thing in the email is the button that lets the customer fix their card in two taps. The system never handles card details itself — that would drag it into PCI scope for no reason. Instead, the link is a signed, single-use URL that lands the customer on the processor’s own hosted card-update page (a billing portal session or a setup link). dunr-mailer builds it with an HMAC over the customer id, the subscription id, and an expiry, keyed from a secret in Secrets Manager, so a forwarded or leaked link can’t be reused after it’s spent or expired. The customer taps it, updates their card on the processor’s secure page, and the processor fires a payment_method.updated webhook — which (Part 2) tells the system to retry now rather than waiting for the next scheduled slot.

Not emailing a dead address forever

Sending mail responsibly means listening to what comes back. The SES configuration set dunr-mail routes bounce and complaint notifications to an SNS topic, and a small handler writes the affected address to a suppression list. If a customer’s email hard-bounces (the mailbox doesn’t exist) or they mark a dunning email as spam, the system stops sending to that address — the retries still run silently, but no more email goes to a place that doesn’t want it. This protects your sender reputation, which is what keeps the next customer’s dunning email out of their spam folder. It’s also why the system is outbound-only on SES: there’s no inbound mail to receive, unlike some other systems in this series.

Design rules that shaped the email

  • It looks like you. Verified domain, your logo, your voice — never a generic decline notice.
  • It escalates gently. Reassuring first, clear about the consequence last; the model writes the words, not the facts.
  • The model only writes copy. Amounts, dates, the link, and whether to send are all deterministic.
  • No cards, ever. The update-card link is a signed, single-use URL to the processor’s own hosted page.
  • Listen to bounces. A configuration set feeds a suppression list, so the system never mails a dead address.
  • Always falls back. If Bedrock is unavailable, a plain templated email with the same facts still goes out.

Next: the one thing the customer actually feels — how access pauses after the final failed retry, and how it’s restored the instant a payment succeeds or a card is updated.

All posts