Skip to main content

Cycle de vie de livraison

Chaque message sortant que vous envoyez via Orbit porte un champ status qui progresse pendant que le message voyage de votre appel d’API vers l’appareil du destinataire — ou vers un résultat d’échec. Cette page explique la machine d’états conceptuelle : ce que signifient les états, ce qui pousse chaque transition et où se trouvent les bords dépendants de l’opérateur. Lisez-la avant de souscrire votre premier webhook ou de faire bifurquer votre intégration sur un résultat de message. La sémantique des états, la table complète des transitions et la carte des événements webhook vivent dans la référence du cycle de vie de l’état des messages ; le schéma de réponse pour lire l’état courant d’un message est dans la référence de l’API de messagerie. Cette page relie les deux à un niveau plus élevé.

Le chemin heureux

Un message qui réussit de bout en bout traversе : pending → queued → sending → sent → delivered → read Chaque transition est poussée par un composant différent — aucune partie ne voit tout le chemin : Deux composants se tiennent à côté de ce chemin et peuvent en sortir un enregistrement :
  • Le planificateur no-DLR. Quand un opérateur ne renvoie jamais d’accusé de livraison, le planificateur fait vieillir sent en submitted_no_receipt après une fenêtre de grâce propre au canal — voir Confirmé par l’opérateur vs. intermédiaire sur le fil.
  • Vous (l’expéditeur). L’annulation d’un message programmé non encore envoyé le fixe à cancelled, un état terminal qu’aucun appel au fournisseur ni aucun DLR ne touche. Les envois de sandbox se résolvent en test_sent, un état terminal rapporté avant toute publication au fournisseur. La suppression d’un enregistrement déplace tout état terminal vers deleted ; après, rien ne peut plus le toucher.
Un message programmé pour le futur stationne en scheduled jusqu’à l’heure de lancement, puis rejoint la file. scheduled ne peut être quitté que par trois voies : promotion en queued à l’heure de lancement, cancelled de votre part, ou expired si sa fenêtre de validité expire avant la publication.

Confirmé par l’opérateur vs. intermédiaire sur le fil

La distinction la plus importante pour les rapports et la réconciliation est de savoir si un état est un résultat confirmé par l’opérateur ou une dispute intermédiaire sur le fil :
  • delivered et read sont confirmés par l’opérateur. Un vrai DLR est arrivé ; l’opérateur lui-même a tranché le résultat.
  • submitted_no_receipt est intermédiaire. Il signifie « accepté pour l’envoi, et aucun accusé n’est revenu dans la fenêtre de grâce ». Il est décrit comme intermédiaire (state_class: "intermediate", is_terminal: false) sur chaque webhook, parce qu’un vrai DLR delivered, read ou d’échec peut arriver plus tard et le remplacer.
Traitez submitted_no_receipt comme un résultat inconnu, non comme une livraison. Que l’« inconnu » penche vers le positif ou vers le simple intermédiaire dépend du canal — voir Variations par canal. expired est la vue par l’autre côté : un DLR est arrivé, mais si tard que la fenêtre de l’accusé était déjà fermée. Le résultat ne peut être connu et l’enregistrement est clos ; expired est distribué aux abonnés comme un événement message.failed.

