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
@@ -1460,7 +1460,7 @@ export default function AppointmentsPage() {
if (!user || selectedReminderColumns.size === 0) return;
setIsSendingReminders(true);
try {
const res = await apiRequest("POST", "/api/twilio/send-reminders-batch", {
const res = await apiRequest("POST", "/api/telnyx/send-reminders-batch", {
date: formattedSelectedDate,
staffIds: Array.from(selectedReminderColumns),
aiFollowUp: reminderAiFollowUp,
@@ -1481,7 +1481,7 @@ export default function AppointmentsPage() {
if (!user || selectedRescheduleColumns.size === 0) return;
setIsSendingReschedule(true);
try {
const res = await apiRequest("POST", "/api/twilio/send-reschedule-batch", {
const res = await apiRequest("POST", "/api/telnyx/send-reschedule-batch", {
date: formattedSelectedDate,
staffIds: Array.from(selectedRescheduleColumns),
aiFollowUp: rescheduleAiFollowUp,
@@ -43,9 +43,9 @@ export default function PatientConnectionPage() {
const { toast } = useToast();
useQuery<{ enabled: boolean }>({
queryKey: ["/api/twilio/after-hours-handoff"],
queryKey: ["/api/telnyx/after-hours-handoff"],
queryFn: async () => {
const res = await apiRequest("GET", "/api/twilio/after-hours-handoff");
const res = await apiRequest("GET", "/api/telnyx/after-hours-handoff");
return res.json();
},
onSuccess: (data: { enabled: boolean }) => setAfterHoursEnabled(data.enabled),
@@ -53,7 +53,7 @@ export default function PatientConnectionPage() {
const afterHoursMutation = useMutation({
mutationFn: async (enabled: boolean) =>
apiRequest("PUT", "/api/twilio/after-hours-handoff", { enabled }),
apiRequest("PUT", "/api/telnyx/after-hours-handoff", { enabled }),
onSuccess: (_: any, enabled: boolean) => {
toast({
title: enabled ? "After-hours AI enabled" : "After-hours AI disabled",
@@ -81,7 +81,7 @@ export default function PatientConnectionPage() {
to: string;
patientId: number;
}) => {
return apiRequest("POST", "/api/twilio/make-call", {
return apiRequest("POST", "/api/telnyx/make-call", {
to,
message:
"Hello, this is a call from your dental office. We are calling to connect with you.",
@@ -112,7 +112,7 @@ export default function PatientConnectionPage() {
to: string;
patientId: number;
}) => {
return apiRequest("POST", "/api/twilio/make-ai-call", { to, patientId, purpose: "recall" });
return apiRequest("POST", "/api/telnyx/make-ai-call", { to, patientId, purpose: "recall" });
},
onSuccess: () => {
toast({
@@ -150,7 +150,7 @@ export default function PatientConnectionPage() {
);
});
// Handle calling patient via Twilio
// Handle calling patient via Telnyx
const handleCall = (patient: Patient) => {
if (!patient.phone?.trim()) {
toast({
@@ -166,7 +166,7 @@ export default function PatientConnectionPage() {
});
};
// Handle AI-driven call via Twilio
// Handle AI-driven call via Telnyx
const handleAiCall = (patient: Patient) => {
if (!patient.phone?.trim()) {
toast({
@@ -201,7 +201,7 @@ export default function PatientConnectionPage() {
};
const { data: recentCommunications = [] } = useQuery<any[]>({
queryKey: ["/api/twilio/recent-communications"],
queryKey: ["/api/telnyx/recent-communications"],
refetchInterval: 10000,
});
@@ -14,6 +14,7 @@ import { Staff } from "@repo/db/types";
import { NpiProviderTable } from "@/components/settings/npiProviderTable";
import { ProgramBridgeTable } from "@/components/settings/program-bridge-table";
import { TwilioSettingsCard } from "@/components/settings/twilio-settings-card";
import { TelnyxSettingsCard } from "@/components/settings/telnyx-settings-card";
import { AiSettingsCard } from "@/components/settings/ai-settings-card";
import { OfficeHoursCard } from "@/components/settings/office-hours-card";
import { OfficeContactCard } from "@/components/settings/office-contact-card";
@@ -30,6 +31,7 @@ type SectionId =
| "npi"
| "programs"
| "twilio"
| "telnyx"
| "ai"
| "aichat"
| "officehours"
@@ -260,6 +262,9 @@ export default function SettingsPage() {
case "twilio":
return <TwilioSettingsCard />;
case "telnyx":
return <TelnyxSettingsCard />;
case "ai":
return <AiSettingsCard />;