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.

Agentic Task
StripePostgreSQLResendFinanceEmail AutomationNotifications & Alerts

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?
By default it fires on each failed payment event from Stripe, so customers get a tone-matched email through the whole Smart Retries cycle. You can also scope it to only the final retry if you prefer fewer emails per customer.
What if the customer isn't in my Postgres database?
The agent falls back to the name and email on the Stripe customer record and still sends the recovery email. The mismatch is logged in the workflow output so you can clean up your database later.
Will it overwrite a customer who's already marked past due?
No. The update is idempotent, so re-runs against the same customer are safe. You won't accidentally double-email someone if Stripe re-delivers the same event.
Can I send from a different provider than Resend?
Yes. The same workflow works with SendGrid, Postmark, AWS SES, or Gmail. Just swap the send-email step when you generate the workflow.
Does this work for one-time charges or only for subscriptions?
Both. The trigger handles one-time charge failures and recurring subscription invoice failures, and the agent writes the email accordingly.

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.