Skip to main content

SMS

Envoyez des SMS dans le monde entier via l’infrastructure de passerelle SMPP d’Orbit. Orbit se connecte à des opérateurs de premier rang et à des agrégateurs dans plus de 190 pays, et gère la concaténation, l’encodage et le suivi de la livraison automatiquement. Cette page parcourt le canal de bout en bout : quel expéditeur attacher, où l’enregistrement est requis avant que le trafic soit accepté, comment l’encodage décide ce que coûte chaque envoi, ce que le cycle de vie de l’état vous dit, et comment lire les échecs quand ils reviennent.

Envoyer un SMS

Les deux to et from sont validés comme E.164 — format international complet avec le préfixe du pays, comme +14155552671. Voyez comment Orbit valide et normalise les numéros dans le cycle de vie des numéros. Utilisez une clé sandbox (dv_test_sk_… sur curl, ORBIT_API_KEY dans le code) pendant le prototypage — les envois en sandbox sont simulés, gratuits et n’atteignent jamais un opérateur. Basculez sur la clé live quand vous passez en production ; rien du code ci-dessus ne change. L’onglet Node ci-dessus utilise l’helper générique orbit.messages.send({ channel: 'sms', ... }) ; la méthode typée par canal orbit.messages.sendSms({ to, body, from }) fait le même POST — choisissez une forme et utilisez-la de manière consistante.

Réponse

Le point répond HTTP 202 Accepted — le message est persisté et en file pour livraison (pas encore livré à l’opérateur). L’id est un id de message de plateforme de la forme msg_ suivi de 32 caractères hex minuscules (pas d’infixe de canal) ; suivez son cycle de vie via les webhooks de livraison.
Quel onglet de langue vous ayez utilisé, l’enveloppe est celle ci-dessus — lisez data.id pour poller ou réconcilier plus tard, et bifurquez sur data.status (il commence à queued) :
  • Node.js / fetch (TypeScript) — le SDK ou la valeur de retour de res.json() portent l’enveloppe : message.data.id, message.data.status.
  • Python — send_sms renvoie le corps JSON brut comme dict : message["data"]["id"], message["data"]["status"].
  • Go — SendSMS décompresse l’enveloppe dans un struct typé : msg.Data.ID, msg.Data.Status.
  • Ruby / PHP (voie d’évitement) — client.request renvoie le corps JSON brut : send.dig("data", "id") / $send['data']['id'].
Lisez segments et encoding à chaque envoi — c’est ce que la facturation facture, et la section Encodage et concaténation ci-dessous montre comment prédire les deux avant d’envoyer.

Patrons communs

Deux patrons couvrent presque tout l’intégration SMS de production : retries sûrs à l’envoi, et paginage du historique des messages.

Envoie idempotent avec un Idempotency-Key

Les points POST qui créent des ressources acceptent un en-tête Idempotency-Key. Une retry qui présente la même clé renvoie l’enveloppe originale — pas d’envoi dupliqué, pas de charge secondaire. Les SDKs mint un UUID par appel automatiquement ; sur HTTP brut vous le posez explicitement. De toute façon, passez une clé stable sur les essais d’un envoi logique au lieu de laisser chaque retry mint la sienne.
Node
Python
Le contrat complet est dans Idempotence et retries sûrs.

Paginer GET /messages via les appels listes du SDK

L’historique des messages est paginé par curseur : chaque page porte meta.pagination.cursor et meta.pagination.has_more ; passez le curseur en retour jusqu’à has_more soit false. Le SDK Node livre une boucle de curseur et un itérateur paginant auto ; les autres SDKs paginent via leurs méthodes liste ou la voie d’évitement :
Node
Python
Le modèle de pagination complet — familles curseur vs. offset, plafonds de taille de page et pourquoi les curseurs ne sont pas bookmarkables — est dans le guide de pagination.

Exemple complet : envoyer, poller, gérer les échecs

La séquence ci-dessous est la même de bout en bout : POST l’envoi, pollez GET /messages/{id} (ou prenez le webhook — poller est le secours), et bifurquez de façon deterministe quand quelque chose échoue. Le corps est délibérément un envoi UCS-2 pour que vous voyiez le verdict d’encodage revenir au lieu de présupposer le prix GSM-7.

