Two SMS agents that hold a real conversation with a lead, answer questions from a knowledge base, and book the sales call straight into the calendar. One works the first call, the other the final call. Together they have booked 2,300+ calls since April 2026.
Paid leads arrived around the clock from Meta and Google. Sales reps were on calls or asleep. A lead who replied at 9pm with a question got an answer, if at all, the next afternoon.
An audit of 369 conversations in April 2026 found the lead had the last word in 53% of them. Each of those was a person who wanted to talk and was left hanging. The pre-existing fix was a drip sequence of canned texts, which produced replies that almost never turned into bookings.
The brief I set myself: answer every lead within a minute, in a way that reads like a person, and get a time in the calendar without a human in the loop.
Everything is TypeScript on Deno running as Supabase edge functions, with Postgres as the single store for leads, conversations, pending offers and bookings. Scheduled work runs on pg_cron. The LLM does classification and drafting; the rules around it decide what is allowed to be sent.
Each incident is written up with the date, the trigger and a regression query, and the query runs daily. The written rule is what stops the same mistake shipping a second time.
Bookings were the number I optimised first because they were the number I could see. Attendance on agent-booked first calls sits at 61%, which is high, but the agent books warm repliers, so that is selection as much as quality. Agent-booked leads also convert to paying customers at a lower rate than self-booked leads, which is what you would expect from a cohort that needed persuading. I would wire win-rate reporting by booking route into the first release, so the trade-off is visible from the start rather than discovered later.
I would also build the per-line send metering before the first number change, not after the flood.