Retour au blog Connectivité et opérations SMS

Identifiants de messages A2P SMS : normaliser les références sans perdre la traçabilité

Concevez un modèle d’identifiants pour distinguer l’événement métier, la tentative technique et les références externes HTTP, SMPP, fournisseurs et DLR. Évitez les écrasements, les collisions et les associations erronées lors des changements de route ou des états tardifs.

Schéma de traçabilité entre un événement métier, des tentatives techniques, des identifiants de fournisseur et des DLR SMS

Le problème : un SMS peut accumuler plusieurs références

Un même envoi logique peut générer plusieurs identifiants au cours de son parcours opérationnel. L’application émettrice peut créer sa propre référence ; une API HTTP peut renvoyer un ID de ressource ; un SMSC ou MC peut renvoyer un message_id dans submit_sm_resp ; le fournisseur peut envoyer une référence différente dans un DLR ; et le système qui reçoit le callback peut attribuer son propre ID d’événement.

Ces références ne sont pas interchangeables. Un identifiant renvoyé par un fournisseur relève généralement du périmètre de cette plateforme, de ce compte, de cette intégration et de cet environnement. Il ne doit pas être traité comme une clé primaire métier globale, ni supposé unique entre fournisseurs, routes, comptes ou environnements.

Le risque apparaît lorsqu’un système écrase un ID par un autre, transforme de manière irréversible des valeurs externes ou relie des événements sur la seule base d’une correspondance textuelle. Cela peut conduire à attribuer un ancien DLR à un renvoi, à mélanger les états de deux fournisseurs ou à perdre les preuves nécessaires pour analyser un incident.

  • L’ID de l’événement métier identifie l’intention : par exemple, une demande d’OTP ou une notification transactionnelle autorisée.
  • L’ID de la tentative technique identifie une exécution précise d’envoi vers un compte, un fournisseur et une route donnés.
  • L’ID externe identifie la ressource ou le message au sein du système qui l’a émis.
  • L’ID de l’événement de callback identifie la notification reçue, et pas nécessairement le message SMS auquel elle se rapporte.
Le problème : un SMS peut accumuler plusieurs références

Quels identifiants peuvent exister en HTTP, SMPP et DLR

L’inventaire exact dépend du contrat de chaque intégration, mais il est utile de modéliser des catégories stables. L’objectif n’est pas d’imposer une nomenclature universelle, mais d’enregistrer ce que représente chaque référence, qui l’a émise et dans quel contexte elle peut être utilisée.

En SMPP, submit_sm_resp renvoie un message_id attribué par le MC ou le SMSC. La spécification le place dans le périmètre du système qui accepte le submit et permet de l’utiliser dans des opérations ultérieures, telles qu’une interrogation, un remplacement ou une association avec un accusé de réception. Il doit donc être conservé comme référence externe de la tentative, et non comme identifiant global du message.

Un accusé de réception SMPP peut arriver via deliver_sm ou data_sm. Lorsqu’il est présent, le TLV receipted_message_id transporte la référence du message original précédemment renvoyée par le MC. Des données d’accusé de réception peuvent également se trouver dans d’autres champs ou formats définis par l’intégration. Conservez le PDU, ou sa représentation brute, en plus de l’extraction normalisée.

En HTTP, une réponse de création ou d’acceptation peut renvoyer un identifiant de message ou de ressource. Son acceptation synchrone ne prouve pas la livraison sur le terminal. Les changements ultérieurs peuvent arriver par callback, consultation de la ressource ou rapport de livraison, avec leur propre référence d’événement et leurs propres horodatages.

  • internal_event_id : ID immuable de l’événement métier logique.
  • send_attempt_id : ID immuable de chaque tentative technique d’émission.
  • client_reference : référence facultative fournie par le client ou le système émetteur.
  • external_message_id : ID renvoyé par le fournisseur, le MC, le SMSC ou l’API.
  • dlr_reference : référence transportée dans le rapport de livraison, telle que receipted_message_id lorsqu’il y a lieu.
  • callback_event_id : ID de la notification reçue par webhook ou infrastructure d’événements.
  • provider_account_scope : compte, tenant, intégration, environnement et fournisseur qui délimitent la signification d’une référence.
Quels identifiants peuvent exister en HTTP, SMPP et DLR

Principe de conception : ID interne immuable et références externes versionnées

