SMS plus web chat. Postgres-enforced no-overlap. Owner confirms before the lock.
A booking agent that holds the slot for fifteen minutes — and asks before it commits.
Foyer runs an intake conversation over SMS or an embedded web widget, validates the address and service area, proposes a slot inside business hours, holds it against a Postgres EXCLUDE constraint, and waits for one-click approval from the shop owner before locking it in Google Calendar. No auto-booking. No ghost appointments. No drift.
The wedge
Every “AI booking bot” demo on the market either auto-books — which turns the shop into a ghost-appointment factory — or chats without touching a calendar, which produces nothing. Foyer is the honest middle. It holds the slot for fifteen minutes against a Postgres exclusion constraint, asks the owner to confirm, then locks it in Calendar. The owner stays in the loop, the customer gets a real confirmation, the shop stops paying for no-shows.
Customer side
Plain text — no app, no link, no signup.
Local-service customers don’t install apps. They text. Foyer runs the intake over SMS or web chat with the same agent behind both — service type, address, timing, photos if the issue needs them — then proposes a slot and stops. The owner is the only thing that can turn a hold into a booking.
Messages
(303) 555-0142
9:47 AM
Three surfaces, one conversation.
The customer texts or types. The agent collects what it needs, proposes a slot, and stops. The owner is the only thing that turns a hold into a booking.
SMS
Customer texts the shop's number.
Twilio webhook validates the signature, dedupes on MessageSid, dispatches a turn job to Redis, and returns 200 in under 500ms. STOP, START, and HELP are caught before any agent dispatch.
Web widget
Embedded chat on the shop's site.
Preact widget under 30KB gzipped, Shadow-DOM isolated so the host page's CSS can't leak in or out. Cross-channel resume requires phone OTP — number-match alone is a spoofing vector.
Owner inbox
One click confirms. One click rejects.
Filament admin shows the transcript, the address with a map preview, the photos, and the proposed slot. Confirm fires an Idempotency-Key-guarded request; the slot locks in Calendar, the customer gets a confirmation SMS, the conversation completes.
Owner side
The card that turns a hold into a booking.
One pending card per request, with the transcript, the geocoded address, the customer’s photos, and the slot the agent proposed. Confirm fires an idempotent request — double-clicks collapse to one Calendar update and one SMS. Reject releases the slot and re-engages the agent with alternatives.
Customer
+1 (303) 555-0142
SMS — phone verified
Service
Drain clearing — kitchen
Proposed slot
Thu Jun 19, 10:00 – 11:30 AM (MDT)
Address
1432 Oak St
Boulder, CO 80301
Geocoded · in service area
Try it
Text Anchor Plumbing's booking line to schedule.
Anchor Plumbing serves Boulder, CO (ZIPs 80301-80310). Open Mon-Fri 8am-5pm Mountain. Services: drain clearing, leak repair, water heater install. Text the number below to request service — the booking assistant will ask for the service type, address, and preferred time, then send a confirmation once the slot is held.
Text to book
(970) 469-8922Send any message describing the service you need — the assistant will reply to gather the details. Reply HELP for help, STOP to opt out at any time.
Message frequency is conversational and reply-only (typically 4-10 messages per booking session). Msg & data rates may apply. By texting this number you agree to the terms and privacy policy.
Web chat
The same widget that ships to customer sites — embedded directly on the demo page.
Open the demo →Pricing — illustrative
One shop, one number, one calendar.
Foyer is a portfolio demo, not a product on sale. The numbers below sketch what a real engagement would look like — a fixed-fee setup that replaces the blank page with a working agent, then a monthly retainer sized to one shop with one Twilio number and one Google Calendar.
Setup
$1,800
one-time
- — Twilio number provisioning + 10DLC walkthrough
- — Google Calendar OAuth wiring
- — Scope configuration — services, hours, area, lead time
- — Agent persona + welcome / confirm / out-of-scope copy
- — Owner inbox access, kill switch, runbook hand-off
Monthly
$240
per shop, per month
- — Hosting, monitoring, uptime watch
- — LLM token cost passed through at cost — typical $4-$12 / mo
- — Twilio SMS cost passed through at cost
- — Scope and persona edits — one round per month
- — Quarterly review of out-of-scope log for service expansion
These numbers assume one Twilio number, one Google Calendar, one human owner. Multi-location shops or shared-calendar shops are scoping work before they are pricing work.
Questions you'd ask before you'd buy.
Why a fifteen-minute hold and not auto-book?+
Auto-booking is the failure mode for this category — the bot agrees to anything, the calendar fills with appointments the owner never sees, the shop ends up sending a tech to an address where nobody's home. The fifteen-minute hold sits in the middle: the slot is reserved against a Postgres exclusion constraint so a second customer can't take it, and the owner has a window to confirm or reject before it becomes a real booking.
What happens if the owner doesn't confirm in fifteen minutes?+
The hold expires, the Calendar event is deleted, and the customer is texted that the slot is no longer available with an offer to check another time. Quiet hours apply — if the expiry would land at 11pm, the message is queued until morning.
What stops the bot from agreeing to something out-of-scope?+
Scope is configured per business and validated at write time — service types, service area as ZIPs or radius, business hours, blocked dates, lead-time bounds, quiet hours. The agent is a state machine constrained to those rules and refuses out-of-scope requests with a documented fallback. Garbage configs cannot be saved.
What about TCPA and 10DLC?+
STOP, START, HELP are caught before any agent dispatch and consent is keyed on the (customer phone, Twilio number) pair. Conversational reply is exempt from express-written-consent rules, but reactivation requires explicit START — no override. 10DLC brand and campaign registration are walked through in the setup docs.
What happens if the owner moves the Calendar event from their phone?+
Google Calendar events.watch push subscription notices the change and a reconciliation job updates the booking. If the push channel expires, a five-minute fallback poll catches it. The owner dashboard surfaces a sync-health indicator if drift exceeds threshold.
Why is there a Python sidecar?+
The agent state machine and LLM adapter live in FastAPI to keep retry, backoff, and circuit-breaker logic out of the request cycle, to make async LLM streaming additive without re-plumbing PHP, and because portfolio coverage of Python alongside PHP is the point. A documented Laravel-only fallback path is described in docs/architecture/why-fastapi.md for buyers who want to fork without the Python half.