{"slug": "how-i-built-an-ai-dunning-agent-that-recovers-failed-payments-on-autopilot", "title": "How I Built an AI Dunning Agent That Recovers Failed Payments on Autopilot", "summary": "A developer built Dunning, an AI-powered agent that automates recovery of failed subscription payments, for the All Things Agentic Hackathon. The full-stack application uses a Gemini-based negotiator to send recovery emails, handle debtor replies, and offer payment plans without human intervention. The developer detailed the technical stack and common deployment pitfalls encountered during development.", "body_md": "I built Dunning for the All Things Agentic Hackathon. This post covers why I made it, how it works under the hood, and the mistakes I made along the way. I am publishing this piece of content for the purpose of entering the All Things Agentic Hackathon on Devpost.\n\nIf you run a SaaS business, you lose money to failed payments every single month. A credit card expires. A bank declines a charge. The customer never even knows it happened, and you just lost recurring revenue.\n\nThe industry calls this involuntary churn, and most companies either ignore it or send a couple of generic retry emails that get buried in inboxes. Collections agencies charge 25-30% of what they recover. That felt broken to me.\n\nDunning is a full-stack web application with an AI agent at its core. When a payment fails, the agent takes over. It figures out who the debtor is, looks at their timezone and jurisdiction, picks the right tone and legal language, and sends a sequence of recovery emails. If the debtor responds, the agent negotiates directly - it can offer payment plans, small discounts, or just answer questions about the invoice.\n\nThe whole thing runs without human intervention. You connect your billing platform, set your rules, and the agent handles the rest.\n\nYou can see it live at [dunning.website](https://www.dunning.website).\n\n**Frontend:** Next.js 14, deployed on Vercel. Server components, a dashboard for managing invoices and rules, a billing page where customers can pick their subscription tier.\n\n**Backend:** Python/FastAPI, deployed on Google Cloud Run. This handles authentication (Clerk), the REST API, webhook processing, and orchestrates the AI agent.\n\n**AI Agent:** This is the heart of it. The negotiator agent uses Gemini through Google Cloud. It has access to tools like generate_recovery_link (creates a real checkout page via DodoPayments) and send_email (sends through Resend). The agent decides when to escalate tone, when to offer a discount, and when to stop emailing entirely.\n\n**Database:** PostgreSQL on Cloud SQL. Stores invoices, dunning rules, organization policies, success fees, and the full conversation thread between the agent and each debtor.\n\n**Payments:** DodoPayments for subscription billing and checkout link generation. Each recovery email includes a unique payment link that the debtor can click to pay immediately.\n\nThe agent is not a chatbot. It runs in the background on a cron schedule. Every cycle, it:\n\nIf the debtor replies to any of these emails, the response comes in through a Resend webhook. The agent reads the reply, generates a response, and continues the conversation. It can negotiate - if the debtor asks for a payment plan, the agent evaluates whether the organization's rules allow it and responds accordingly.\n\n**Database schema drift.** I added new columns to the SQLAlchemy model (jurisdiction, debtor_country, debtor_timezone) but forgot to run the Alembic migration on the production database. The invoices page returned a 500 error for hours before I tracked it down to a missing column. The fix was a manual ALTER TABLE against the Cloud SQL instance.\n\n**Hardcoded API URLs.** My billing portal endpoint was manually constructing URLs to the DodoPayments live API instead of using the Python SDK. When I switched from the live API key to the test key, those hardcoded URLs broke with a 401. I replaced every requests.post call with the official SDK methods, which respect the environment setting automatically.\n\n**Secret overwriting.** My GitHub Actions workflow used gcloud run deploy --set-secrets which replaces all secrets on the service. One deploy wiped out the database URL, Clerk keys, and everything else. I changed it to --update-secrets which only adds or updates the ones you specify.\n\nThe agent currently creates a new DodoPayments product for every single recovery email. That works, but it clutters the product catalog. A better approach would be to create products once and reuse them.\n\nI would also add a proper queue (Cloud Tasks or Pub/Sub) instead of running the agent on a cron timer. Right now, if the cron job takes too long, it could overlap with the next one.\n\nThe project is live at [dunning.website](https://www.dunning.website). If you run a SaaS and failed payments are quietly eating your revenue, give it a look.\n\n*Built for the All Things Agentic Hackathon using Gemini, Google Cloud Run, and the Agent Development Kit.*", "url": "https://wpnews.pro/news/how-i-built-an-ai-dunning-agent-that-recovers-failed-payments-on-autopilot", "canonical_source": "https://dev.to/kirthi-sagar/how-i-built-an-ai-dunning-agent-that-recovers-failed-payments-on-autopilot-fpl", "published_at": "2026-08-31 00:28:14+00:00", "updated_at": "2026-08-31 01:21:34.982457+00:00", "lang": "en", "topics": ["ai-agents", "ai-products", "developer-tools", "generative-ai"], "entities": ["Dunning", "Gemini", "Google Cloud", "Next.js", "FastAPI", "DodoPayments", "Resend", "Clerk"], "alternates": {"html": "https://wpnews.pro/news/how-i-built-an-ai-dunning-agent-that-recovers-failed-payments-on-autopilot", "markdown": "https://wpnews.pro/news/how-i-built-an-ai-dunning-agent-that-recovers-failed-payments-on-autopilot.md", "text": "https://wpnews.pro/news/how-i-built-an-ai-dunning-agent-that-recovers-failed-payments-on-autopilot.txt", "jsonld": "https://wpnews.pro/news/how-i-built-an-ai-dunning-agent-that-recovers-failed-payments-on-autopilot.jsonld"}}