La base de la conception est simple : générez des identifiants internes contrôlés par votre organisation et ne les réutilisez pas. Traitez ensuite toute référence externe comme une preuve attribuée à une source et à un moment d’observation.

La séparation entre l’événement logique et la tentative technique est essentielle. Un événement métier peut entraîner une tentative initiale, une nouvelle tentative contrôlée ou un basculement vers une autre route. Chaque tentative doit disposer de son propre send_attempt_id, même si toutes dépendent du même internal_event_id. Cela évite d’interpréter une réémission comme une mise à jour de l’envoi précédent.

Les références externes ne doivent pas non plus être écrasées. Une même tentative peut recevoir une référence d’acceptation, une autre référence dans un DLR et une référence supplémentaire lors d’une consultation ultérieure. Enregistrez chacune comme une ligne ou un événement distinct, avec son type, sa valeur d’origine, sa valeur de comparaison et son contexte opérationnel.

L’interprétation opérationnelle peut évoluer. Par exemple, un état dérivé peut passer d’en attente à livré ou non livré lorsqu’une nouvelle preuve arrive. Cependant, l’événement reçu et les preuves qui ont motivé cette interprétation doivent rester intacts.

  • Utilisez UUID ou un autre schéma interne stable pour internal_event_id et send_attempt_id.
  • N’utilisez pas un message_id de fournisseur comme clé primaire du domaine métier.
  • Conservez une relation un-à-plusieurs entre tentative technique et références externes.
  • Conservez une relation un-à-plusieurs entre tentative technique et observations d’état.
  • Conservez la version de l’intégration qui a traité chaque réponse ou callback.
  • Distinguez l’état observé de l’état dérivé utilisé par vos opérations.

Normalisation pratique sans détruire la valeur reçue

Normaliser ne signifie pas remplacer la valeur d’origine. La règle sûre consiste à toujours stocker la représentation exacte reçue et à créer séparément une représentation de comparaison. Cette seconde représentation sert uniquement aux recherches et aux règles de rapprochement documentées.

La comparaison peut exiger de vérifier l’encodage, la longueur, les espaces, la sensibilité à la casse, les préfixes ou la troncature. N’appliquez pas de transformations universelles : une modification de casse peut être anodine pour une intégration et destructive pour une autre ; tronquer une chaîne peut créer une collision ; convertir des octets en texte sans connaître l’encodage peut altérer l’identifiant.

Chaque normalisation doit être reproductible. Enregistrez le nom de la règle, sa version et son résultat. Si l’intégration change le format d’une référence, vous pourrez réexaminer les valeurs d’origine sans perdre les preuves.

  • external_value_raw : valeur exacte reçue, conservée sans transformation.
  • external_value_compare : valeur dérivée pour comparaison selon une règle explicite.
  • normalization_rule_version : version de la règle appliquée.
  • external_id_type : par exemple, submit_sm_resp_message_id, receipted_message_id ou http_message_id.
  • observed_at : moment auquel le système a reçu ou observé la valeur.
  • source_payload_id : lien vers le payload, le PDU ou l’événement brut stocké de manière contrôlée.
  • Ne supprimez pas les espaces, les zéros, les préfixes ou les caractères non alphanumériques sans règle spécifique au fournisseur.

Modèle de données minimal pour enquêter sans écraser les preuves

Un modèle relationnel minimal peut résoudre la plupart des investigations s’il préserve la séparation entre intention, exécution, références et observations. Il n’est pas nécessaire d’imposer que tous les fournisseurs renvoient les mêmes champs ; il faut enregistrer explicitement ce qui a été reçu et dans quel périmètre.

La table des événements métier représente la demande fonctionnelle autorisée. La table des tentatives représente chaque envoi technique. Les références externes et les événements d’état sont liés à la tentative, et non directement à l’événement logique, sauf si le contrat du fournisseur permet de démontrer cette relation.

Pour limiter l’exposition, le destinataire doit être traité comme une donnée sensible. Enregistrez-le sous une forme internationale cohérente lorsque cela est nécessaire pour l’investigation et les clés composées, avec des contrôles d’accès, une conservation proportionnée et, lorsque cela est pertinent, une tokenisation ou une protection équivalente. Il n’est pas nécessaire de stocker l’intégralité du contenu du message pour résoudre tous les incidents ; un hash sécurisé du payload ou d’une représentation canonique peut aider à distinguer les tentatives sans accroître inutilement l’exposition des données.

  • business_event : internal_event_id, tenant_id, type d’événement, idempotency_key, created_at.
  • send_attempt : send_attempt_id, internal_event_id, provider_id, provider_account_id, route_id, environment, integration_version, submitted_at.
  • external_reference : reference_id, send_attempt_id, external_id_type, raw_value, compare_value, normalization_rule_version, observed_at.
  • status_observation : observation_id, send_attempt_id, callback_event_id, raw_status, normalized_status, provider_timestamp, received_at, payload_reference.
  • investigation_context : destinataire protégé ou tokenisé, hash sécurisé du payload, origine de l’envoi et données d’audit nécessaires.