Variations par canal

  • Canaux DM de Meta (Instagram, Messenger). La surface d’envoi de Meta n’émet jamais d’accusés de livraison. L’envoi quitte Orbit en sent, marque no_dlr_channel dans les métadonnées du message, et décroît en submitted_no_receipt après 5 minutes. Un destinataire accepté de manière optionnelle implique que Meta garantit la livraison à l’acceptation — donc sur ces canaux, submitted_no_receipt se comporte comme un signal fonctionnel de livraison, et les métadonnées du message portent no_dlr_channel: true pour que vous puissiez distinguer l’état.
  • Canaux desservis par SMPP (SMS, MMS, voix, fax, RCS). La fenêtre de grâce est de 30 minutes. Ici, submitted_no_receipt est un véritable intermédiaire : l’appareil a peut-être reçu le message sans accusé rapporté, l’opérateur n’émet peut-être jamais d’accusés sur cette route, ou l’accusé s’est peut-être perdu en transit. Suivez cette proportion comme un signal distinct du taux de livraisons — une proportion installée de submitted_no_receipt sur une destination signale une route mal desservie ou une route d’accusés cassée, et mérite une enquête dans les deux cas.
  • E-mail. Ajoute un résultat d’échec que les canaux DM purs n’ont pas : bounced, quand le serveur de messagerie réception refuse le message. Le rebond compte dans votre taux d’échec final comme failed, mais c’est un état séparé qui vous permet de distinguer les refus côté destinataire de ceux côté fournisseur.
  • Annulation de l’expéditeur. cancelled n’est rapportée que par vous — via POST /messages/:id/cancel sur un message non encore envoyé. Aucun opérateur ne l’écrit jamais, et par conséquent cela ne déclenche aucun événement webhook ; rien n’informe l’abonné de l’annulation. Appelez GET /messages/:id si vous exposez l’annulation dans votre interface et devez l’observer.
  • Sandbox / mode test. Les envois de test se résolvent en test_sent avant toute publication au fournisseur. Ils déclenchent message.sent avec status: "test_sent" et metadata.test_mode: true, donc l’abonné doit bifurquer sur metadata.test_mode pour garder le trafic sandbox hors du traitement de production.

Sur quoi bifurquer

Les intégrations ne doivent bifurquer que sur des champs lisibles par machine, jamais sur des descriptions affichées :
  • status — l’état courant du message. C’est le point de bifurcation primaire. Traitez l’ensemble complet : outre les états du chemin heureux et le résultat d’échec, n’oubliez pas cancelled, test_sent, submitted_no_receipt et bounced.
  • metadata.classified_error_code — présent sur les résultats d’échec terminaux ; la catégorie d’échec définie par machine et activable par machine. Combinez-la avec le error_code / error_message brut du webhook message.failed quand vous voulez les mots de l’opérateur.
  • metadata.no_dlr_channel — présent sur les enregistrements DM de Meta ; vous dit que submitted_no_receipt est l’état de livraison garanti par Meta, pas l’intermédiaire traduit de SMPP.
  • state_class / is_terminal — sur le webhook du cycle de vie. is_terminal: true (associé à state_class: "terminal") signifie que le résultat est final ; submitted_no_receipt rapporte is_terminal: false précisément pour que vous ne fermiez pas le livre dessus.

Erreurs fréquentes

  1. Traiter message.created comme l’acceptation. message.created se déclenche au moment où l’enregistrement est mis en file, avant tout appel au fournisseur. Un refus synchrone (4xx à l’envoi, ou un rejected/failed immédiat) laisse cet événement livré tout de même. Combinez-le avec message.sent avant de conclure que le message est parti.
  2. Supposer que delivered est immuable. Les opérateurs sur certaines routes émettent un accusé de livraison puis une correction quelques minutes plus tard — certains opérateurs indiens et brésiliens le font. Orbit le respecte : un enregistrement peut passer de delivered → undelivered ou de delivered → failed. Si vous reflétez les états dans votre propre magasin de données, appliquez les mises à jour de façon idempotente par id de message au lieu de jeter les transitions pour un message déjà servi.
  3. Attendre un webhook sur cancelled. Il n’arrivera jamais — l’annulation est liée à votre appel d’API, pas à un appel de l’opérateur, donc la plateforme n’émet pas d’événement pour elle. L’enregistrement reste simplement en cancelled jusqu’à ce que vous le supprimiez.
  4. Jeter submitted_no_receipt dans le flux des échecs. Il arrive comme type d’événement message.failed pour des raisons de classification (pas de route à part), mais bifurquez sur data.status, pas sur le type d’événement, et tenez-le à l’écart des tons d’échec durs.
  5. Attendre un seul événement final par message. Un message peut émettre message.failed avec status: "submitted_no_receipt" puis message.delivered, quand l’accusé lent de l’opérateur arrive enfin dans la fenêtre de retard. Mettez à jour et dédupliquez par message_id, et laissez l’événement le plus tardif l’emporter.
Une fois la machine d’états claire, les charges utiles d’événements par état sont dans la référence des événements webhook, et la sémantique des états seule dans la référence du cycle de vie de l’état des messages.