1. Envoyez le message

Les envois API directs doivent déclarer un expéditeur — from, sender_pool_id, messaging_service_id ou from_extension — sinon le point renvoie HTTP 422 avec SENDER_REQUIRED (p. ex. quand un paste comme "from": null se glisse dans votre requête). Voyez Choisissez un type d’expéditeur pour ce que signifie chaque selecteur.

2. Lisez la réponse d’envoi

status: queued est l’état de départ — rien n’est encore livré, et aucun external_id (référence côté opérateur) n’est revenu. Les champs segments et encoding sont exactement ce que la tarification par segment multiplie contre ; traitez-les comme la facture, pas un indice.

3. Pollez la ligne

Quand vous ne pouvez pas (ou ne voulez pas) vous abonner aux webhooks, pollez GET /messages/{id} sur l’id que l’envoi a renvoyé. La ligne avance par sending → sent → delivered / un échec terminal. Chaque réponse monte aussi metadata.classified_error_code pour les échecs terminaux et metadata.no_dlr_channel pour les lignes Meta-DM, alors bifurquez sur le status lisible par machine, jamais sur des chaînes d’étiquette.
Un poll sans webhooks attrapera habituellement des états intermédiaires aussi — sending (le fournisseur l’a prise) ou sent (le fournisseur l’a acceptée) avant delivered. Ajoutez ?include=webhook_deliveries quand vous voulez l’historique de fan-out dans le même tour.

4. Bifurquez sur les échecs

Bifurquez sur le status du poll, le data.status du webhook, ou metadata.classified_error_code — ils disent tous la même histoire. Les familles d’échec ci-dessous sont celles que vous avez à gérer réalitement : Sur tout webhook message.failed, data.status est le champ à bifurquer — submitted_no_receipt et expired montent le même événement, et un webhook message.delivered ultérieur peut atterrir même après l’un d’entre eux.

Fondements de la deliverability SMS

La deliverability SMS tourne sur trois choses, dans cet ordre : de qui vous envoyez, où, et à quelle vitesse. Faites-les bien, et l’encodage devient la seule variable de coût qui reste.

Choisissez un type d’expéditeur

Les expéditeurs existent pour une raison chacun : deliverability. Vous avez besoin d’un ID expéditeur approuvé pour les règles du pays destination, ou l’opérateur filtre le trafic avant qu’il vous coûte un segment — voyez Requis de conformité par pays.

Onboarding via le tableau de bord

La visite du tableau de bord prend moins de cinq minutes et attrape les deux modes d’échec ci-dessous avant d’écrire une ligne de code.
  1. Ouvrez Channels → SMS dans le tableau de bord.
  2. Cliquez Attach Sender et choisissez un type d’expéditeur ci-dessus. Orbit valide la forme (numéro E.164, ID alphanumérique de 3–11, ou référence de bassine) et vérifie si la destination que vous choisissez comme défaut requière l’inscription.
  3. Envoyez un message de test à un numéro que vous possédez. Le compositeur montre segments et encoding en ligne, donc vous voyez le coût par segment estimé avant d’envoyer.
  4. Si votre port de destination rejette l’attache (US sans campagne 10DLC, pays gaté sans validation d’ID expéditeur), l’assistant vous dirige vers le flux d’inscription — voir les deux sections suivantes.
Une fois un expéditeur attaché ici, le même expéditeur est sélectionnable du SenderIdSelect dans le compositeur et passe par le chemin d’envoi inscrit. C’est aussi comment vous auditez ce que GET /api/v1/messaging/senders renvoie via API.

Inscription 10DLC & types de campagnes (US)