Lorsque le fournisseur réutilise, transforme ou ne renvoie pas de référence corrélable

Tous les fournisseurs ne préservent pas une référence envoyée par le client, ne renvoient pas un ID stable ou n’incluent pas le même ID dans les DLR. Le modèle doit prendre en charge cette limite sans inventer une relation qui ne peut pas être démontrée.

Si un fournisseur réutilise des identifiants, la référence ne peut être unique qu’au sein d’une clé composée. Au minimum, incluez le tenant, le fournisseur, le compte fournisseur, l’environnement, le type de référence et un intervalle temporel d’observation. Ajoutez la route et l’intégration lorsqu’elles peuvent modifier la signification opérationnelle de la valeur.

Si le fournisseur transforme l’identifiant, enregistrez les deux valeurs et la règle de transformation connue. En l’absence de règle contractuelle ou techniquement vérifiable, n’établissez pas d’association automatique sur la base d’une similarité partielle. Marquez le cas comme ambigu et transmettez-le à un rapprochement ou à une investigation.

Lorsqu’il n’existe pas de référence corrélable, la traçabilité peut se poursuivre jusqu’à la tentative technique et à la preuve d’acceptation, mais le lien avec un DLR précis restera incertain. Cette limite doit être visible dans le tableau de bord et dans les procédures opérationnelles.

  • Ne dédupliquez jamais globalement à partir d’un external_message_id isolé.
  • N’utilisez pas des correspondances par préfixe, suffixe ou troncature comme preuve d’identité.
  • Exigez une clé composée incluant le périmètre opérationnel pour chaque règle de recherche.
  • Classez les liens comme confirmés, probables ou non corrélables ; réservez les automatisations irréversibles aux liens confirmés.
  • Documentez les références renvoyées par chaque fournisseur et celles qui peuvent apparaître dans ses DLR.

submit_sm_resp, DLR et états asynchrones : ce qui peut être lié

Dans un flux SMPP classique, l’ESME envoie submit_sm et reçoit submit_sm_resp. Le message_id de la réponse identifie le message dans le MC ou SMSC ayant répondu. Si un accusé de réception a été demandé via registered_delivery et que le système émet un DLR, celui-ci peut indiquer qu’il s’agit d’un MC Delivery Receipt au moyen de esm_class et transporter l’identifiant du message reçu dans le TLV receipted_message_id.

Cette relation permet une corrélation forte lorsque receipted_message_id correspond au message_id enregistré précédemment, dans le même fournisseur, compte, environnement et intégration. Conservez néanmoins le DLR complet : l’état, les horodatages et les champs disponibles font partie des preuves et peuvent être nécessaires en présence de doublons ou d’événements hors séquence.

En HTTP, l’identifiant renvoyé lors de la création d’une ressource peut servir à consulter son état ultérieurement ou à relier des callbacks, selon le contrat du fournisseur. Un code HTTP de création ou d’acceptation indique que la plateforme a traité ou mis en file la demande selon sa propre sémantique ; il ne prouve pas à lui seul la livraison au terminal.

Les callbacks peuvent arriver tardivement, en double ou dans le désordre. Ne rejetez pas automatiquement une observation simplement parce qu’elle est ancienne par rapport à son heure de réception. Comparez l’horodatage du fournisseur, l’horodatage de réception et la séquence connue ; appliquez ensuite des règles de clôture et de rapprochement auditables.

  • Conservez submit_sm, submit_sm_resp et DLR comme des étapes distinctes.
  • Demandez des DLR avec registered_delivery lorsque le contrat SMPP et le cas d’usage l’exigent.
  • Ne transformez pas un DLR en preuve de lecture humaine ou de qualité générale de route.
  • Ne supposez pas qu’un état terminal empêche l’arrivée ultérieure de preuves contradictoires ou dupliquées.
  • Maintenez une politique documentée pour déterminer quel état dérivé afficher, sans supprimer les états précédents.

