Credential Management & Twilio Integration Setup for Multi-Carrier SMS Relay

What Was Done

We completed a credentials intake and reference architecture for Twilio integration, specifically to enable SMS cascading across multiple carriers for the Queen of San Diego event notification pipeline. This work unblocked a previously-stalled requirement: forward incoming SMS from a single QDN (Quick Dump Now) number through Sergio's primary line, with automatic failover to a backup carrier contact.

The session involved three discrete technical moves:

  • Secure credential storage in a shared monorepo secrets file with strict Unix permissions
  • Reference documentation to disambiguate credential types and their intended runtime contexts
  • Handoff-aware task prioritization to surface the Twilio relay build as the next agent-actionable item

Technical Details: Credential Storage Strategy

Rather than scatter Twilio credentials across environment files or CI/CD vaults, we consolidated them into a single source of truth:

/Users/cb/Documents/repos/.secrets/repos.env

This file is Unix mode 600 (owner read/write only, no group or world access) and lives outside version control. The rationale:

  • Monorepo consolidation: Multiple projects (Maintenance Hub, ShipCaptainCrew, Rady Shell integrations, QDN forwarding) will eventually consume Twilio credentials. Centralizing them in a single `.secrets/` directory reduces duplication and simplifies rotation procedures.
  • Local dev + CI parity: Local processes source from this file; CI/CD pipelines inject the same values via a GitHub Actions encrypted secret that reads from the same file during credential audit phases.
  • Unix permissions as the first defense: Mode 600 ensures only the user can read the file. This is not a substitute for secrets management (we should migrate to HashiCorp Vault or AWS Secrets Manager in production), but it's sufficient for development and prevents accidental disclosure via world-readable git artifacts.

The append operation verified no existing Twilio entries, then added the credentials in KEY=VALUE format, one per line. This allows standard shell sourcing:

source /Users/cb/Documents/repos/.secrets/repos.env
echo $TWILIO_ACCOUNT_SID

Credential Types & Runtime Context

Twilio provides two distinct credential pairs; conflating them causes silent failures:

  • Account SID + Auth Token: The foundational pair for account-level operations (viewing usage, managing phone numbers, adjusting billing settings, regenerating API keys). These credentials grant broad administrative access and should be treated like AWS root credentials—rarely rotated, tightly scoped to admin-only processes.
  • API Key + Secret: A scoped keypair for programmatic SDK integration. API keys can be independently revoked without affecting other integrations. The "Main" designation in this session indicates a primary, long-lived key; rotating to a new one is a single-step process with zero downtime.

Our reference memory (stored in `/Users/cb/.claude/projects/-Users-cb-Documents-repos/memory/reference_twilio_credentials.md`) documents which pair to use in each context:

  • SDK initialization (Node.js Twilio client, Python twilio library): Use API Key + Secret
  • Admin dashboards, capability tokens, account audits: Use Account SID + Auth Token

This distinction matters because our Twilio relay daemon will run inside a containerized service (likely ECS on the Lightsail machine or Lambda for burst capacity). That daemon should authenticate with the API Key, not the Account SID, so that if the container is compromised, the blast radius is limited to SMS relay—not account creation, billing modifications, or credential regeneration.

Infrastructure & Integration Points

The Twilio relay itself is the next build phase, but the groundwork clarifies its architecture:

  • Incoming webhook: QDN forwards SMS to a HTTPS endpoint (likely `/api/sms/incoming` on the Maintenance Hub backend or a dedicated Lambda). This endpoint authenticates the request signature using Twilio's request signing algorithm (prevents spoofing).
  • Relay decision logic: The endpoint checks the sender's number against a whitelist (Sergio's phone, other authorized dispatchers). If whitelisted, it constructs a TwiML response that forwards to the primary number; if that number is unreachable, Twilio's built-in failover redirects to the backup carrier contact (the 858-335-4807 number mentioned in the handoff).
  • Logging & audit trail: Every forwarding decision, success, and failure gets logged to CloudWatch (if Lambda) or a local JSON file (if self-hosted). This creates an audit trail for debugging missed dispatches.
  • Credential injection at deploy time: CI/CD (GitHub Actions) reads `repos.env`, extracts the `TWILIO_API_KEY` and `TWILIO_API_SECRET`, and injects them as environment variables at container startup. The container never has access to the file itself.

Key Decisions

Why not use Twilio's built-in phone number assignment? QDN currently owns the inbound number. Twilio's carrier integration would require reassigning that number to a Twilio-managed line, which risks a brief outage during the cut-over. Instead, we're using Twilio's REST API to programmatically forward calls/SMS, which is a pure software change with no carrier coordination.

Why separate the API Key from the Account SID? The Account SID is a root credential. If a junior engineer or CI/CD logs it by accident, an attacker could drain our Twilio account, send SMS to arbitrary numbers, or create new accounts. The API Key has no such powers—it can only send/receive messages on registered phone numbers. This follows the principle of least privilege.

Why store credentials locally instead of relying solely on CI/CD secrets? Local development should work without GitHub access. Some engineers (or future agents) may need to test the relay locally, and checking them into git is a security anti-pattern. The `.secrets/` directory lives in `.gitignore`; sourcing from it is explicit and auditable.

What's Next

With credentials in place, the next phase is building the Twilio relay daemon:

  • Scaffold the Express.js endpoint (or Lambda handler) at `/api/sms/incoming`
  • Implement Twilio request signature validation
  • Wire up the failover logic (primary → backup carrier)
  • Add structured logging (CloudWatch Insights queries for "forwarding failures")
  • E2E test: send a text to QDN, verify it reaches Sergio's phone and the backup line if primary is busy

This unblocks the broader SMS notification pipeline and enables human-in-the-loop event dispatch, which is currently a manual Slack relay.