Si vous envoyez à des destinations US via des codes longs numériques, les opérateurs exigent l’inscription ; le trafic non-inscrit est filtré, surtaxé, ou les deux. Orbit soumet l’inscription via The Campaign Registry à votre nom — la visite complète (marque → campagne → approbation → étages de débit) est dans le guide d’inscription 10DLC. Le point US-spécifique à tenir à l’esprit : le débit après inscription est accordé comme un plafond de segment par jour, par numéro basé sur votre score de vetting de marque — pas un taux par seconde. Si vous avez besoin de plus de capacité quotidienne, montez votre score de vetting (données de marque complètes, EIN, site web) ou attribuez plus de numéros à la campagne. Pour les destinations non-US qui gâtent les ID expéditeurs alphanumériques (majorité de l’Europe, Brésil, Bangladesh, parties de APAC), le flux parallèle d’inscription est Inscription de l’ID expéditeur, qui enregistre votre approbation par pays et attache les documents KYC de support. La port au temps d’envoi rejette le trafic vers les pays gatés tant que l’entrée du pays correspondant n’est pas approved — pas de filtrage silencieux, l’échec revient comme un rejected visible. L’Inde est l’exception : ses ID expéditeurs (“Headers”) s’inscrivent via DLT/TRAI au lieu du flux générique — voyez Onboarding DLT-Inde.

Encodage et concaténation

L’encodage est la seule variable de coût qui survit un expéditeur bien inscrit. SMS facture par segment, et le compte de segments dépend entièrement de quel alphabet le corps rentre.

GSM-7 contre UCS-2

  • GSM-7 — l’alphabet GSM 7-bit (ASCII plus quelques extras : @, £, €, quelques grec). Un corps qu’y rentre obtient jusqu’à 160 caractères par segment, ou 153 caractères en envoi multi-parties (7 par segment vont vers l’en-tête UDH de concaténation).
  • UCS-2 — tout caractère hors GSM-7 (emoji, CJK, quotes intelligentes, scripts les plus non-Latin) bascule tout le message vers un encodage 16-bit. Un corps obtient jusqu’à 70 caractères par segment, ou 67 en multi-parties.
Un seul caractère non-GSM-7 reclassifie tout le message. Orbit normalise les intrus accidentels — quotes intelligentes collées, tires longs, apostrophes recourbées, caractères encombrants de largeur zéro — en équivalents sûrs-GSM-7 avant de compter, pour que les erreurs de coller-des-documents ne doublent pas votre facture de segments. Le modèle complet d’encodage et de segmentation — tableaux de capacité, le plafond de 10 segments et pourquoi le compositeur correspond à la facturation — est dans le concept Segments SMS et encodage. Une formule de travail que vous pouvez appliquer manuellement :

Exemple complet

Votre commande expédiée — répondez YES — 30 caractères, tous GSM-7 → 1 segment, GSM-7. Coller un seul emoji bascule l’alphabet : 54 caractères dans UCS-2 → toujours 1 segment, mais un bulletin ASCII de 160 caractères devient 2 segments UCS-2 dès qu’un emoji entre. À l’échelle, c’est le nombre qui dirige le coût. La plateforme compte les segments avant de dispatché ; la réponse d’envoi porte le verdict :
Plafond raide : 10 segments (1.530 caractères GSM-7 ou 670 caractères UCS-2). Les corps au-delà sont tronqués et l’envoi continue ; le compteur de segments marque wasTruncated: true pour que vous le voyez plutôt que de vous demander où est partie la queue. C’est ce que la liste des fonctionnalités appelle “messages concaténés jusqu’à 10 segments”. Lisez segments à chaque envoi et bifurquez vos estimations de facturation dessus — c’est exactement ce que la tarification par segment multiplie contre.

Limites de débit, débit & bassines d’expéditeurs

Deux limites indépendantes sur le débit SMS, et vous devez dégager les deux :
  1. Limite de requête côté API. 100 requêtes/minute par tenant par défaut sur POST /messages/sms, clé par organisation. Au-delà vous recevez 429 RATE_LIMITED ; voyez le guide des limites de débit pour les en-têtes et la gestion des retries. Le tableau complet par canal ci-dessous.
  2. Débit côté expéditeur. US 10DLC : un plafond de segment par jour, par numéro fixé par votre étage de vetting. Ailleurs : ce que la route de destination supporte par expéditeur.
Quand l’un des plafonds lie, une bassine d’expéditeurs est le levier : ajoutez plus d’expéditeurs inscrits à la bassine, répartissez le volume sur une stratégie sticky, round_robin, random ou geomatch, et le débit par expéditeur multiplie par taille de bassine. Les bassines laissent aussi un expéditeur dégradé sortir sans que votre code d’envoi change — le point de santé bassine vous dit quel membre dégrade avant que ça devienne un problème de deliverability.