Changements de route, renvois et doublons : modéliser par contexte

Un basculement, une nouvelle tentative ou une réémission peuvent correspondre au même événement métier, mais ne sont pas nécessairement le même message technique. La règle pratique consiste à créer un nouveau send_attempt_id pour chaque émission vers une combinaison précise de tenant, fournisseur, compte, route, environnement et intégration.

Ne consolidez pas les DLR de routes différentes comme des mises à jour de la même tentative. Un état issu d’une route antérieure ne doit pas être attribué à une nouvelle route simplement parce que le destinataire, le contenu ou une référence externe semblent similaires. La relation correcte est maintenue par l’intermédiaire de internal_event_id, tandis que les preuves de chaque fournisseur restent associées à leur propre tentative.

L’idempotence doit être appliquée avant l’envoi. Pour les requêtes HTTP, POST n’est pas idempotent par définition ; une nouvelle tentative après une réponse incertaine peut dupliquer un envoi s’il n’existe pas de clé d’idempotence applicative ou de confirmation fiable que l’opération précédente n’a pas été appliquée. Ne dépendez pas d’un ID externe qui n’a peut-être pas encore été renvoyé.

Un renvoi délibéré doit également être visible comme tel. Enregistrez sa cause : délai d’acceptation dépassé, défaillance technique, politique de basculement, décision manuelle ou autre motif autorisé. Cela permet de distinguer une duplication accidentelle d’une seconde exécution contrôlée.

  • Clé de contexte recommandée : tenant_id, provider_id, provider_account_id, route_id, environment, external_id_type et external_value_compare.
  • N’ajoutez des fenêtres temporelles qu’en tant que contrainte supplémentaire, et non comme preuve unique d’identité.
  • Utilisez idempotency_key par événement ou intention métier avant d’appeler le fournisseur.
  • Enregistrez retry_sequence, failover_reason et la relation entre tentative source et tentative suivante.
  • Évitez d’envoyer du contenu sensible inutile vers les journaux, les outils de recherche ou les URL.
FAQ

Questions fréquentes

Le message_id de submit_sm_resp peut-il être utilisé comme ID global du message ?

Non. Il s’agit d’une référence attribuée par le MC ou SMSC ayant répondu, qui doit être interprétée dans son périmètre opérationnel. Conservez-la avec le fournisseur, le compte, l’environnement, l’intégration, le type de référence et le moment d’observation.

Un HTTP 202 ou une réponse d’API réussie confirme-t-il la livraison du SMS ?

Pas nécessairement. Une acceptation ou une création confirme le traitement de la demande selon l’API, mais la livraison exige d’observer un rapport d’état ultérieur ou de consulter la ressource lorsque le fournisseur le permet.

Que faire si le DLR arrive avant, après ou en double par rapport à d’autres événements ?

Conservez chaque observation sans l’écraser. Enregistrez l’heure du fournisseur et l’heure de réception, appliquez une règle d’interprétation versionnée et gardez l’événement brut pour le rapprochement.

Dois-je conserver le contenu du SMS pour corréler les messages ?

Ce n’est pas indispensable dans tous les cas. Privilégiez la minimisation des données. Si vous devez distinguer les tentatives, envisagez un hash sécurisé d’une représentation contrôlée du payload et protégez les données de destination ainsi que les métadonnées associées.

Un état delivered prouve-t-il la réception ou la lecture par une personne ?

Non. Il représente la confirmation de livraison que le fournisseur reçoit de sa chaîne amont et, lorsqu’elle est disponible, du terminal. Ce n’est pas une preuve universelle de lecture humaine ni une garantie indépendante de la qualité de route.

Sources consultées

  1. SMPP v3.4 specificationSMPP Developers Forum
  2. SMPP Delivery Receipt FormatSMPP Developers Forum
  3. SMPP protocol overviewSMPP Developers Forum
  4. Message resourceTwilio
  5. Outbound Message Status in Status CallbacksTwilio
  6. Best Practices for Messaging Delivery Status LoggingTwilio
  7. Operations and Message TrackingTwilio
  8. Delivery Reports - Get - REST APIMicrosoft Learn
  9. Azure Communication Services SMS eventsMicrosoft Learn
  10. SMS logsMicrosoft Learn
  11. ITU-T Recommendation E.164International Telecommunication Union
  12. RFC 9110: HTTP SemanticsIETF / RFC Editor