Migrate SMS from Sinch
This page maps Sinch's SMS API, groups, and delivery reports to Bird. Follow the main migration guide in order and use these mappings for steps 3, 4, and 5.
Two structural differences shape the port, and both cost more than the field renames. Sinch keys the send on a service plan in the URL path and sends a batch, so one message to one person is still an array; Bird's POST /v1/sms/messages takes one recipient on your regional host with a bearer key and no plan segment. And the US registration you cannot skip is on a different host from the send, behind a different credential family, so a codebase that reaches Sinch for both is reaching two places.
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 Sinch 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/sinch.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 Sinch 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 | Sinch | Bird |
|---|---|---|
| Recipient | to (array, or a group ID) | to (one per request) |
| Sender | from | from |
| Body | body | text |
| Account routing | service plan, in the URL path | the bearer key; no path segment |
| Intent | (none) | category, required on free text |
| Delivery reporting | delivery_report + callback_url, per batch | a workspace webhook; no per-send control |
| Correlation | client_reference | metadata, echoed on every event |
| Filterable labels | (none) | tags: {name, value} pairs |
| Safe retries | (none documented) | Idempotency-Key header |
| Flash | flash_message | no equivalent |
Porting notes:
- Choose a single send, a batch or a broadcast deliberately. to is an array on Sinch and a single number here, so use single sends or the batch endpoint for up to 100 independent messages. An audience campaign belongs to the broadcast workflow. A batch that named a group needs the membership resolved first; see the opt-out section, because that is the same problem.
- body becomes text. It is the one rename that touches every call site.
- client_reference is not an idempotency key. Sinch defines it as an identifier added to the batch's delivery report, so it correlates but does not deduplicate. If you were relying on it to make a retry safe, you were not covered; Idempotency-Key is what does that here.
- Nothing corresponds to category. Decide per message type whether it is transactional, marketing, authentication, or service.
Carry over opt-outs
Sinch records who is in, and Bird needs to know who is out. That inversion is the work.
Sinch manages recipients as groups, and a group can auto-update from keyword triggers, so a subscriber who texts STOP is removed from the group and a subscriber who texts SUBSCRIBE is added. The opt-out is therefore encoded as absence from a list rather than presence on one, and absence is not exportable: a number missing from a group may have opted out, may never have joined, or may have been removed by an import six months ago.
So reconstruct rather than export. Your own inbound-message log is the reliable source, because some opt-outs began as inbound messages, while others came through support, forms or another preference channel, and those messages exist whatever the group membership now says. Where you kept your own unsubscribe flag alongside the group, that flag is better evidence than the membership. Take the reconstructed list into the suppression loop, and show the list to whoever owns the account before importing it: a wrong entry here silently stops messages you intended to send.
A Bird suppression is one sender-and-subscriber pair, so a subscriber you stop across three senders is three records. Reading and managing suppressions carries the command, and the reason a manual suppression blocks every category including transactional.
Once you are here, Bird answers the stop keywords itself from its own catalog per country, so the group auto-update behaviour has no counterpart to rebuild: a subscriber who texts STOP produces a suppression without your application doing anything. 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.
Sinch's delivery-report reference includes queued, dispatched, delivered and several distinct final failure states. Preserve the recipient-level code and status when translating your reporting.
| Sinch concept | Bird integration decision |
|---|---|
| Queued / Dispatched | Track acceptance and carrier submission separately with sms.accepted and sms.sent. |
| Delivered | Record the network outcome through sms.delivered; it does not prove reading. |
| Failed / Rejected / Deleted | Inspect the reported reason. Bird failure events are not selected by a name-only substitution. |
| Aborted / Expired / Cancelled | Preserve the cause and stage. Bird's individual-send API has no scheduling or validity timer to recreate these controls. |
| Unknown | Keep the outcome uncertain; do not count a missing interpretable receipt as delivery. |
Bird's sms.expired follows a carrier expiry report. Review your existing expiry and cancellation behavior separately from that event rather than mapping every timeout to it.
Note also that intermediate statuses are only reported when the batch asked for per_recipient reporting, which is part of what changes below.
You lose per-send control of delivery reporting, and it is worth saying plainly. A Sinch batch chooses its own report granularity and can override the service plan's callback URL for that send alone. Bird has neither: reporting is a workspace subscription, every subscribed event is delivered, and there is no per-message override. If you were using delivery_report to keep chatty campaigns quiet, that filtering moves into your handler. If you were routing one campaign's reports to a different endpoint, that becomes one endpoint plus a branch, or a second subscription.
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. Bird sends JSON signed per Standard Webhooks; 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.
Cut over
Destinations, senders, and the traffic ramp are provider-independent and covered in the main guide. Two Sinch-specific items belong on the cutover plan.
Your 10DLC brand and campaign are registered with The Campaign Registry through Sinch and do not automatically become Bird registrations. Confirm the applicable migration or registration procedure before submitting paid work. This is also where the integration simplifies. On Sinch the registration API is a separate host from the send and takes project credentials rather than the service plan token, and Sinch's own documentation says HTTP Basic there is intended for test purposes only and is heavily rate limited, so a production integration builds an OAuth token flow for it. On Bird /v1/sms/10dlc/* sits beside /v1/sms/messages under one base URL and one key, so that token lifecycle is retired rather than ported. Start from Register for 10DLC, which covers what each field means and the requirements call that tells you what to supply before you create the brand, which is the chargeable step.
Numbers you own at Sinch need a port that support arranges, on its own schedule rather than yours.
Next steps
-
Compare Bird and Sinch 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 report 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.