Recover failed Stripe payments with Postgres context and Resend
When a Stripe payment fails, look up the customer in your Postgres database, mark the account past due, and send a personalized recovery email from Resend.
Build me an agent workflow that recovers failed Stripe payments by enriching them with customer context from my Postgres database and sending a personalized recovery email through Resend.
Trigger: a Stripe webhook listening for invoice.payment_failed and charge.failed events.
When the webhook fires, the agent should:
1. Pull the Stripe customer ID, invoice or charge amount, currency, failure reason, and next retry date out of the event payload.
2. Use PostgreSQL Find Row to look up the matching account in my customers table by stripe_customer_id. The lookup should return the customer's name, primary contact email, plan tier, signup date, and last successful payment date.
3. Use PostgreSQL Update Row on that same row to set account_status to 'past_due' and stamp payment_failed_at with the current timestamp. Keep the update idempotent so re-deliveries of the same Stripe event don't churn the row or trigger duplicate emails.
4. If the event includes a subscription ID, optionally call Stripe Retrieve Subscription to get the current period end and next billing date so the email can reference real dates instead of vague language.
5. Compose the dunning email. Tone and personalization is where the agent earns its keep over a static template. Shape the copy by plan tier (self-serve trial gets a light touch, paid customer gets warmth, enterprise gets a more formal note), tenure (a brand-new account vs. a multi-year customer), and the specific failure reason from Stripe (declined card, expired card, insufficient funds, do_not_honor, etc.). The email should acknowledge the failure plainly, explain when Stripe will retry, link to the billing portal so the customer can update their card, and sign off from the billing or success team.
6. Send the email via Resend Send Email to the contact email from the Postgres row, falling back to the email on the Stripe customer object if the database lookup missed.
Edge cases: if the Postgres lookup finds no matching row, still send the recovery email using the Stripe customer name and email, and return the mismatch in the workflow output so we can reconcile later. If the row is already marked past_due with a payment_failed_at within the last 24 hours, skip the email and just log the additional failure so we don't spam the customer through Stripe's retry cycle.
Output: a structured summary with the customer ID, plan tier, failure reason, whether the database lookup hit or missed, whether the email was sent or skipped, and the Resend message ID.
Additional information
What does this prompt do?
- Fires the instant a Stripe charge or invoice payment fails, so dunning never waits on a manual export.
- Looks up the matching account in your Postgres customer table and marks it past due with a timestamp, so the rest of your product knows the customer is at risk.
- Pulls the customer's name, plan, and last successful payment date from your database to write an email that actually sounds like it knows them.
- Sends a warm, on-brand recovery email through Resend with the retry schedule and a link to update their card, with tone shaped by plan tier and tenure.
What do I need to use this?
- A Stripe account in live or test mode
- A Postgres database with a customers or accounts table that includes the Stripe customer ID, plan, and a status column the workflow can update
- A Resend account with a verified sending domain
- A billing portal or update-card URL to include in the email
How can I customize it?
- Change which columns get stamped on failure, for example past_due, delinquent, or a numeric grace counter.
- Edit the email tone, signoff, and which billing portal link is included for each plan tier.
- Add a higher-touch path for paid or enterprise customers, like skipping the email and opening a CSM task instead.
- Scope the trigger to only the final Smart Retry instead of every failed attempt if you'd rather email less often.
Frequently asked questions
Does this fire on every failed attempt or only after Stripe gives up retrying?
What if the customer isn't in my Postgres database?
Will it overwrite a customer who's already marked past due?
Can I send from a different provider than Resend?
Does this work for one-time charges or only for subscriptions?
Stop losing revenue to silent card failures.
Connect Stripe, Postgres, and Resend once, and Geni handles every failed payment with a personalized recovery email and a clean record in your database.