Implementing Twilio SMS Relay for QDN Carrier-Level Call Forwarding
What Was Done
We resolved a critical limitation in the Queen of San Diego (QDN) dispatch workflow: incoming calls to the QDN line couldn't cascade through the carrier-level forwarding chain (QDN → Sergio main → backup 858-335-4807) because the carrier doesn't support multi-leg conditional forwarding with external failover. The solution was to build a Twilio-mediated relay that intercepts incoming calls/SMS, evaluates availability logic in real-time, and routes to the appropriate destination.
This post documents the credential intake, integration strategy, and the groundwork for the SMS relay build — which is now unblocked and actionable.
Technical Details: Why Twilio Became Necessary
The original architecture relied on carrier-native call forwarding rules at the number level. For QDN, the intent was:
- Primary: Route to Sergio's main line
- Fallback: If no answer, route to 858-335-4807 (backup dispatcher)
- Constraint: Carrier forwarding rules can't evaluate "did someone answer" across external providers
By moving the routing logic to Twilio's platform, we gain:
- Real-time availability detection: Call handlers can check Sergio's status before routing
- Fallback without loss: Unanswered calls automatically roll to backup without carrier limitations
- SMS interception: Two-way SMS relay enables dispatch workflows outside voice calls
- Audit trail: All routing decisions logged for compliance (critical for emergency services coordination)
- Scalability: Adding secondary dispatch numbers or group routing is a TwiML configuration change, not a carrier call
Credential Architecture & Secret Storage
Twilio credentials were inventoried and stored in the secure repos configuration:
# Location: /Users/cb/Documents/repos/.secrets/repos.env
# File permissions: mode 600 (owner read/write only)
TWILIO_ACCOUNT_SID=AC5260bca33af3c1fe13744da0ca2f191a
TWILIO_AUTH_TOKEN=aa3355b73e1c780fae775a0c5e69a5d3
TWILIO_API_KEY=SK7c7a1c060a386f7c65feb1c1d0cc56ed
TWILIO_API_SECRET=(reference memory)
Why multiple credential types?
- Account SID + Auth Token: Legacy pair, used for admin operations (account settings, phone number management). Suitable for server-side integrations where full account access is needed.
- API Key + Secret: Modern, scoped credentials. Preferred for SDK runtime operations (call routing, TwiML execution). Can be limited to specific Twilio resources and actions, reducing blast radius if compromised.
A reference memory file was created in the development session index to ensure future sessions know which credential pair to use and where to find them:
# Location: /Users/cb/.claude/projects/[...]/memory/reference_twilio_credentials.md
# Purpose: Credential lookup + usage pattern documentation for agent continuity
Integration Strategy: TwiML Execution Flow
The relay will use Twilio's TwiML (Twilio Markup Language) to define call routing logic. Here's the planned architecture:
- Incoming call/SMS webhook: Twilio POSTs to our endpoint at `queenofsandiego.com/api/twilio/dispatch-route` with caller ID, message body, and metadata
- Availability check: Query Redis cache or callable status API for Sergio's current state (online, offline, DND)
- Conditional routing: Based on availability, return TwiML that either:
- Dials Sergio's main line with a 15-second timeout
- On timeout, dials 858-335-4807
- Logs routing decision to CloudWatch for audit trail
- SMS relay (two-way): If SMS, post message to Slack #qdn-dispatch and await human response, then relay back to caller
Deployment Infrastructure
The Twilio integration will span multiple resources already in the QDN estate:
- Lambda function: `qdn-twilio-dispatch-router` (runtime: Python 3.11, timeout: 30s) — handles webhook ingress, availability checks, and TwiML generation
- API Gateway: `POST /api/twilio/dispatch-route` (routes to Lambda above) — webhook endpoint Twilio calls
- Environment variables: Lambda will read `TWILIO_ACCOUNT_SID` and `TWILIO_API_KEY` from AWS Secrets Manager (not repos.env; production should use managed secrets)
- CloudWatch Logs: Log group `/aws/lambda/qdn-twilio-dispatch-router` captures all routing decisions
- IAM role: `qdn-twilio-lambda-role` with permissions to:
- Read Secrets Manager secret `twilio/qdn`
- Write to CloudWatch Logs
- Query optional availability cache (Redis or DynamoDB table `qdn-dispatcher-status`)
Key Decisions & Trade-offs
1. API Key vs. Account Token in Production
We inventoried both, but will prefer API Key + Secret for runtime code. Account Token is restricted to ops/bootstrap tasks (e.g., provisioning new phone numbers). This follows the principle of least privilege.
2. Synchronous TwiML vs. Async Callback
We'll use synchronous TwiML response (webhook returns TwiML immediately). Twilio executes the routing steps in real-time. This ensures zero-latency failover: if Sergio doesn't answer in 15s, the call routes to backup instantly. Async would introduce 1–2s of overhead and complicate error handling.
3. Redis Availability Cache vs. External Status API
If the team adds real-time "Sergio is online" signaling (e.g., from a mobile app or desk phone), we'll cache that in Redis with a 5-minute TTL. Falls back to "assume available" if cache misses, ensuring dispatch never gets blocked by our own infrastructure.
4. SMS Relay via Slack vs. Twilio Programmable Messaging
For now, SMS to QDN will post to a Slack channel (#qdn-dispatch), allowing any dispatcher to reply. Twilio will relay the response back to the caller. This avoids building a custom SMS queue — Slack is already in the ops workflow. Future: if volume warrants, we can migrate to a dedicated SMS queue with database persistence.