Start here
Vasooli chases overdue B2B invoices on a bounded, auditable schedule. It works out why each invoice is unpaid, writes a reminder in a tone that matches, stops the moment a customer promises to pay or disputes the bill, and stops permanently the moment money actually arrives — confirmed by a signed Razorpay webhook, never by a model's opinion.
The AI cannot touch money
A language model reads customer replies and drafts reminder copy. It cannot mark an invoice paid, change an amount, pause recovery, or send anything — not because the prompt asks it not to, but because the code it would need is unreachable from the AI layer. That boundary is enforced by a test that parses the import graph and fails the build if it is ever crossed.
- backend/tests/architecture/test_layering.py
- backend/tests/integration/test_disputes.py
- backend/app/policy/disputes.py
The first proves the AI layer cannot import a mailer, a database session, or the payment client. The second asserts that when recovery pauses, the pause is attributed to the policy engine and the reading to the AI — two separate actors in the audit trail. The third is the decision itself: a pure function, no model involved.
What is real, and what is not
| Payment links, reconciliation, webhooks | Real Razorpay — test mode |
| Reminder emails | Real, sent through Resend from this domain |
| Customer replies | Real inbound email, signature-verified |
| Reason diagnosis, reply reading, drafting | Real Gemini, with deterministic fallbacks |
| Recipients | Redirected to the operator inbox, not customers |
| The ledger | Eight seeded demo invoices, not real merchants |
The amber bar at the top of every dashboard page reports these modes live. It turns red if the system is ever pointed at real customers or live payment keys.
Where to click
- 1Open the recovery queueEvery row answers “why is this happening?” in one sentence — no need to open anything to understand the state of the ledger.
- 2Open the invoice marked DisputedThis is the one worth seeing. Recovery is paused, and the card shows the customer’s own words beside the AI’s reading of them, the claims it extracted, and how confident it was.
- 3Scroll to the conversationEvery reminder, reply, AI reading, policy decision and payment event in order — colour-coded by who acted.
- 4Open the audit logAppend-only, enforced by a database trigger rather than convention. Nothing in the application can edit or delete a row.
Safe to explore. Reviewer accounts are read-only at the framework level — they cannot run a cycle, resolve a dispute, or send an email, whatever they click. If you were given an operator account instead, the grey Dry run button evaluates the whole cadence and sends nothing.
How well it actually works
An evaluation harness runs three strategies over 150 invoices and 45 simulated days: no chasing, a naive chaser, and Vasooli.
| Naive | Vasooli | |
|---|---|---|
| Recovered, by value | 85.0% | 65.1% |
| Contacts per invoice | 5.17 | 1.10 |
| Compliance breaches | 92 | 0 |
The naive chaser recovers more. It does so by contacting every customer five times and never stopping — including customers who already paid and customers disputing the bill. Vasooli recovers 77% of that with a fifth of the contacts and no breaches of its own rules. The claim is not “recovers the most”; it is “recovers most of it without behaviour you would be embarrassed to defend.”
What this is not
A single-merchant system running on test-mode payment keys. It is not multi-tenant, has no billing, and has never processed a real customer's money. What production would require is written up honestly in the repository rather than implied here.