Cycle de vie de l’état du message

Chaque SMS sortant porte un status qui avance quand le message se déplace de votre appel API vers le handset — ou vers un échec terminal : queued → sending → sent → delivered (ou failed / undelivered / rejected / expired) Abonnez-vous aux transitions d’état via webhooks en utilisant les événements message.sent, message.delivered et message.failed — un webhook par transition, chacun portant l’id du message et la nouvelle valeur status. Les nuances qui attrapent les opérateurs :
  • submitted_no_receipt — le fournisseur a accepté l’envoi, aucune DLR n’est revenue dans la fenêtre de grâce SMPP de 30 minutes. Étiqueté intermédiaire (is_terminal: false) parce qu’une delivered tardive ou une échec DLR peut encore atterrir et écrire par-dessus. Traitez-le comme résultat inconnu, pas une livraison et pas un échec.
  • expired — une DLR a arrivé après que la fenêtre de laisse-arrière tardive se soit fermée. Ligne fermée, résultat non connaissable ; fan-out sur message.failed.
  • cancelled — accessible seulement par vous ; aucun webhook ne tir jamais pour ça.
La visite de niveau concept de cette machine à états, les fenêtres de grâce par canal et ce que signifie is_terminal vit dans Cycle de vie de la livraison ; les sémantiques par état et le tableau des transitions complet sont dans la référence du cycle de vie de l’état du message.

Dépannage

Quand message.failed arrive, bifurquez sur le status de la charge, pas le type d’événement — submitted_no_receipt et expired fan-out sur ce même événement. Lisez metadata.classified_error_code pour le traitement par machine, et error_code / error_message pour la propre formulation de l’opérateur quand vous ouvrez un ticket. Le pack d’escalade qui fait répondre un ticket de support au premier passage : votre ID tenant, un ID message, et la classified_error_code de la ligne — plus Référence fournisseur du Journal de livraison pour les lignes symptomatiques de DLR. Le guide du journal de livraison montre comment filtrer l’état ; Recherche de numéro est comment vous distingué un numéro mort d’un vivant avant de reprendre.

Tarification

La tarification SMS est par segment et varie par pays de destination. Consultez la page de tarification ou demandez le point GET /api/v1/pricing/messaging?country=<ISO> pour les tarifs en temps réel (le tableau renvoyé channels inclut sms). Le nombre de planification vient des sections précédentes : multipliez le taux par segment que le point de tarification renvoie par la valeur segments que la réponse d’envoi porte. Deux envois sinon identiques facturent différemment dès que l’autre bascule vers UCS-2 — voyez Encodage et concaténation pour quand cela se produit et comment le normaliser de nouveau.

Fonctionnalités

  • Messagerie A2P et P2P — support application-à-personne et personne-à-personne
  • Portée globale — 190+ pays via le réseau d’agrégations de Devotel
  • Rapports de livraison — webhooks d’état en temps réel pour chaque message
  • Messages concaténés — SMS multi-parties géré de manière transparente (jusqu’à 10 segments)
  • Validation E.164 — validation automatique du format du numéro de téléphone
  • Support Unicode — encodage GSM-7 et UCS-2 complet avec détection automatique et normalisation des quotes intelligentes
  • ID expéditeur — ID expéditeurs alphanumériques là où les réglementations locales les supportent, inscrits-gâtés là où requis

Limites de débit

Les envois SMS sont limités à 100 requêtes/minute par tenant par défaut sur le point /messages/sms. La limite est clé par tenant (par organisation), pas par IP ou par plan, donc les appelants concurrents dans le même tenant partagent une file alors que les tenants séparés ne s’affectent jamais. Besoin d’un plafond plus haut ? Le plafond par tenant est surmontable via les paramètres de messagerie de votre organisation (channel_rate_overrides.sms) — contactez le support ou configurez-les depuis le tableau de bord. Dépasser la limite renvoie 429 Too Many Requests avec le code d’erreur RATE_LIMITED ; voyez le guide des limites de débit pour les en-têtes et la gestion des retries. Le même modèle par canal, par minute, par tenant s’applique sur les canaux de messagerie (chacun surmontable indépendamment) :