Skip to main content

SMS

Send SMS messages worldwide through Orbit’s SMPP gateway infrastructure. Orbit connects to tier-1 carriers and aggregators across 190+ countries, handling concatenation, encoding, and delivery tracking automatically. This page walks you through the channel end to end: which sender to attach, where registration is required before traffic is accepted, how encoding decides what each send costs, what the status lifecycle tells you, and how to read failures when they come back.

Send an SMS

Both to and from are validated as E.164 — full international format with the country prefix, like +14155552671. See how Orbit validates and normalizes numbers in the Numbers lifecycle.

Response

The endpoint responds with HTTP 202 Accepted — the message is persisted and queued for delivery (it has not yet been handed to the carrier). The id is a platform message id of the form msg_ followed by 32 lowercase hex characters (no channel infix); track its lifecycle via delivery webhooks.
Read segments and encoding on every send — they are what billing charges against, and the Encoding & concatenation section below shows how to predict both before you send.

SMS deliverability fundamentals

SMS deliverability turns on three things, in this order: who you send from, where, and how fast. Get those right and encoding becomes the only cost variable left.

Choose a sender type

Senders exist for one reason each: deliverability. You need a sender ID approved for the destination country’s rules, or the carrier filters the traffic before it costs you a segment — see Country Compliance Requirements.

Onboarding via the dashboard

The dashboard walkthrough takes under five minutes and catches the two failure modes below before you write a line of code.
  1. Open Channels → SMS in the dashboard.
  2. Click Attach Sender and pick a sender type above. Orbit validates the format (E.164 number, 3–11 alphanumeric ID, or pool reference) and checks whether the destination you pick as a default requires registration.
  3. Send a test message to a number you own. The composer surfaces segments and encoding inline, so you see the estimated per-segment cost before you send.
  4. If your destination gate rejects the attach (US without a 10DLC campaign, a gated country without a sender-ID approval), the wizard routes you to the registration flow — see the next two sections.
Once a sender is attached here, the same sender is selectable from the SenderIdSelect in the composer and runs through the registered send path. This is also how you audit what GET /api/v1/messaging/senders returns over the API.

10DLC registration & campaign types (US)

If you send to US destinations over numeric long codes, the carriers require registration; unregistered traffic is filtered, surcharged, or both. Orbit submits registration through The Campaign Registry on your behalf — the full walkthrough (brand → campaign → approval → throughput tiers) is in the 10DLC Registration guide. The US-specific point to hold in your head: throughput after registration is granted as a per-day, per-number segment cap based on your brand vetting score — not a per-second rate. If you need more daily capacity, raise your vetting score (complete brand data, EIN, website) or assign more numbers to the campaign. For non-US destinations that gate alphanumeric sender IDs (most of Europe, Brazil, Bangladesh, parts of APAC), the parallel registration flow is Sender-ID Registration, which records your approval per country and attaches the supporting KYC documents. The send-time gate refuses traffic to gated countries until the matching country’s entry is approved — no silent filtering, the failure comes back as a visible rejected. India is the exception: its sender IDs (“Headers”) register through DLT/TRAI rather than the generic flow — see DLT-India Onboarding.

Encoding & concatenation

Encoding is the only cost variable that survives a properly registered sender. SMS bills per segment, and the segment count depends entirely on which alphabet the body fits in.

GSM-7 vs UCS-2

  • GSM-7 — the 7-bit GSM alphabet (ASCII plus a few extras: @, £, , some Greek). A body that fits it gets up to 160 characters per segment, or 153 characters on a multi-part send (7 per segment go to the UDH concatenation header).
  • UCS-2 — any character outside GSM-7 (emoji, CJK, smart quotes, most non-Latin scripts) flips the whole message to 16-bit encoding. A body gets up to 70 characters per segment, or 67 on multi-part.
One non-GSM-7 character reclassifies the entire message. Orbit normalizes the accidental offenders — copy-pasted smart quotes, em dashes, curly apostrophes, invisible zero-width characters — to GSM-7-safe equivalents before it counts, so paste-from-docs mistakes do not double your segment bill. The full encoding and segmentation model — capacity tables, the 10-segment cap, and why the composer matches billing — is in the SMS segments and encoding concept. A single workspace formula you can apply by hand:

Worked example

