--- name: telnyx-twilio-migration description: >- Migrate from Twilio to Telnyx. Orchestrates a complete 6-phase migration: discovery, planning, setup, code migration, validation, and cleanup. Covers voice (TwiML to TeXML, Call Control API), messaging, WebRTC, SIP trunking, verify, fax, video, IoT, number lookup, and porting. Includes automated scanners, validation scripts, and integration tests. user_invocable: true metadata: author: telnyx product: migration compatibility: "Requires bash 4+, jq, curl. macOS ships bash 3.2 — scripts auto-upgrade via Homebrew bash if available (brew install bash)." --- # Twilio to Telnyx Migration > **Path convention:** `{baseDir}` in this document is the directory containing this `SKILL.md` file (e.g., `/path/to/skills/telnyx-twilio-migration`). Substitute the absolute path before running any script shown below — do not pass the literal string `{baseDir}` to bash. You MUST follow these phases in order (0 → 1 → 2 → 3 → 4 → 5 → 6). Do NOT skip phases. Each phase has prerequisites and exit criteria — do not proceed until the exit criteria are met. You MUST run the scripts specified in each phase (do not substitute your own checks). You MUST modify the user's source files to complete the migration. **Interaction model**: Phase 0 collects ALL user input (API key, phone number, cost approval). Phases 1–6 run **fully autonomously** — do NOT ask the user any questions. Make all decisions deterministically using the rules in each phase. The only exception: if a failure persists after 3 fix attempts, present the issue to the user with error details and what you tried. **Context recovery**: If you lose context (e.g. after compaction), IMMEDIATELY run `bash {baseDir}/scripts/migration-state.sh status ` and `bash {baseDir}/scripts/migration-state.sh show ` to recover your current phase and all resource IDs. Then resume from that phase. ### Migration State Tracking Track progress in `migration-state.json` via `bash {baseDir}/scripts/migration-state.sh [args]`. Commands: `init`, `set-phase `, `set `, `add-product `, `add-file `, `set-commit `, `status`, `show`. This preserves resource IDs across phases and enables resume after interruption. For a complete product mapping, see `{baseDir}/references/product-mapping.md`. ## Universal Changes (All Migrations) 1. **Authentication**: Basic Auth (`AccountSID:AuthToken`) → Bearer Token (`Authorization: Bearer $TELNYX_API_KEY`). Get key at https://portal.telnyx.com/#/app/api-keys 2. **Webhook Signatures**: HMAC-SHA1 → Ed25519. Get public key at https://portal.telnyx.com/#/app/account/public-key 3. **Webhook Payloads**: Flat form-encoded → nested JSON under `data.payload`. See `{baseDir}/references/webhook-migration.md` 4. **Recording Defaults**: Single → dual-channel. Set `channels="single"` to match Twilio behavior. --- ## Phase 0: Prerequisites (User Input — ONLY Interaction Point) > **This is the ONLY phase that requires user interaction.** Collect all inputs now — Phases 1–6 run fully autonomously. Do not ask the user any further questions during migration unless you hit a failure you cannot resolve after 3 attempts. > > **Exit criteria**: `TELNYX_API_KEY` validates, user phone number collected, costs approved. ### Step 0.1: Collect All Required Information Ask the user for these **three things** in a single message: 1. **`TELNYX_API_KEY`** — API key v2 from https://portal.telnyx.com/#/app/api-keys. If they don't have a Telnyx account yet, direct them to: create account (https://telnyx.com/sign-up), complete KYC, add payment method, then generate key. 2. **`TELNYX_TO_NUMBER`** — their personal phone number in E.164 format (e.g., `+15551234567`) for receiving test SMS/call/OTP during integration testing. 3. **Cost approval** — present this table and get explicit approval: | Item | Cost | When Charged | |------|------|-------------| | Phone number (if account has none) | ~$1.00/month | Phase 5 integration tests | | Integration tests (SMS + voice + verify + lookup + fax) | ~$0.144 total | Phase 5 | | 10DLC registration (US A2P messaging only) | ~$19 | Phase 3 setup (only if applicable) | | Number porting | Free | Post-migration (optional) | **Total estimated cost for most migrations: under $1.20.** 10DLC adds ~$19 if applicable. Individual paid actions still have `--confirm` gates in the scripts. **Do not proceed until the user provides all three items.** This is the last time you will ask the user for input. ### Step 0.2: Validate API Key & Initialize State ```bash bash {baseDir}/scripts/migration-state.sh init export TELNYX_API_KEY="" export TELNYX_TO_NUMBER="" curl -s -H "Authorization: Bearer $TELNYX_API_KEY" https://api.telnyx.com/v2/balance ``` If validation fails, ask the user to check their key and try again. This is the only retry that requires user input. **Phase 0 exit**: `bash {baseDir}/scripts/migration-state.sh set-phase 0` --- ## Phase 1: Discovery > **Prerequisites**: Phase 0 complete (`TELNYX_API_KEY` valid, phone number collected, costs approved). > **Exit criteria**: `twilio-scan.json` exists with scan results, migration scope determined. ### Step 1.1: Run Full Discovery Run the discovery script — this executes preflight check, Twilio scan, deep scan, and partial migration check in one command: ```bash bash {baseDir}/scripts/run-discovery.sh ``` This produces `/twilio-scan.json` (and optionally `twilio-deep-scan.json`). **You must run this script.** Do not manually scan files or skip this step. ### Step 1.2: Triage and Determine Scope (Autonomous) Review scan results and classify each match: - **Active import/SDK call** (e.g., `from twilio.rest import Client`, `client.messages.create()`): Needs migration - **String reference** (e.g., `# formerly used Twilio`, URL in docs, log message): Usually no code change needed — just update text - **Config/env var** (e.g., `TWILIO_ACCOUNT_SID`): Needs env var rename (see Phase 3) - **Test mock** (e.g., `mock_twilio_response`): Migrate in Phase 4 alongside product code **Do NOT ask the user to confirm scope.** Migrate ALL detected Twilio products. Apply these rules automatically: - **Supported products** (voice, messaging, verify, webrtc, sip, fax, video, lookup, numbers, porting): migrate - **Unsupported products** (Flex, Studio, TaskRouter, Conversations, Sync, Notify, Proxy, Pay, Autopilot): automatically keep on Twilio — record in state and continue: ```bash # Automatically record each unsupported product as kept on Twilio: bash {baseDir}/scripts/migration-state.sh set kept_on_twilio. true ``` See `{baseDir}/references/unsupported-products.md` for alternatives to note in the migration report. **Mobile platforms** (detected and guided): iOS native, Android native, React Native, Flutter. These require client-side SDK migration — see `{baseDir}/references/mobile-sdk-migration.md` for complete migration guides. **Phase 1 exit**: `bash {baseDir}/scripts/migration-state.sh set-phase 1 && bash {baseDir}/scripts/migration-state.sh set scan_file "twilio-scan.json"` --- ## Phase 2: Planning > **Prerequisites**: Phase 1 complete, `twilio-scan.json` exists, scope determined. > **Exit criteria**: `MIGRATION-PLAN.md` exists in project root. ### Step 2.1: Read Relevant References For each product detected in the scan, read the corresponding reference file: | Detected Product | Read This Reference | |---|---| | `voice`, `texml` | `{baseDir}/references/voice-migration.md` and `{baseDir}/references/texml-verbs.md` | | `messaging` | `{baseDir}/references/messaging-migration.md` | | `webrtc` | `{baseDir}/references/webrtc-migration.md` and `{baseDir}/references/mobile-sdk-migration.md` | | `verify` | `{baseDir}/references/verify-migration.md` | | `sip`, `sip-integrations` | `{baseDir}/references/sip-trunking-migration.md` | | `fax` | `{baseDir}/references/fax-migration.md` | | `video` | `{baseDir}/references/video-migration.md` | | `iot` | `{baseDir}/references/iot-migration.md` | | `lookup` | `{baseDir}/references/lookup-migration.md` | | `numbers`, `numbers-config` | `{baseDir}/references/numbers-migration.md` | | `porting-in`, `porting-out` | `{baseDir}/references/number-porting.md` | | *(all products)* | `{baseDir}/references/webhook-migration.md` | ### Step 2.2: Apply Decision Matrix (Autonomous) **Do NOT ask the user to choose.** Apply these rules deterministically: **Voice approach** — select automatically based on the codebase: | If the codebase has... | Use... | |---|---| | TwiML/XML files, `VoiceResponse()` builders, simple IVR (Say, Gather, Dial, Record) | **TeXML** (minimal code changes, nearly 1:1) | | Media streaming, real-time audio forking, `` elements | **Call Control** (event-driven API) | | Both patterns | **Both** — TeXML for inbound (webhook returns XML), Call Control for outbound | **Migration strategy** — select automatically: | If... | Use... | |---|---| | ≤10 files with Twilio code, single product | **Big-bang** (all at once) | | >10 files or multiple products | **Incremental** — order: messaging → voice → verify → webhooks → other | ### Step 2.3: Generate Migration Plan ```bash cp {baseDir}/templates/MIGRATION-PLAN.md /MIGRATION-PLAN.md ``` Populate the plan based on the decisions above. Do not ask for user approval — proceed directly to Phase 3. **Phase 2 exit**: `bash {baseDir}/scripts/migration-state.sh set-phase 2` --- ## Phase 3: Setup > **Prerequisites**: Phase 2 complete, `MIGRATION-PLAN.md` exists. > **Exit criteria**: Telnyx SDK installed, environment variables updated, setup committed to git. ### Step 3.1: Create Migration Branch ```bash cd && git checkout -b migrate/twilio-to-telnyx ``` ### Step 3.2: Install Telnyx SDK (Keep Twilio Until Phase 6) Install Telnyx SDK **alongside** Twilio — do NOT remove Twilio from the package manifest yet (removal is Phase 6). Keep `twilio` in `requirements.txt`/`package.json`/`Gemfile`/`go.mod` until Phase 6 so you can revert if validation fails. **Server SDKs** — use these EXACT commands with version constraints (do NOT use `pip install telnyx` or `npm install telnyx` without a version range): - Python: `pip install 'telnyx>=4.0,<5.0'` — and write `telnyx>=4.0,<5.0` in `requirements.txt` (NOT just `telnyx`). Initialize with `from telnyx import Telnyx; client = Telnyx(api_key=os.environ.get("TELNYX_API_KEY"))`. - Node: `npm install telnyx@^6` — writes `"telnyx": "^6.x.x"` in `package.json` automatically. Initialize with `const Telnyx = require('telnyx'); const client = new Telnyx({ apiKey: process.env.TELNYX_API_KEY });` (CJS) or `import Telnyx from 'telnyx'` (ESM). - Ruby: `gem 'telnyx', '~> 5.0'` in Gemfile + `bundle install` - Go: `go get github.com/team-telnyx/telnyx-go` - Java/PHP/C#: No official SDK — use REST API with `{baseDir}/sdk-reference/curl/` for API examples **Client-side WebRTC SDK** (if WebRTC detected): `npm install @telnyx/webrtc` — see `{baseDir}/sdk-reference/webrtc-client/javascript.md` for the full API reference **Supported languages**: Python, JavaScript/TypeScript, Go, Ruby have full SDKs with reference docs in `{baseDir}/sdk-reference/{lang}/`. Java, PHP, C#/.NET use REST/curl only via `{baseDir}/sdk-reference/curl/`. Client-side WebRTC SDKs exist for Swift (iOS), Kotlin (Android), React Native, Flutter — see `{baseDir}/references/mobile-sdk-migration.md`. > **JavaScript module warning**: The `sdk-reference/javascript/` files use ESM syntax (`import Telnyx from 'telnyx'`). If the project uses CommonJS (`require`), translate to: `const Telnyx = require('telnyx'); const client = new Telnyx({ apiKey: process.env.TELNYX_API_KEY });`. Do NOT copy ESM imports into CJS files unless `"type": "module"` is in `package.json`. ### Step 3.3: Update Environment Variables | Twilio Variable | Telnyx Replacement | Notes | |---|---|---| | `TWILIO_ACCOUNT_SID` | `TELNYX_API_KEY` | Bearer token, get from portal | | `TWILIO_AUTH_TOKEN` | `TELNYX_PUBLIC_KEY` | For webhook validation (Ed25519) | | `TWILIO_API_KEY` / `_SECRET` / `_SID` | — | Not needed (single API key model) | | `TWILIO_PHONE_NUMBER` | `TELNYX_PHONE_NUMBER` | Your Telnyx number (E.164) | | `TWILIO_MESSAGING_SERVICE_SID` | `TELNYX_MESSAGING_PROFILE_ID` | Messaging profile UUID | | `TWILIO_VERIFY_SERVICE_SID` | `TELNYX_VERIFY_PROFILE_ID` | Verify profile UUID | | *(voice/SIP/WebRTC)* | `TELNYX_CONNECTION_ID` | The connection or application ID used for outbound calls. The value depends on your voice approach — see disambiguation below. | > **`TELNYX_CONNECTION_ID` disambiguation** — all three are different Telnyx resources: > - **TeXML**: This is a **TeXML Application ID** from `POST /v2/texml_applications`. It owns your webhook URLs and outbound calling config. > - **Call Control**: This is a **Call Control Application ID** from `POST /v2/call_control_applications`. It routes inbound call events to your webhook. > - **SIP trunking**: This is a **SIP Connection ID** from `POST /v2/credential_connections` or `POST /v2/ip_connections`. It's used for PBX/SBC trunking. > > Use a single `TELNYX_CONNECTION_ID` env var — its value is whichever ID matches your voice approach. If the app uses multiple approaches (e.g., TeXML for inbound + SIP for trunking), use separate env vars with descriptive names like `TELNYX_TEXML_APP_ID` and `TELNYX_SIP_CONNECTION_ID`. Update `.env`, `.env.example`, secrets manager, CI/CD variables, and deployment configs. **Ensure every env var used in the migrated code is present in `.env.example`** — missing env vars are a top cause of runtime failures. > **Whitelisted destinations (CRITICAL):** When creating or reusing Telnyx resources, you MUST ensure `whitelisted_destinations` includes the target country. Without this, sends/calls will fail silently or with cryptic errors. > - **Messaging profiles**: `whitelisted_destinations` on the profile itself. Use `["*"]` for all countries or specify e.g. `["US", "GB", "IE"]`. Check existing profiles via `GET /v2/messaging_profiles/{id}` and update with `PATCH` if needed. > - **Outbound Voice Profiles (OVP)**: `whitelisted_destinations` controls which countries you can call. Create/update OVP via `/v2/outbound_voice_profiles`. Assign the OVP to your Call Control app or TeXML app's `outbound.outbound_voice_profile_id`. > - **Verify profiles**: `sms.whitelisted_destinations` inside the SMS channel config. Check existing profiles via `GET /v2/verify_profiles/{id}`. > - The test scripts (`test-messaging.sh`, `test-voice.sh`, `test-verify.sh`) handle this automatically, but when writing migration code, always set `whitelisted_destinations` explicitly. > **Rate limits**: Messaging: 1 msg/sec per number (10DLC), voice: varies by connection type. Implement exponential backoff for 429 responses. ### Step 3.4: Commit Setup Changes ```bash git add && git commit -m "chore: add Telnyx SDK alongside Twilio, update env vars" bash {baseDir}/scripts/migration-state.sh set-phase 3 bash {baseDir}/scripts/migration-state.sh set-commit 3 ``` --- ## Phase 4: Migration > **Prerequisites**: Phase 3 complete, Telnyx SDK installed, env vars updated, setup committed. > **Exit criteria**: All source files transformed, per-product validation passes, all changes committed. Transform code file-by-file, grouped by product area. **You must actually modify the user's source files** — reading references alone is not sufficient. ### Migration Loop Process each product area in priority order: **messaging → voice → verify → numbers → others**. **For each product area:** 1. Read `{baseDir}/references/{product}-migration.md` — this is the **primary source** with Twilio→Telnyx before/after code, parameter mappings, and pitfall warnings 2. Collect all files for this product from the scan manifest (`twilio-scan.json`) **For each file in the product area:** 1. **Read** the user's source file 2. **Identify** every Twilio pattern (imports, client init, API calls, webhooks, env vars) 3. **Transform** each pattern using the reference guide's before/after examples 4. **If the reference doesn't cover a specific API call**, look it up in `{baseDir}/sdk-reference/{language}/{product}.md` for the exact Telnyx method signature. The `{baseDir}/sdk-reference/curl/{product}.md` files have the richest examples with optional fields. 5. **Write** the transformed file 6. **Self-check**: Re-read the file and verify no Twilio patterns remain **After all source files in the product area:** 7. **Migrate tests**: Find ALL test files for this product — `grep -rl -i "twilio\|TwilioVoice\|TwilioClient\|twilio_" *test* *Test* *spec* *Spec* 2>/dev/null`. Migrate every one: update imports, mock objects, mock payloads, assertions, and type references. Do NOT defer test files as "remaining manual steps" — they are part of the migration. Run the test suite to confirm. 8. **Lint**: `bash {baseDir}/scripts/lint-telnyx-correctness.sh --product {product}` — catches common anti-patterns (wrong method names, wrong parameter names, missing profile IDs). Fix all ISSUE items before proceeding. 9. **Validate**: `bash {baseDir}/scripts/validate-migration.sh --product {product} --scan-json /twilio-scan.json` 10. **Fix** any validation failures or lint issues, re-run until both exit code 0 11. **Commit**: `git add && git commit -m "migrate: {product} — Twilio to Telnyx"` 12. **Track**: `bash {baseDir}/scripts/migration-state.sh add-product {product}` (and `add-file` for each file migrated) **After ALL product areas are migrated:** 13. **Env var audit**: Grep all migrated source files for `process.env.TELNYX_` / `os.environ["TELNYX_"]` / `ENV["TELNYX_"]` references. Verify EVERY referenced env var exists in `.env.example` (or equivalent config template). Missing env vars are the #1 cause of "works in dev, fails in prod" bugs. ### Post-Migration Documentation Update (MANDATORY) After ALL product areas are migrated and committed, you MUST update documentation. This is NOT optional — agents that skip this step produce incomplete migrations. 1. **Find all docs**: `grep -rl -i "twilio" README.md README CONTRIBUTING.md docs/ *.md 2>/dev/null` (in project root) 2. **Update each file** — replace ALL of the following: - Project description: "uses Twilio" → "uses Telnyx" - Account setup instructions: Twilio Console → Telnyx Mission Control Portal (portal.telnyx.com) - API key generation: "Twilio Account SID and Auth Token" → "Telnyx API Key v2 from portal.telnyx.com/#/app/api-keys" - Environment variable names: every `TWILIO_*` → its `TELNYX_*` equivalent (see Phase 3 env var table) - API endpoint URLs: `api.twilio.com` → `api.telnyx.com/v2` - SDK install commands: `pip install twilio` → `pip install 'telnyx>=4.0,<5.0'`, `npm install twilio` → `npm install telnyx@^6`, etc. - Webhook setup instructions: update signature verification method - Badge URLs, status page links, support links 3. **Commit**: `git add && git commit -m "docs: update all documentation from Twilio to Telnyx"` **Phase 4 exit**: `bash {baseDir}/scripts/migration-state.sh set-phase 4 && bash {baseDir}/scripts/migration-state.sh set-commit 4` If validation fails and you cannot fix the issue, document it and continue to the next product. Do not abandon the migration. ### Product-Specific Transform Guidance **Voice (TeXML path):** - **Static XML files**: Usually no changes needed — ``, ``, ``, etc. are compatible - **Dynamic XML (TwiML builder replacement)**: If the original code uses `VoiceResponse()` (Python) or `new twilio.twiml.VoiceResponse()` (Node) to build XML programmatically, replace with XML string templates. Telnyx has no builder class — return raw XML strings from your webhook endpoints. For dynamic content, use f-strings (Python) or template literals (JavaScript) with proper XML escaping (replace `&` with `&`, `<` with `<`, `>` with `>`, `"` with `"` in user-provided values). See `{baseDir}/references/voice-migration.md` → "TwiML builder classes → raw XML strings" for complete before/after examples in Python and JavaScript. Do NOT install third-party XML builder libraries — raw strings are sufficient and avoid adding dependencies. - Validate with: `bash {baseDir}/scripts/validate-texml.sh ` - API calls: Change base URL from `api.twilio.com/2010-04-01/Accounts/{SID}` to `api.telnyx.com/v2/texml` - Auth: Basic Auth → Bearer Token - Recording: Set `channels="single"` if expecting mono - **`speechModel` does NOT exist in TeXML** — remove it or replace with `transcriptionEngine` (e.g., `transcriptionEngine="Google"`). Using `speechModel` will be silently ignored. - **Polly voices**: TeXML supports `voice="Polly.{VoiceId}"` and `voice="Polly.{VoiceId}-Neural"`. Always prefer Neural variants (e.g., `Polly.Amy-Neural` instead of `Polly.Amy`) — non-Neural voices may silently fall back to the default voice. If a specific Polly voice is unavailable, use `voice="woman"` with the appropriate `language` attribute. - **Outbound calls**: Use the Telnyx SDK — do NOT use raw `fetch()` to the TeXML API. The SDK handles auth, retries, and response parsing. Pass the **TeXML Application ID** (from `TELNYX_CONNECTION_ID`, NOT a SIP connection ID) as the `connection_id` parameter. See `{baseDir}/sdk-reference/{language}/texml.md` for the exact method signature. **Voice (Call Control path):** - Replace TwiML response generation with Call Control API commands - Use `client_state` (base64 JSON) for stateless server architecture - See `{baseDir}/references/voice-migration.md` → "Advanced Voice Patterns" **Messaging:** - `body` → `text` parameter name change - `from_` → `from` (same in most SDKs) - `StatusCallback` per-message → configure on Messaging Profile - `MessagingServiceSid` → `messaging_profile_id` - **Always include `messaging_profile_id`** in send requests — messages without a profile will fail - Webhook payload: flat `{From, Body}` → nested `{data.payload.from.phone_number, data.payload.text}` - **10DLC blocker**: US A2P SMS requires 10DLC campaign registration. See `{baseDir}/references/messaging-migration.md` → "10DLC Registration". **WebRTC:** - Delete simple dial TwiML endpoints (use `client.newCall()` instead) - Convert complex TwiML endpoints to TeXML - Replace Access Token generation with SIP credentials - Update client SDK: `@twilio/voice-sdk` → `@telnyx/webrtc` - **Client-side files**: Migrate browser JavaScript/HTML files that import `Twilio.Device`, `@twilio/voice-sdk`, or `twilio-client`. These are in frontend directories (e.g., `public/`, `src/`, `static/`, CDN `