Foyer

Twilio setup

Account, number, webhook, status callback. The bits that have to be wired before any inbound message will land. 10DLC brand and campaign registration are a separate, longer-running track — see the 10DLC walkthrough.

1. The account

Foyer needs a Twilio account with Programmable SMS and Programmable Messaging enabled. The account SID and a restricted auth token go in .env:

TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TWILIO_AUTH_TOKEN=...
TWILIO_WEBHOOK_HOST=https://api.foyer.example.com   # the URL Twilio will POST to

Restricted tokens with only the messaging scope are preferred over the primary auth token. Document the rotation cadence — six months is the floor Foyer assumes.

2. Provision a number

In Twilio Console, buy a number with SMS and MMS capability in a region that matches the business. Toll-free, long code, and short code all work — the choice has carrier-filtering implications but no impact on Foyer code paths. For 10DLC-registered long codes see the 10DLC walkthrough.

3. Configure the inbound webhook

Point the number’s inbound webhook at POST /v1/sms/inbound on the API host. Twilio will sign the request with X-Twilio-Signature; Foyer validates the signature on every inbound and fails closed on mismatch — never falls back to accepting unsigned posts.

A MESSAGE COMES IN  →  Webhook
URL                 →  https://api.foyer.example.com/v1/sms/inbound
Method              →  HTTP POST

The webhook is queue-fast-ack — it dedupes on MessageSid in the messages table, dispatches the agent turn to Redis, and returns 200 within 500ms regardless of LLM latency. Twilio retries on 5xx and on a response over 15 seconds; keeping the response tight protects delivery rates.

4. Configure the status callback

Set the messaging service’s status callback URL to POST /v1/twilio/status. Delivery, undelivered, and failed callbacks land in the message_deliveries table; carrier filter (30007) and unknown error (30008) codes raise alerts and pause that destination number for 24 hours.

5. Register the number with the business

Map the Twilio number to the Foyer business in the admin. Foyer keys consent state on the (customer phone, twilio number) pair, so one Twilio number per business is the simplest topology and the one the tests assume.

6. Confirm with a real inbound

Text the number. The webhook should return 200 immediately; the outbound reply should land within seconds. If nothing comes back, the usual suspects:

  • The signature check is failing — confirm the public host in .env exactly matches the URL Twilio is hitting, including trailing slash and scheme.
  • The agent worker is not running — Horizon is up but workers/agent uvicorn is not.
  • The destination number is STOP’d for this business — check the consent_state table.
  • Quiet hours are in effect — outbound is queued for the next allowed window rather than dropped.

7. After the smoke test

Move on to 10DLC registration. Toll-free numbers and unregistered long codes work for sandbox testing, but US carriers will throttle and eventually block unregistered conversational traffic. Start the brand and campaign registration before the demo depends on it — turnaround is a few business days.