Your order shipped — reply YES — 30 characters, all GSM-7 → 1 segment, GSM-7. Appending a single emoji flips the alphabet: 54 chars in UCS-2 → still 1 segment, but a 160-character ASCII newsletter becomes 2 UCS-2 segments the moment one emoji sneaks in. At scale, that is the number that drives cost. The platform counts segments before it dispatches; the send response carries the verdict:
Hard ceiling: 10 segments (1,530 GSM-7 characters or 670 UCS-2 characters). Bodies beyond that are truncated and the send proceeds; the segment counter flags wasTruncated: true so you see it rather than wonder where the tail went. This is what the features list calls “concatenated messages up to 10 segments.” Read segments on every send and branch your billing estimates on it — it is exactly what per-segment pricing multiplies against.

Rate limits, throughput & sender pools

Two independent limits sit on SMS throughput, and you have to clear both:
  1. API-side request limit. 100 requests/minute per tenant by default on POST /messages/sms, keyed per organization. Over it you get 429 RATE_LIMITED; see the Rate Limits guide for headers and retry handling. The full per-channel table lives below.
  2. Sender-side throughput. US 10DLC: a per-day, per-number segment cap set by your vetting tier. Elsewhere: whatever the destination’s route supports per sender.
When either ceiling binds, a sender pool is the lever: add more registered senders to the pool, spread volume across them on a sticky, round_robin, random, or geomatch strategy, and per-sender throughput multiplies by pool size. Pools also let a degraded sender rotate out without your send code changing — the pool health endpoint tells you which member is degrading before it becomes a deliverability problem.

Message status lifecycle

Every outbound SMS carries a status that advances as the message moves from your API call toward the handset — or toward a terminal failure: queuedsendingsentdelivered (or failed / undelivered / rejected / expired) Subscribe to status transitions via webhooks using the message.sent, message.delivered, and message.failed events — one webhook per transition, each carrying the message id and the new status value. The nuances that catch operators:
  • submitted_no_receipt — the provider accepted the send, no DLR came back inside the 30-minute SMPP grace window. Tagged intermediate (is_terminal: false) because a late delivered or failure DLR can still land and overwrite it. Treat it as outcome unknown, not a delivery and not a failure.
  • expired — a DLR arrived after the late-arrival window closed. Closed row, unknowable outcome; it fans out on message.failed.
  • cancelled — reachable only by you; no webhook ever fires for it.
The concept-level walkthrough of this state machine, the per-channel grace windows, and what is_terminal means lives in Delivery lifecycle; the per-status semantics and full transition table are in the message status lifecycle reference.

Troubleshooting

When message.failed arrives, branch on the payload’s status, not the event type — submitted_no_receipt and expired fan out on that event too. Read metadata.classified_error_code for machine handling, and error_code / error_message for the carrier’s own wording when you open a ticket. The escalation pack that gets a support ticket answered first pass: your tenant ID, one message ID, and the row’s classified_error_code — plus Provider Reference from the Delivery Log for DLR-symptom rows. The delivery log guide shows how to filter the status; Number Lookup is how you tell a dead number from a live one before re-sending.

Pricing

SMS pricing is per-segment and varies by destination country. Check the pricing page or query the GET /api/v1/pricing/messaging?country=<ISO> endpoint for real-time rates (the returned channels array includes sms). The planning number comes from the previous sections: multiply the per-segment rate the pricing endpoint returns by the segments value the send response carries. Two otherwise identical sends bill differently the moment one’s encoding flips to UCS-2 — see Encoding & concatenation for when that happens and how to normalize it back.

Features

  • A2P and P2P messaging — application-to-person and person-to-person support
  • Global reach — 190+ countries via Devotel’s aggregator network
  • Delivery reports — real-time status webhooks for every message
  • Concatenated messages — multi-part SMS handled transparently (up to 10 segments)
  • E.164 validation — automatic phone number format validation
  • Unicode support — full GSM-7 and UCS-2 encoding with automatic detection and smart-punctuation normalization
  • Sender ID — alphanumeric sender IDs where supported by local regulations, registration-gated where required

Rate Limits

SMS sends are rate-limited to 100 requests/minute per tenant by default on the /messages/sms endpoint. The limit is keyed per tenant (per organization), not per IP or per plan, so concurrent callers within the same tenant share one pool while separate tenants never affect each other. Need a higher ceiling? The per-tenant cap is override-able via your organization’s messaging settings (channel_rate_overrides.sms) — contact support or set it from the dashboard. Exceeding the limit returns 429 Too Many Requests with the RATE_LIMITED error code; see the Rate Limits guide for headers and retry handling. The same per-channel, per-minute, per-tenant model applies across messaging channels (each independently override-able):