Skip to main content
By the end of this guide, three revenue-critical emails send automatically from Stripe events: a payment-failed notice when a charge fails, a win-back when a subscription is canceled, and a trial-ending reminder before a trial lapses — each from a dashboard template you can rewrite without a deploy. Stripe already emails receipts and invoices, so don’t rebuild those. What Stripe doesn’t send well is the branded, copy-sensitive lifecycle mail: a multi-step dunning nudge, a “we’d love you back” after cancellation, or a trial reminder on your own schedule. That copy changes constantly — exactly what belongs in a template, not a redeploy.
You’ll need: a SenderKit account with an API key, a Stripe account with subscriptions or Checkout live, and a Next.js app. Use an sk_test_ SenderKit key and Stripe test mode while you build.
1

Author the three templates

Create three email templates in the dashboard. Suggested slugs and variables:Write the copy for each — these are the messages you’ll iterate on most, so lean on AI authoring for a first draft and publish when ready.
2

Install dependencies and configure clients

lib/senderkit.ts
3

Stand up the Stripe webhook route

Stripe signs every webhook, and you must verify the signature against the raw request body. In the Next.js App Router, await req.text() gives you the untouched body — there’s no bodyParser config to disable (that was the Pages Router).
app/api/webhooks/stripe/route.ts
Webhook delivery is at-least-once — Stripe will retry, and the same event can arrive more than once. Every send() below carries a stable idempotencyKey so a replay doesn’t double-email the customer.
4

Send on a failed payment (dunning)

The invoice.payment_failed event carries everything you need directly on the invoice — including customer_email, so no extra lookup is needed.
Keying on ${invoice.id}:${invoice.attempt_count} means each retry attempt gets its own email, but a duplicate delivery of the same attempt does not.
5

Win back a canceled subscription

A subscription object doesn’t carry the email, so look up the customer.
6

Remind before a trial ends — as a scheduled sequence

customer.subscription.trial_will_end fires roughly three days out. Send the reminder now, and use scheduledAt to queue a final nudge for the day before the trial lapses — one event, a two-touch sequence.
The final variable lets the one template render two ways — use a show-if block ({{#final}}…{{/final}}) to sharpen the copy on the last reminder.
7

Verify it works

Forward events to your local route and trigger one with the Stripe CLI:
With your sk_test_ key, watch the message move through its lifecycle in the SenderKit dashboard — no real email sent.
8

Go live

Register a production endpoint in the Stripe Dashboard → Webhooks, set its whsec_ secret in your environment, and swap your SenderKit key to sk_live_. Same code, real delivery.

What’s next

Welcome on signup

The other end of the lifecycle — greet new users automatically.

Scheduling sends

How scheduledAt defers delivery up to 30 days out.

Idempotency

Why every webhook handler needs a stable key.

Messages

Filter by metadata to find a customer’s sends.