feat: migrate SMS/voice to Telnyx (TeXML), keep browser dial pad on Twilio

Twilio now handles only the in-browser dial pad (Access Token + browser-Device
outbound TwiML); Telnyx takes over SMS, inbound voicemail, the AI voice
conversation loop, and staff-triggered outbound calls via TeXML. Adds
TelnyxSettings model, Communication.provider discriminator, new comms/routes
modules, and a real Telnyx Settings page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-03 15:39:43 -04:00
co-authored by Claude Sonnet 5
parent b899659e13
commit 79ac6aaa31
463 changed files with 7823 additions and 3397 deletions
+68 -9
View File
@@ -657,16 +657,17 @@ sudo systemctl start cloudflared
---
### Variant — Tunnel scoped to Twilio webhooks only
### Variant — Tunnel scoped to Twilio/Telnyx webhooks only
The example above tunnels the whole app (`service: http://localhost:3000`) to the public
subdomain. If the only reason you need public access is Twilio's webhooks (inbound SMS/voice —
see [Twilio In-Browser Calling Setup](#twilio-in-browser-calling-setup-dial-pad) below), point
the tunnel at nginx's port-80 block instead, which only forwards `/api/twilio/*` and returns
`403` for everything else (see [nginx.conf](nginx.conf)) — the rest of the app stays unreachable
from the internet even though the tunnel is live.
subdomain. If the only reason you need public access is webhooks — Twilio's (dial pad outbound
calls only — see [Twilio In-Browser Calling Setup](#twilio-in-browser-calling-setup-dial-pad)
below) and Telnyx's (everything else — see [Telnyx Setup](#telnyx-setup) below) — point the
tunnel at nginx's port-80 block instead, which only forwards `/api/twilio/*` and `/api/telnyx/*`
and returns `403` for everything else (see [nginx.conf](nginx.conf)) — the rest of the app stays
unreachable from the internet even though the tunnel is live.
Example actually used for Summit Dental Care's Twilio integration — paste this into
Example actually used for Summit Dental Care's Twilio/Telnyx integration — paste this into
`/etc/cloudflared/config.yml` (`sudo nano /etc/cloudflared/config.yml`, this is a YAML file, not
the certbot `.ini` credentials file from the LAN HTTPS section above):
@@ -690,8 +691,8 @@ sudo systemctl start cloudflared
```
Skip Steps 8–9 (Vite `allowedHosts` / backend CORS) for this variant — the public hostname never
reaches the frontend or triggers a login/API CORS check, only the unauthenticated Twilio webhook
routes.
reaches the frontend or triggers a login/API CORS check, only the unauthenticated Twilio/Telnyx
webhook routes.
---
@@ -960,6 +961,64 @@ Once saved, the dial pad on the Patient Connection page is fully functional. The
---
## Telnyx Setup
Telnyx handles everything except the in-browser dial pad above: outbound/inbound SMS (including
the AI text conversation), inbound voicemail, and both staff-triggered and AI-driven outbound
calls. It uses **TeXML** (Telnyx's TwiML-compatible voice API), which is why the webhook XML looks
just like the Twilio webhooks above.
This reuses the same Cloudflare Tunnel already set up for Twilio above (see
[Cloudflare Tunnel Setup](#cloudflare-tunnel-setup-remote-access-per-office)) — no new tunnel or
DNS record needed, Telnyx just hits the same public hostname on a different path
(`/api/telnyx/*` instead of `/api/twilio/*`). If you're using the
[tunnel-scoped-to-webhooks variant](#variant--tunnel-scoped-to-twiliotelnyx-webhooks-only), make
sure `nginx.conf`'s port-80 block includes the `/api/telnyx/` `location` block — Telnyx webhooks
will silently 403 in production without it.
### Step 1 — Buy/assign a phone number for this office
In the [Telnyx Mission Control Portal](https://portal.telnyx.com), buy or port a number for this
office (each office needs its own Telnyx number, separate from its Twilio dial-pad number).
### Step 2 — Create a Messaging Profile (for SMS)
1. **Messaging → Messaging Profiles → Create Messaging Profile**
2. Attach this office's phone number to the profile
3. Set the **Inbound Webhook URL** to:
```
https://<office>.mydentalofficemanagement.com/api/telnyx/webhook/sms
```
4. Note the **Messaging Profile ID** if your account requires one for sending (some number types don't)
### Step 3 — Create a TeXML Application (for calls)
1. **Voice → TeXML Applications → Create TeXML Application**
2. Set the **Voice Webhook URL** to:
```
https://<office>.mydentalofficemanagement.com/api/telnyx/webhook/voice
```
3. Attach this office's phone number to the application
4. Note the **Application ID** (also called Connection ID)
### Step 4 — Get an API Key and Account SID
**API Keys & Credentials** in the portal → create an API Key. The **Account SID** (separate from
the API Key, needed for placing outbound calls) is shown in your account/organization settings.
### Step 5 — Enter credentials in the app
Go to **Settings → Telnyx Settings** and fill in: API Key, Account SID, Phone Number, TeXML
Application ID, Messaging Profile ID (if applicable), and an optional Voicemail Greeting → Save.
### Step 6 — Webhook signature verification (optional, recommended)
Telnyx signs webhooks with Ed25519. If you want inbound webhook signatures verified (rejecting
forged requests to your webhook URLs), copy your account's **Public Key** from the portal into
the Webhook Public Key field in Telnyx Settings.
---
## License Key Generator
The license key generator is a private tool that lives only on your dev PC. Use it to generate a new license key for any office every 3 months.