Migrate SMS from Bandwidth
This page maps Bandwidth's Messages API, Applications, and message callbacks to Bird. Follow the main migration guide in order and use these mappings for steps 3, 4, and 5.
Two differences shape the whole port. Bandwidth splits the channel across two hosts: sending lives on the messaging host under your account path, authenticated over HTTP Basic, while 10DLC registration lives on the main API host. Bird puts sending, registration and delivery events under one base URL and one bearer key. And the applicationId on every Bandwidth send carries the callback configuration; Bird has no equivalent object, because callbacks are a workspace subscription rather than a property of the message.
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 Bandwidth 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/bandwidth.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 Bandwidth 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 | Bandwidth | Bird |
|---|---|---|
| Recipient | to (array) | to (one per request) |
| Sender | from | from |
| Body | text | text |
| Callback routing | applicationId | a workspace webhook subscribed to the delivery events below |
| Intent | (none) | category, required on free text |
| Free-form label | tag (one string) | metadata; tags only if you can name it |
| Round-trip context | your own store, keyed by ID | metadata: arbitrary JSON, echoed on every event |
| Delivery priority | priority | no equivalent |
| Safe retries | (none in their specification) | Idempotency-Key header |
| Media | media | no equivalent: media_urls is rejected |
Porting notes:
- to collapses from an array to one recipient. Bandwidth takes a list; Bird sends one message per request. A loop replaces the array, and each call can carry its own Idempotency-Key.
- The applicationId disappears rather than moving. It exists to tell Bandwidth where to post callbacks. On Bird that is a workspace subscription, so nothing on the send names it.
- tag and tags are not the same field. Bandwidth's tag is one free-form string; Bird's tags are {name, value} pairs that become query dimensions. A single opaque string is usually better carried in metadata.
- Nothing on the Messages API corresponds to category. Decide per message type whether it is transactional, marketing, authentication, or service.
Carry over opt-outs
There is no list to export, and that is the finding rather than a gap in this guide.
Outside toll-free, Bandwidth does not maintain opt-in or opt-out lists for you. Their own guidance puts it plainly: the onus of honoring the commands and maintaining the lists lies with the customer. Toll-free is the exception, where STOP and its variants are enforced at the network layer regardless of your configuration; long codes and short codes get no such handling.
So on this migration the authoritative list is already yours. It is a table, a flag on a contact record, or a check your send path runs before calling the API, and the first task is deciding which of those is authoritative rather than requesting an export from anyone. Your own inbound-message log is the fallback: some opt-outs began as inbound messages, while others came through support, forms or another preference channel.
Then import through the suppression loop. A Bird suppression is one sender-and-subscriber pair, so a subscriber you stopped across three senders is three records. Reading and managing suppressions carries the command, and the reason a manual suppression blocks every category including transactional.
Decide who owns the list after cutover, because this is where you gain something and can lose track of it. Bird answers stop keywords from its own catalog per country, so once you are sending here the platform maintains suppressions for you: a subscriber who texts STOP produces a record with reason keyword_stop without your application doing anything. If your code keeps its own list and keeps enforcing it, the two drift, and the usual symptom is a subscriber who resumed on one side and not the other. Keep the audience preference owner explicit and synchronize relevant changes deliberately. Sender suppressions alone do not cover workspace-wide preferences or requests outside the keyword catalogue. Reasons stack rather than merge, so a pair you imported as manual that later texts STOP holds two records, and messages stay stopped until both have ended.
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 | Bandwidth callback type | Bird |
|---|---|---|
| API accepted the message | the 202 response, no event | sms.accepted |
| Handed to the carrier | message-sent | sms.sent |
| Carrier confirmed delivery | message-delivered | sms.delivered |
| Never reached the carrier | message-failed | sms.rejected |
| Carrier rejected it | message-failed | sms.failed |
| Carrier reported non-delivery | message-failed | sms.undelivered |
| Carrier gave up | message-failed | sms.expired |
| Request refused at admission | request error | HTTP error; no message or event |
Two things about that table are worth acting on rather than reading past.
Rebuild terminal-state handling around Bird's message record and event timestamps. Webhook deliveries can repeat or arrive out of order; your consumer must not assume one delivery of one final callback. A rejected status and a delivery-failed status can select different Bird events even when both originated downstream.
message-sending has no row because it is MMS-only, and message-read is RBM-only; neither fires for SMS.
Two mechanics change with the names:
- Subscriptions replace the Application. Bandwidth routes callbacks by the applicationId the message named. Bird delivers to endpoints your workspace registers, each subscribed to the event types it wants, so a new consumer is a new subscription rather than a new Application and a redeploy.
- Standard Webhooks replaces their callback authentication. 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.
Cut over
Destinations, senders, and the traffic ramp are provider-independent and covered in the main guide. Two Bandwidth-specific items belong on the cutover plan: your 10DLC brand and campaign are registered with The Campaign Registry through Bandwidth and do not automatically become Bird registrations. Confirm the applicable migration or registration procedure before submitting paid work. Numbers you own at Bandwidth 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 Bandwidth 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 callback 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.