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
+11 -10
View File
@@ -7,26 +7,27 @@ import type { Request } from "express";
* and Cloudflare/Let's Encrypt terminate TLS in front of it either way.
*
* Only valid for requests that actually arrived via the public tunnel (i.e. inside
* a Twilio webhook handler). For anything triggered from the staff browser — which
* reaches the backend over the LAN-only local-* hostname — use getTwilioPublicBaseUrl()
* instead, since Twilio can never reach that LAN hostname to fetch a callback URL.
* a Twilio/Telnyx webhook handler). For anything triggered from the staff browser —
* which reaches the backend over the LAN-only local-* hostname — use
* getPublicTunnelBaseUrl() instead, since neither provider can reach that LAN
* hostname to fetch a callback URL.
*/
export function getPublicBaseUrl(req: Request): string {
return `https://${req.get("host")}`;
}
/**
* Fixed public origin Twilio must use to call back into this office's server
* (e.g. for outbound-call TwiML webhooks). Comes from CLOUDFLARE_HOST, the
* per-office env var already reserved for this — not derived from the
* triggering request, since that request may have arrived over the LAN-only
* hostname, which Twilio cannot reach.
* Fixed public origin Twilio/Telnyx must use to call back into this office's server
* (e.g. for outbound-call webhooks). Comes from CLOUDFLARE_HOST, the per-office env
* var already reserved for this — not derived from the triggering request, since
* that request may have arrived over the LAN-only hostname, which neither provider
* can reach.
*/
export function getTwilioPublicBaseUrl(): string {
export function getPublicTunnelBaseUrl(): string {
const host = process.env.CLOUDFLARE_HOST?.trim();
if (!host) {
throw new Error(
"CLOUDFLARE_HOST is not set — required to build a Twilio-reachable callback URL. Set it in .env to this office's public tunnel hostname."
"CLOUDFLARE_HOST is not set — required to build a provider-reachable callback URL. Set it in .env to this office's public tunnel hostname."
);
}
return `https://${host}`;