Skip to content
Groundmail

Guides

Stripe failed payment emails: renewals, first payments and dunning

What Stripe emails when a subscription payment fails, which failures get no email, and a two-email dunning series that stops when the customer pays.

By GroundmailUpdated All guides →

Short answer

When a card payment on a subscription invoice fails, Stripe can email the customer after each failed attempt with a link to update their card: turn on Send emails when card payments fail in Stripe's revenue recovery settings. Your own code gets the same moment as the invoice.payment_failed event.

Two things those emails miss. A first or one-off payment that fails, for example when the bank's 3D Secure check isn't approved, fails before there's any invoice, so invoice-based emails never fire; listen for payment_intent.payment_failed instead. And one generic email per attempt isn't a series: to send two or three emails that stop the moment the customer pays, send your own from invoice.payment_failed and stop on invoice.paid.

What Stripe emails when a payment fails

SituationStripe emailSetting
Card payment on a subscription invoice failsOne email after each failed attempt, with a link to update the payment methodRevenue recovery → Send emails when card payments fail
A renewal needs the customer to confirm it (3D Secure)An email with a link to a Stripe page where they confirm the payment, plus optional remindersSend a Stripe-hosted link for customers to confirm their payments when required
An invoice you sent for payment isn't paidReminders on the schedule you setSend reminders if a recurring invoice hasn't been paid
The card on file is about to expireOne email a month beforeSend emails about expiring cards
A first or one-off payment failsNoneNo setting: send it yourself

From Stripe's automated customer emails docs. Each is off until you turn it on.

Last checked on each vendor's public page.

The link in Stripe's email goes to a Stripe-hosted page or your own billing page, where the customer can update their card and pay what's due. The email comes from stripe.com, or from failed-payments@ on your domain if you've verified a custom email domain in Stripe. Its look follows your branding settings; the wording is Stripe's.

The gap: first payments and 3D Secure

Stripe's failed payment emails, and any of your own that run on invoice.payment_failed, need an invoice. A lot of failures happen before there is one: the first payment at checkout, a one-off purchase, a top-up. Those run on a PaymentIntent, and when one fails Stripe fires payment_intent.payment_failed.

The common case is 3D Secure. The customer enters their card, their bank asks them to approve the payment in its app, and they don't, or the approval fails. The PaymentIntent records the error payment_intent_authentication_failure, the customer leaves, and nobody tells them what happened. To them it looks like your checkout was broken.

payment_intent.payment_failed also fires when a subscription renewal's charge fails. If you send renewal emails from invoice.payment_failed, make sure one failure doesn't send both. A simple rule: skip the PaymentIntent email when the customer already has an active or past_due subscription, because then it's a renewal and your invoice emails cover it.

Tell the customer what to do

"Your payment failed" leaves the customer guessing. The PaymentIntent's last_payment_error says why: a code, and for card declines a decline_code. Turn the common ones into one plain instruction:

Stripe codeWhat happenedWhat to tell them
payment_intent_authentication_failureThe bank's 3D Secure check wasn't completed or failedYour bank didn't approve the payment. Approve it in your banking app when it asks, or use another card.
insufficient_funds (decline code)Not enough money on the cardYour card didn't have enough funds. Use another card or try again after topping it up.
expired_cardThe card has expiredThe card on file has expired. Add your new card.
incorrect_cvcWrong security codeThe security code didn't match. Check the 3 or 4 digits on the card and try again.
Anything elseA decline without a clear reasonYour bank declined the payment. Try another card, or ask your bank why.

Code meanings from Stripe's error codes and decline codes.

Node: a reason line from the PaymentIntent
const REASONS = {
  payment_intent_authentication_failure:
    "your bank didn't approve the payment. Approve it in your banking app when it asks, or use another card",
  insufficient_funds: "your card didn't have enough funds. Use another card or try again later",
  expired_card: "your card has expired. Add your new card",
  incorrect_cvc: "the security code didn't match. Check it and try again",
};

export function failureReason(paymentIntent) {
  const err = paymentIntent.last_payment_error;
  const key = err?.decline_code ?? err?.code;
  return REASONS[key] ?? "your bank declined the payment. Try another card, or ask your bank why";
}

Keep it to that one line, and never pass on the bank's raw message. Some decline codes, such as fraudulent, lost_card and stolen_card, Stripe says to present to the customer as a generic decline, which the fallback line above does.

A two-email dunning series that stops when they pay

Dunning is the follow-up after a failed renewal. A short series works better than one email: the first is a heads-up, the second says what happens next. The rule that matters is that it stops the moment the invoice is paid, so nobody gets "your payment failed" after they've fixed it.

