Inspiration

Unpaid invoices are one of the most common ways a healthy small business dies. The work was delivered, the money was earned, and it simply never arrives — and chasing it is the task every owner postpones, because it's awkward, repetitive, and easy to do badly. Collections agencies want 25–50% and treat the customer relationship as disposable. Most owners just write the invoice off.

We wanted to find out whether an AI could do the part humans avoid: follow up on a schedule, stay professional at contact number five, and know exactly when to stop.

What it does

Ledgerhound takes a small business's aged-receivables export and works each overdue invoice autonomously.

For every invoice, on every cycle, Gemini decides the next action — send, wait, offer a payment plan, escalate to the owner, or stop — and then drafts the message. It reads every reply and classifies intent: promise to pay, dispute, partial offer, already paid, hostile, unsubscribe. A promise reschedules the follow-up past the promised date. A dispute halts the sequence immediately and hands the invoice back to the owner.

Every email carries a Stripe payment link, so paying is one click. When Stripe fires the webhook, outreach stops automatically.

The business model is a $99/month platform fee plus a percentage of cash actually recovered — we only do well when the merchant gets paid.

How we built it

One Cloud Run service holding the API, the agent, and the dashboard. Firestore for state. Cloud Scheduler drives the loop every five minutes.

The agent is three Gemini calls via the Gemini API (Vertex AI-ready for production — same SDK, one flag), all using structured output against Pydantic schemas — so there is no parsing layer anywhere in the system. Gemini 3.5 Flash makes the outreach decision; Gemini 3.5 Flash-Lite handles classification and review:

  1. Decide — full invoice state, correspondence history, and the merchant's policy envelope go in; a typed action comes out.
  2. Classify — inbound replies mapped to a seven-way intent enum with extracted amounts and dates.
  3. Review — an adversarial pre-send check that reads the drafted email against the policy and can reject it. AI checking AI, both calls logged.

Between the model and the outside world sits a deterministic policy guard written in plain Python with zero LLM calls: contact caps, minimum interval between messages, quiet hours and weekends in the debtor's timezone, payment-plan limits, a tone ceiling, and a banned-claims regex covering legal threats and credit-bureau references. The model proposes; the guard disposes.

The decision ledger

Every Gemini call writes an append-only row: the model, prompt and policy version, the structured output, the reasoning, the confidence, the guard's verdict, latency, and tokens. Nothing is ever deleted.

The invariant we held to: the ledger is never quieter than the agent. A vetoed action is logged exactly as loudly as a taken one, so the record shows the agent being overruled by its own safety layer.

The dashboard's headline number falls straight out of it: the percentage of actions taken with no human in the loop. Every row has an Override button, which is what makes that percentage mean something — the owner could intervene on any decision.

Challenges we ran into

Getting the stop conditions right mattered more than getting the persuasion right. An agent that recovers slightly less money is fine; an agent that keeps pressing someone with a legitimate dispute destroys the merchant's customer relationship and our credibility at once. So the classifier is deliberately biased toward dispute on ambiguity, and disputes hard-stop before any other logic runs.

The second was resisting the urge to let the model own the guardrails. Tone caps and contact limits live in code, not in the prompt, because a prompt is a request and a regex is a rule.

Current status — honestly

This was built inside the hackathon window. The system is complete and runs end to end, but it ships with DRY_RUN enabled: the full loop executes — Gemini decides, the guard rules, the ledger fills — and no email is sent until a human reads the drafts and turns it off.

We have not yet onboarded a paying merchant, and we have earned no revenue. We are not going to claim otherwise on a judged entry. What exists today is a working autonomous agent, a complete audit trail of its decisions, and a pricing model we believe in. The users and the revenue are the next step, not a finished one.

What we learned

Autonomy is only legible if you instrument it. We built the decision ledger before the first Gemini call, and that ordering turned out to be the most important decision in the project — it's the difference between claiming an AI runs the business and being able to show it, row by row.

What's next

Onboard the first merchants from aged-receivables exports, targeting invoices 60–120 days overdue — the ones that either pay under real pressure within days or never. Then: voice follow-up for the final contact in a sequence, direct QuickBooks and Xero sync to replace CSV upload, Stripe Connect so merchant payouts settle automatically, and a learned per-debtor model of what actually gets a specific business to pay. That signal is already sitting in the ledger.

Built With

  • cloud-scheduler
  • fastapi
  • firestore
  • google-cloud-run
  • google-gemini
  • pydantic
  • python
  • resend
  • stripe
  • vertex-ai
Share this project:

Updates