Migrate SMS from Telnyx
This page maps Telnyx's Messages API, messaging profiles, and delivery webhooks to Bird. Follow the main migration guide in order and use these mappings for steps 3, 4, and 5.
The send call is the closest to Bird's of any provider here: JSON, a bearer key, and the same field names. POST https://api.telnyx.com/v2/messages takes from, to and text, and so does POST /v1/sms/messages. What does not carry over is the messaging profile. Telnyx makes it the unit of almost everything: sender pool, webhook URL, opt-out scope, and the keyword configuration. Bird splits those across senders, webhook subscriptions and suppressions. Most of the work in this migration is unpicking that object.
Hand this to your agent
Use this brief in your coding agent. It starts with discovery and produces a reviewable migration plan before any production change.
Code example
Help me migrate my SMS integration from Telnyx to Bird.
1. Inspect this repository's sends, senders, callbacks, schedules, templates, opt-outs and tests. List the traffic and behavior that must survive the migration.
2. Read the Markdown guides at https://bird.com/docs/guides/sms/migrate/telnyx.md and https://bird.com/docs/guides/sms/migrate.md. Use an existing authenticated Bird MCP or CLI connection. If neither is available, follow https://bird.com/docs/ai/set-up-your-agent.md. Discover the actual operations; do not invent commands or ask me to paste credentials into chat.
3. Prepare the code changes, sender/destination requirements, consent migration, webhook verification and rollout/rollback plan. Preserve the scope of each customer's preferences, including requests outside SMS replies. Separate API batches from audience broadcasts and preserve any behavior that has no direct endpoint equivalent.
4. Show me the exact affected resources, destinations, test volume and known costs before an action that sends messages, spends money, registers or changes a sender, or moves production traffic. Require explicit human authorization for each paid submission or production change. Name one-off 10DLC registration and resubmission fees before requesting approval. An existing explicit approval for that exact action is sufficient; broad migration approval is not. Simulated SMS destinations are billable and still require authorization.
5. If I am keeping Telnyx numbers, prepare the human support port request and obtain authorization to send it. Read bird support-tickets create --help, then use the available CLI or MCP support operation with the reviewed number list and requirements. Return the ticket ID and follow the reply; support arranges the port on its own schedule, separately from the code cutover.
6. Run local and intercepted tests first. When authorized, perform the agreed bounded integration tests, inspect accepted and final outcomes separately, and report failures or uncertainty. Do not claim a delivery receipt proves reading or that request idempotency guarantees exactly-once delivery.
7. Keep production cutover and retiring the old provider as explicit steps in the approved rollout. Finish with the diff, evidence, unresolved requirements and the next action.Map the send call
| What it does | Telnyx | Bird |
|---|---|---|
| Recipient | to | to (one per request) |
| Sender | from or messaging_profile_id | from |
| Body | text | text |
| Intent | (none) | category, required on free text |
| Delivery reports | the profile's webhook URL | a workspace webhook subscribed to the delivery events below |
| Round-trip context | your own store, keyed by ID | metadata: arbitrary JSON, echoed on every event |
| Filterable labels | (none) | tags: {name, value} pairs |
| Safe retries | (none documented) | Idempotency-Key header |
| Media | media_urls | no equivalent: media_urls is rejected |
Porting notes:
- A messaging profile ID becomes a plain sender value. Telnyx resolves the number pool and its sending rules behind the profile. Bird takes the sender itself in from, so choose it per send, or use a template send, which selects a valid sender for the destination and rejects from.
- Nothing on the Messages API corresponds to category. Decide per message type whether it is transactional, marketing, authentication, or service. Authentication traffic in particular should be labelled as such rather than left in a marketing default.
- Review retry semantics separately. Telnyx's send reference documents no idempotency key, so a timeout there leaves you guessing. Send the Idempotency-Key header from the first port.
Carry over opt-outs
This is the step that surprises people, and the number to work out first is how many suppressions your list becomes.
Telnyx scopes an opt-out to the whole messaging profile: a subscriber who texts STOP to any one number on a profile is blocked from every number on that profile, and a send to them answers error 40300, "Blocked due to STOP message". Keeping separate opt-out lists for separate programs is done by keeping separate profiles.
Bird scopes a suppression to a sender-and-subscriber pair instead. So one Telnyx opt-out against a profile holding twelve numbers becomes twelve Bird suppressions, and a profile with a hundred numbers becomes a hundred. Count before you import: the multiplier is the number of senders you are carrying over from that profile, and it decides whether the import is a loop of hundreds or of tens of thousands.
Preserve the profile-wide withdrawal across all relevant senders. Sender-level storage is not permission to resume a programme under another number. Check whether a workspace-wide preference is the appropriate representation for the person's actual request.
Import through the suppression loop. Reading and managing suppressions carries the command, and the reason a manual suppression blocks every category including transactional.
Recreate the custom keywords and auto-responses configured through autoresp_configs as Bird keyword rules. A send to a suppressed pair is refused at admission with E12077 SMSRecipientSuppressed; a downstream opt-out is a separate recipient_opted_out delivery result. Handle both paths when replacing Telnyx error 40300.
Reasons stack rather than merge, which matters once traffic is flowing: a pair you imported as manual that then texts STOP gets a second record with reason keyword_stop, and messages stay stopped until every record for that pair has ended. Resuming a subscriber you once imported means removing both.
Translate delivery statuses
Use this table to compare lifecycle concepts, not to rename events mechanically. Bird chooses a failure event from the reported status and reason. A refused API request creates no message; a rejection after acceptance can produce sms.rejected, including a carrier rejection. Missing delivery evidence remains unknown. Preserve the raw provider status and code alongside your normalized outcome.
| Outcome | Telnyx | Bird |
|---|---|---|
| API accepted the message | queued | sms.accepted |
| Handed to the carrier | sent, on message.sent | sms.sent |
| Carrier confirmed delivery | delivered, on message.finalized | sms.delivered |
| Delivery failed | delivery_failed | sms.undelivered |
| Permanent failure | sending_failed | sms.failed |
| Request refused at admission | request error | HTTP error; no message or event |
| Validity window elapsed | (none) | sms.expired |
The event shape changes, not just the words. Telnyx sends one message.finalized webhook carrying the terminal state in a status field, so your handler branches on a value inside a single event type. Bird emits distinct event types instead, and you subscribe to the ones you want, so the branch moves out of your code and into the subscription. That is why the left column above names an event and a status together and the right column names only an event.
Two more mechanics change with the names:
- Subscriptions replace the profile's webhook URL. Telnyx posts delivery updates to the URL on the messaging profile, so the destination is a property of the profile every message was sent through. Bird delivers to endpoints your workspace registers, each subscribed to the event types it wants, so a second consumer is a second subscription rather than a change to a shared object.
- Standard Webhooks replaces Telnyx's signature scheme. Bird sends JSON signed per Standard Webhooks; swap the verification for the recipe in Webhooks & events.
Register the endpoint once, naming the event types your handler wants: the sms.* events above are the list to subscribe to, and there is no wildcard that stands in for them. Create an endpoint has the command and the one thing to get right on the first call, which is storing the signing secret the response shows exactly once.
Bird reports a failure with a standardized error code such as invalid_destination, content_rejected, provider_unavailable, or recipient_opted_out; the full list is on the events page. Map your alerting to those.
Cut over
Destinations, senders, and the traffic ramp are provider-independent and covered in the main guide. Two Telnyx-specific items belong on the cutover plan: your 10DLC brand and campaign are registered with The Campaign Registry through Telnyx and do not automatically become Bird registrations. Confirm the applicable migration or registration procedure before submitting paid work. Numbers you own at Telnyx need a port that support arranges, on its own schedule rather than yours.
For the Bird-side requirements, start from Register for 10DLC: it covers what each field means, the entity types the registry recognizes, and the requirements call that tells you what to supply before you create the brand, which is the chargeable step.
Next steps
-
Compare Bird and Telnyx for SMS: product evaluation and migration considerations
-
Sending SMS: the payload you are porting to, in full
-
Opt-outs and keywords: keyword coverage per country and suppression management
-
SMS events: the event vocabulary your webhook handler moves to
-
Webhooks & events: endpoint setup and Standard Webhooks verification
Related resources
Continue with the documentation, guides and examples for this topic. Resources are in English.