WhenEmailSays
On invoice.payment_failed1: Your payment didn't go throughThe amount, the card that failed (brand and last 4), the reason if you have it, one button to the hosted invoice page, and when you'll try again (next_payment_attempt).
3 days later, if still unpaid2: Update your card to keep your accountSame button, plus what happens if it stays unpaid, in your own terms: access paused, plan downgraded, or account closed on a date.
On invoice.paidStopCancel anything still waiting. Optionally send a short "you're all set".
Node: start and stop the series from the webhook
switch (event.type) {
  case "invoice.payment_failed": {
    const invoice = event.data.object;
    // Stripe can retry the charge several times; start the series once per invoice.
    if (await dunning.isRunning(invoice.id)) break;
    await sendEmail(`dunning-1:${invoice.id}`, firstNotice(invoice));
    await dunning.start(invoice.id, {
      customer: invoice.customer,
      secondAt: Date.now() + 3 * 24 * 60 * 60 * 1000,
    });
    break;
  }
  case "invoice.paid": {
    await dunning.stop(event.data.object.id); // the scheduled second email is never sent
    break;
  }
}

// A scheduled job, every few minutes: send the second email for series still running.
for (const run of await dunning.due()) {
  const invoice = await stripe.invoices.retrieve(run.invoiceId);
  if (invoice.status === "paid") { await dunning.stop(run.invoiceId); continue; }
  await sendEmail(`dunning-2:${invoice.id}`, secondNotice(invoice));
  await dunning.finish(run.invoiceId);
}

dunning is a small table of runs keyed by invoice id; sendEmail posts to your email API with its first argument as the Idempotency-Key, so a retried event or a re-run job can't send the same email twice (full handler). Checking the invoice again before the second email covers a invoice.paid event that arrives late: Stripe doesn't guarantee event order.

The button should go to the invoice's hosted_invoice_url, where the customer can enter a new card and pay that invoice right away. That's better than "log in and update your billing details", which still leaves them waiting for the next retry.

Or start from the Stripe starters

With the Groundmail Stripe integration connected (Integrations → Stripe → Connect with Stripe), Automations → New → From Stripe has two starters for this page:

  • Payment failed: starts on stripe.invoice.payment_failed, sends two emails 3 days apart, and stops when invoice.paid arrives for that customer.
  • Payment didn't go through: starts on stripe.payment_intent.payment_failed, waits 30 minutes, sends one email that says why, at most once a day per customer, and stops if they pay.

Emails can use {{event.data.invoice.amount}}, {{event.data.card.brand}}, {{event.data.card.last4}}, {{event.data.next_attempt}}, {{event.data.invoice.url}}, {{event.data.payment.amount}} and {{event.data.payment.reason}}, which is already written as an instruction like the ones above. They go out from your verified domain on the transactional stream, apart from any marketing mail. Both open as drafts. If you run both, point the second one's button at your own billing page and keep it for first payments, or turn on just one, so a failed renewal doesn't send both.

Copy that gets cards updated

  • Subject says what happened: "Your Example payment didn't go through", not "Action required" or "Urgent".
  • One button, one job: "Update card and pay $29.00", linking to the hosted invoice page.
  • Name the card: "Visa ending 4242" tells them which card to fix.
  • Give a date: when you'll try again, and when access changes if it stays unpaid.
  • No blame: expired cards and bank checks happen to everyone. Keep the tone of a helpful reminder.
  • Send it like a receipt: from your domain, on the same transactional stream as receipts and sign-in codes, never from a newsletter tool.

Frequently asked questions

Does Stripe email customers when a payment fails?

For subscription invoices paid by card, yes, if you turn on Send emails when card payments fail in revenue recovery settings. It emails after each failed attempt. It doesn't email when a first or one-off payment fails.

Why didn't the customer get an email when their first payment failed?

A first or one-off payment fails on the PaymentIntent before any invoice exists, so invoice-based emails (Stripe's or your own on invoice.payment_failed) don't fire. Listen for payment_intent.payment_failed and send your own.

What does payment_intent_authentication_failure mean?

The payment method failed authentication, usually because the customer didn't complete or pass their bank's 3D Secure check. Ask them to approve the payment in their banking app, or use another card.

How many dunning emails should I send?

Two or three is common: one when the payment fails, one a few days later saying what happens next, and optionally a last notice before access changes. Whatever the number, stop as soon as invoice.paid arrives.

Can I change the text of Stripe's failed payment email?

You can set its branding and where the button links. To use your own wording and timing, turn it off and send your own from invoice.payment_failed.

Sources

Facts on this page about other companies' products, prices and policies come from these public pages. They change, so check them before you decide.

Email for apps. Set up by your agent.

Sign-in codes, receipts and alerts kept separate from marketing mail, with inbound, templates and automations included. Start free with 3,000 emails a month, and move to a dedicated IP or server without changing your code.