SmartReply
In productionMissed-call auto-SMS for small businesses. When nobody can answer, the owner gets an alert, the caller gets a text back, and the lead is recorded.
- FastAPI
- Supabase
- Postgres RLS
- Twilio
- Stripe
- Docker
- Nginx
- pytest
The problem
A tradesperson up a ladder cannot answer the phone, and the person ringing usually tries the next number on the list. A missed call is often a missed job.
The approach
One FastAPI service reacting to Twilio's call status webhook, with Postgres doing the consistency work. Multi-tenant from the start: each business has its own Twilio subaccount and number, so the number that was called identifies the tenant.
Architecture
- Caller
- Twilio number
- Nginx, HTTPS
- FastAPI webhook
- Supabase Postgres
- SMS to owner and caller
Walk through every decision on this request path further down the page.
AI's role
Built with AI coding assistants throughout. The commit history shows the webhook engine, invites, auth and Stripe billing landing over two days of sprints in February 2026. A hardening pass in May went back over all of it, and that is where most of the fixes below came from.
My role
Setting the constraints: one service, no queues or extra infrastructure, tenant isolation on every query, and a webhook that must never fail loudly. I wrote those down as project rules for the AI assistant, so every change starts from them.
Challenges
- Twilio can deliver the same event more than once. A database function now acquires a per-caller cooldown atomically, so a caller cannot be texted twice in quick succession.
- A route ordering bug where
/contacts/allwas being matched as a phone number by/contacts/{phone_number}. - The admin basic-auth check was not actually rejecting bad credentials. Admin routes are now gated at Nginx instead.
Security and quality
- Twilio signatures validated per tenant, with that tenant's own token.
- Row level security on every table. Anonymous access denied; signed-in users read only their own business's rows.
- Stripe webhook idempotency, a CSRF origin check on checkout, phone numbers masked in logs.
- A pytest suite covering the webhook's short-circuit paths and helpers.
What I would take forward
The first version worked. The second version was safe. Idempotency, log redaction and tests all arrived in a hardening pass, and they belong in the first one.