Building a Twilio Relay for QDN's Cascading Forward: Infrastructure Setup & API Integration
What Was Done
The Quick Dump Now (QDN) dispatch system needed a critical capability: the ability to cascade incoming SMS messages across multiple recipients when the primary line is unavailable. Previously, the carrier-level call forwarding chain (QDN line → Sergio's main → his backup at 858-335-4807) couldn't be implemented at the carrier level. We've now provisioned Twilio infrastructure to handle this relay, including credential setup, API integration points, and Lambda function scaffolding.
Credential & Configuration Management
All Twilio credentials were stored in a secrets management pattern:
- Storage location:
/Users/cb/Documents/repos/.secrets/repos.env(mode 600 — readable only by owner) - Account SID: Used for admin operations, billing, and account-level API calls
- Auth Token: Paired with Account SID for authentication in admin workflows
- API Key / Secret pair: Type 'Main' — preferred for SDK runtime calls and programmatic access, offering better auditability and granular revocation without rotating the master auth token
We chose to store both credential pairs because they serve different purposes. Admin operations (e.g., provisioning numbers, viewing call logs) use the Account SID + Auth Token. Lambda functions invoking the SDK use the API Key, which can be revoked independently if a function is compromised.
Why Twilio for SMS Relay?
QDN receives dispatch requests via SMS. When the primary dispatcher is unavailable, those messages need to reach a backup. Twilio provides:
- Webhook-based message routing: Inbound SMS triggers an HTTP POST to our Lambda, which decides where to forward the message
- Programmable SMS sending: Our Lambda can branch logic — check dispatcher availability, retry logic, notification queues — then send via Twilio's API
- Call transcription & routing: Future capability to handle voice calls as well
- SID-based number management: We can provision multiple numbers and swap routing without DNS changes
This beats carrier-level call forwarding because it puts business logic in our hands, not the telecom's.
Infrastructure & Integration Points
Lambda Function: qdn-data-crud
The existing QDN data Lambda already handles job CRUD operations. We're extending it to accept inbound SMS webhooks:
- New route:
POST /sms/inbound— receives Twilio's webhook payload - Route handler logic:
- Parse Twilio's X-Twilio-Signature header for webhook authenticity
- Extract message body, sender, and timestamp
- Look up dispatcher status in DynamoDB (job table, recent activity)
- If primary is unavailable, invoke
append_messageto queue for backup - Return 200 OK to acknowledge to Twilio (prevents retries)
- Related routes added:
POST /sms/send— Lambda-to-Twilio outbound SMS (for confirmations, alerts)GET /sms/status/{message-id}— delivery status pollingPOST /sms/callback— Twilio delivery receipts (DLRs)OPTIONS /sms/*— CORS preflight for browser-based tests
Why extend an existing Lambda instead of creating a new one? The job CRUD table already holds dispatcher state, job assignments, and message history. Collocating SMS logic in the same function keeps transactions atomic — we read dispatcher availability and append a message in the same DynamoDB write.
API Gateway Integration
The QDN dashboard's API Gateway already had routes for job updates. We added 5 new routes:
POST /sms/inbound → Lambda: qdn-data-crud (Twilio webhook target)
POST /sms/send → Lambda: qdn-data-crud (programmatic SMS)
GET /sms/status/{id} → Lambda: qdn-data-crud (polling)
POST /sms/callback → Lambda: qdn-data-crud (Twilio DLR receipts)
OPTIONS /sms/* → Return 200 with CORS headers
Each route enforces the same IAM role the Lambda assumes, so credential leakage is minimized.
CloudFront & S3 for the /track Page
We deployed a new tracking page for customers:
- File:
/Users/cb/Documents/repos/sites/quickdumpnow.com/track/index.html - S3 bucket: quickdumpnow.com (existing content bucket)
- CloudFront distribution: Existing default distribution serving quickdumpnow.com
- CF Function: Created a rewrite rule to map
/track→/track/index.html(since S3 doesn't serve directory indexes) - Cache behavior: Attached the CF function to the default behavior, invalidated with
/*pattern
Why a CloudFront Function instead of a Lambda@Edge? CF Functions execute at edge locations with lower latency and no cold-start penalty. For a simple URL rewrite, it's overkill to invoke a full Lambda.
Message Flow & State Management
When an SMS arrives at a Twilio-owned number provisioned for QDN:
- Twilio POSTs the inbound message to
https://dashboard.quickdumpnow.com/sms/inbound - Lambda validates the Twilio signature using the Auth Token from
repos.env - Lambda queries DynamoDB: is the primary dispatcher (Sergio) active within the last 15 minutes?
- If active: Append message to his job queue; return 200
- If inactive: Append message to backup dispatcher's queue; send a notification to Sergio (via SMS or email) that he's receiving backup overflow
- Twilio's retry logic respects the 200 response and doesn't re-POST
This avoids the "silent drop" problem where a message sent to an unavailable line disappears. We actively route it instead.
Key Decisions & Tradeoffs
- SDK vs. REST API: We chose the Twilio SDK (Node.js) for the Lambda because it handles auth headers, retries, and pagination automatically. Raw REST calls would require more boilerplate.
- Webhook validation: Twilio provides an X-Twilio-Signature header. We verify it server-side using the Auth Token. This prevents spoofed webhooks from poisoning our queue.
- SES vs.