n8n · Twilio · OpenAI · Supabase · Cal.com

Lead SMS Booking Engine

A multi-tenant n8n workflow that captures inbound leads from four channels, qualifies them over multi-turn SMS with GPT-4o-mini, books into Cal.com, logs to Google Sheets, and chases the leads that go quiet.

43workflow nodes
4intake channels
3nudges before cold
2.38.5n8n verified

The problem

A missed call at a plumbing company is a lost job. The lead calls the next number on the list within minutes. Most small businesses answer hours later, or never — and a booking form on a website catches only the people patient enough to fill one in.

This closes that window. Every inbound signal — an unanswered call, a text, a form, a lead-notification email — turns into an SMS conversation within seconds, and that conversation runs until it produces a calendar booking or a clear no.

How it works

Missed call ──┐
Inbound SMS ──┤
Web form ─────┼──► Normalize ──► Resolve tenant ──► Authenticate ──► Send budget
Lead email ───┘                        │                    │             │
                                       └─ unknown ──┬── forged            │ within
                                                    ▼                     ▼
                                                error_log ◄── capped   GPT-4o-mini
                                                                          │
                     ┌──── booking ready ────┐                     Twilio reply
                     │                       │                            │
               Cal.com booking           (keep asking)           Persist the turn
                     │                       │                            │
                     └────► Google Sheets ◄──┘        30-min sweep ──► re-engage

All four channels converge on one normalizer, so there is a single conversation engine rather than four parallel implementations. Tenant identity comes from the Twilio number the lead contacted, which is what makes one workflow serve many client businesses.

Booking gate

The model proposing a time is not enough to book. A booking fires only when there is both a confirmed start time and a collected email address — Cal.com rejects bookings without one. A confirmed intent missing either is downgraded and the bot keeps asking.

A failed booking still lands in the sheet as Booking Failed and the thread stays active, rather than the lead silently disappearing.

Not trusting the caller

Two of the intake endpoints are public and name their tenant in the request body. Left open, anyone who found the URL could pick both the business and the destination number, and have a text sent from that business's own phone number. That is not a billing problem — it is the client's liability, at statutory damages per message.

So nothing is taken on trust. Web-form leads carry a per-tenant secret. Twilio's own webhooks are checked against X-Twilio-Signature, recomputed and compared in constant time. Both gates fail closed: no secret, no token, no send.

Signature checking is the only thing standing between a forged From field and a text sent from a client's number. It is on by default and a tenant has to opt out of it deliberately.

Authentication decides whether a request is real. It does not bound what happens when a real-looking one is wrong — a leaked secret, a runaway loop, a plain bug. So sends are capped too: per business, and more tightly per person. No single lead can be messaged more than four times an hour or ten times a day, whatever the rest of the system believes.

Going cold

A sweep runs every 30 minutes and nudges leads who stopped replying.

EventStatusNext nudge
Lead repliesnurturing+2h — ladder resets
Nudge 1 sentnurturing+24h
Nudge 2 sentnurturing+72h
Nudge 3 sentdeadnone
Booking createdbookednone

A reply at any point resets the ladder, so a slow responder gets the whole sequence again instead of falling off after one late answer.

Quiet hours are enforced in the tenant's timezone, not the server's — 08:00–21:00 by default, per TCPA. A sweep that ignores this texts people at 3am.

Compliance

STOP, STOPALL, UNSUBSCRIBE, CANCEL, QUIT, END, REVOKE and OPTOUT mark the thread opted out and stop every future nudge. START and UNSTOP restore it.

Opt-out is scoped to the lead and the tenant's number, so opting out of one client never silences another. yes is deliberately not filtered — it is the single most common way a lead confirms an appointment.

Built to be regenerated

The workflow JSON is generated, not hand-edited. A build script is the source of truth, and a test suite runs the real node code against real payloads.

node tools/verify-all.js     # rebuild, then run every check

That suite caught six defects static review missed. One was $json used in three all-items Code nodes, which n8n only defines in per-item mode — and one of those was the error handler, so failures would have vanished instead of being logged. Another was a booking body that Cal.com rejects only when the model happens to be terse, which would have read as intermittent. The last two were the open endpoints above.