Retour au blog Connectivité et opérations SMS

Comment concevoir des tentatives idempotentes dans une API SMS pour éviter les envois en double

Guide opérationnel pour gérer les timeouts et les résultats ambigus en SMS sans transformer une nouvelle tentative technique en second message. Inclut les clés d’idempotence, les états persistants, la réconciliation via callbacks ou DLR et des contrôles spécifiques aux OTP.

Diagramme de flux de tentatives idempotentes dans une API SMS

Pourquoi un timeout ne prouve pas que le SMS n’a pas été accepté

Un timeout indique que le client n’a pas reçu de réponse dans le délai configuré. Il ne prouve pas, à lui seul, que le serveur n’a pas reçu la requête, ne l’a pas traitée ou n’a pas accepté le message pour un envoi ultérieur.

Dans une opération HTTP, tous les octets de la requête peuvent avoir été transmis et le message distant peut avoir été créé avant que la réponse ne soit perdue. Il peut également arriver que la connexion échoue avant que le fournisseur ne reçoive la requête. Du point de vue de l’application cliente, ces deux scénarios peuvent sembler identiques : aucune réponse exploitable n’est disponible.

C’est pourquoi un timeout survenant après le début de la transmission doit être classé comme un résultat ambigu. Répéter immédiatement une création de message avec une nouvelle identité peut produire un second SMS, même si le premier existe déjà ou a été accepté.

  • Ne traitez pas l’absence de réponse comme un rejet définitif.
  • Distinguez, lorsque votre bibliothèque et votre télémétrie le permettent, un échec avant l’envoi de la requête, un échec pendant la transmission et un échec après son envoi.
  • Concevez le flux afin qu’une même intention métier puisse être consultée, réconciliée ou répétée sans créer une nouvelle opération.
Pourquoi un timeout ne prouve pas que le SMS n’a pas été accepté

Le risque opérationnel des doublons

Un doublon n’est pas seulement un coût supplémentaire. Dans un flux OTP, il peut désorienter l’utilisateur s’il reçoit des codes différents, en particulier si le système invalide le code précédent lors de la génération d’un nouveau code. Pour les alertes opérationnelles, deux notifications peuvent déclencher des actions répétées. Dans les notifications transactionnelles, le destinataire peut interpréter le second message comme une erreur ou une tentative de fraude.

La prévention ne doit pas être confondue avec une suppression indiscriminée. Deux actions métier réellement distinctes peuvent nécessiter deux SMS vers le même numéro avec un contenu similaire. L’objectif est de dédupliquer la répétition technique d’une même intention, et non d’empêcher des communications légitimes, consenties et nécessaires.

  • Une nouvelle tentative technique doit conserver l’identité de l’intention d’origine.
  • Un nouveau challenge OTP, une nouvelle opération ou une nouvelle version délibérée du message doit avoir une nouvelle identité.
  • La déduplication doit s’appliquer à une fenêtre temporelle et à une sémantique documentées, et pas uniquement au numéro de destination.
Le risque opérationnel des doublons

Définissez un modèle d’états séparant acceptation et livraison

Un modèle minimal doit distinguer ce que votre application sait de ce que confirme le fournisseur et, plus tard, de ce qu’indique le cycle de livraison. L’acceptation par un fournisseur ou par un opérateur en amont n’équivaut pas nécessairement à une livraison finale au destinataire.

Une séquence interne pratique peut inclure : créé, tentative en cours, envoyé au fournisseur, acceptation confirmée, résultat en attente et résultat final. Le nom exact importe moins que le maintien de transitions persistantes, auditables et non ambiguës.

Le résultat final peut représenter une livraison signalée, une non-livraison, un échec définitif ou l’expiration d’une fenêtre de réconciliation. Si vous recevez des DLR ou des callbacks, enregistrez l’événement d’origine et son heure de réception, en plus de l’état consolidé.

  • Créé : l’intention métier est déjà persistée, mais l’envoi n’a pas commencé.
  • Tentative en cours : une tentative identifiable a été réservée avant l’ouverture de la connexion.
  • Acceptation confirmée : le fournisseur a répondu et a renvoyé une confirmation ou un identifiant conforme à son contrat.
  • Résultat en attente : une acceptation ou un résultat ambigu existe, et une consultation, un callback, un DLR ou une autre réconciliation est attendue.
  • Résultat final : le flux a atteint un état terminal selon les règles documentées pour cette intégration.

Utilisez une clé d’idempotence pour une intention précise

Une clé d’idempotence permet à une API de distinguer la répétition d’une requête précédente d’une nouvelle opération. Pour fonctionner, le client doit réutiliser la même clé lorsqu’il retente exactement la même intention métier.

La clé ne doit pas représenter uniquement le numéro de téléphone. Un même destinataire peut recevoir plusieurs communications légitimes. Elle doit être liée à un événement stable : par exemple, l’identifiant interne d’une notification, un challenge OTP précis, le destinataire, le canal SMS et la version du contenu à envoyer.

Si le fournisseur accepte un jeton d’idempotence, respectez son contrat : format, emplacement dans la requête, traitement des paramètres divergents, portée de la déduplication et durée de conservation. S’il ne le prend pas en charge, l’idempotence doit être contrôlée principalement dans votre propre système, et les nouvelles tentatives après un résultat ambigu exigent encore davantage de prudence.

  • Générez la clé une seule fois par intention, et non une fois par tentative réseau.
  • Persistez la clé avant l’appel distant.
  • Conservez une empreinte ou une version des paramètres pertinents afin de détecter si quelqu’un tente de réutiliser la clé pour une opération différente.
  • Ne réutilisez pas la clé pour un nouveau challenge OTP ni pour une communication métier indépendante.
  • Ne supposez pas que tous les fournisseurs dédupliquent sur la même durée ou avec la même sémantique.

Persistez l’intention avant d’appeler HTTP ou SMPP

La persistance doit avoir lieu avant l’opération réseau. Si vous appelez d’abord l’API et n’enregistrez qu’ensuite une trace, une interruption entre ces deux actions peut laisser un message distant sans intention locale permettant de le suivre ou de le réconcilier.

Dans une transaction locale, créez l’enregistrement d’envoi, attribuez l’identifiant interne, la clé d’idempotence, les paramètres nécessaires pour reproduire la requête et l’état créé. Ensuite, un processus d’envoi peut prendre cet enregistrement, marquer la tentative comme en cours et appeler le fournisseur.

Lorsque vous recevez une réponse valide, persistez l’identifiant distant et la signification de la réponse. En SMPP, une réponse réussie à submit_sm renvoie un message_id attribué par le SMSC ; cet identifiant doit être conservé pour corréler les opérations et les accusés ultérieurs.

  • Identifiant interne de l’intention.
  • Clé d’idempotence et empreinte de la requête.
  • Destination et contenu, ou référence sécurisée à sa version autorisée.
  • Horodatage de création, de début et de fin de chaque tentative.
  • Identifiant renvoyé par le fournisseur ou le SMSC, lorsqu’il existe.
  • État actuel, historique des transitions et motif de tout état terminal.

Classez les résultats avant de décider d’une nouvelle tentative

Une politique de nouvelles tentatives sûre ne consiste pas à « renvoyer pour chaque erreur ». Elle doit séparer les réponses définitives, les échecs récupérables et les résultats ambigus. Les catégories précises doivent découler du contrat de chaque fournisseur et du protocole utilisé.

Les réponses définitives exigent généralement de terminer le processus ou de rendre le contrôle au flux métier : par exemple, identifiants invalides, paramètres invalides, format non pris en charge ou adresse de destination déclarée invalide. Retenter sans modifier la cause n’améliore pas la fiabilité et peut augmenter inutilement le trafic.

Les échecs récupérables peuvent inclure une indisponibilité temporaire, une limitation de débit ou des erreurs réseau temporaires explicitement documentées. En SMPP, ESME_RTHROTTLED indique que les limites de messages autorisées ont été dépassées ; la réaction appropriée consiste à réduire la pression et à appliquer une attente contrôlée, et non à renvoyer en boucle.

Les résultats ambigus comprennent les timeouts, les déconnexions et les réponses perdues après le début de l’envoi. Ils ne doivent pas être traités automatiquement comme des échecs récupérables, car le fournisseur peut avoir accepté le message.

  • Définitif : terminez, enregistrez la cause et corrigez la requête ou le flux avant de créer une nouvelle intention.
  • Récupérable : planifiez une nouvelle tentative limitée avec la même clé d’idempotence.
  • Ambigu : consultez si possible, attendez un signal de réconciliation et réutilisez la même identité uniquement si le fournisseur propose une déduplication compatible.
  • Limitation de débit : appliquez un contrôle de débit et une attente progressive ; ne concentrez pas les tentatives au même instant.

Appliquez des limites, une attente progressive et une fenêtre de validité

Une nouvelle tentative doit être limitée par le nombre de tentatives, le temps total et la durée de validité de l’intention métier. L’attente progressive évite de concentrer les requêtes après un incident ou une limitation de débit. Vous pouvez ajouter une variation aléatoire contrôlée afin d’éviter que de nombreux workers ne retentent de manière synchronisée.

Il n’existe pas de nombre universel de tentatives ni de délai valable pour toutes les routes et tous les cas d’usage. Définissez-les selon la criticité du message, le comportement documenté du fournisseur, les limites de débit contractuelles ou techniques et la durée de vie utile du contenu.

La politique doit s’arrêter lorsqu’un état final est atteint, lorsque la fenêtre métier expire ou lorsque le budget de tentatives est dépassé. Une nouvelle tentative après la période d’utilité du message peut être pire qu’un échec : une alerte tardive ou un OTP expiré ne répond pas au besoin initial.

  • Définissez un nombre maximal de tentatives et une limite de temps totale par intention.
  • Augmentez l’intervalle entre les nouvelles tentatives en cas d’échecs temporaires ou de limitation de débit.
  • Maintenez une file d’attente différée au lieu de bloquer le flux principal avec des attentes actives.
  • Enregistrez chaque décision : pourquoi une nouvelle tentative a été effectuée, combien de temps a été attendu et quelle règle a arrêté le processus.
  • Ne transformez pas une récupération technique en envoi indéfini.

Traitez les OTP comme un cas de sécurité et d’expérience utilisateur

Pour un OTP, l’intention n’est pas « envoyer un texte à un numéro », mais remettre un code associé à un challenge précis et à durée de validité limitée. La clé d’idempotence doit correspondre à ce challenge, et non à une requête HTTP individuelle.

Le système doit définir clairement si une nouvelle tentative de vérification réutilise le même challenge ou en crée un autre. S’il crée un nouveau code tout en laissant le précédent actif, l’utilisateur peut recevoir plusieurs codes valides. S’il invalide le précédent, un SMS retardé peut contenir un code qui ne fonctionne déjà plus. Ces deux décisions relèvent du produit et de la sécurité, mais doivent rester cohérentes avec la politique d’envoi.

La fenêtre de tentatives doit se terminer avant l’expiration du challenge. Elle doit également être coordonnée avec toute période de validité de file d’attente configurée chez le fournisseur. Il n’est pas approprié de continuer à tenter de livrer un OTP lorsqu’il ne peut plus être vérifié.

  • Associez chaque OTP à un identifiant de challenge persistant.
  • Retentez le même envoi avec la même clé lorsqu’il s’agit de la même intention.
  • Documentez à quel moment un nouveau code est généré et ce qu’il advient des précédents.
  • Arrêtez les nouvelles tentatives et les envois en attente lorsque le challenge expire.
  • Mesurez les doublons, les retards et les abandons sans stocker davantage de données personnelles que nécessaire.
FAQ

Questions fréquentes

Un timeout dans une API SMS signifie-t-il que le message n’a pas été envoyé ?

Non. Il peut signifier que la connexion a échoué avant que le fournisseur ne reçoive la requête, mais aussi que le fournisseur l’a reçue et traitée alors que la réponse a été perdue ou retardée. Traitez-le comme un résultat ambigu tant que vous ne pouvez pas le réconcilier.

Que doit contenir une clé d’idempotence pour les SMS ?

Elle doit identifier une intention métier précise. Elle peut être liée à un identifiant interne d’événement ou de challenge OTP, au destinataire, au canal et à la version du contenu. Elle ne doit pas se limiter au numéro de destination ni être régénérée à chaque nouvelle tentative.

Dois-je retenter après une erreur de limitation de débit ?

Cela peut justifier une nouvelle tentative contrôlée si le contrat du fournisseur la classe comme temporaire. Appliquez un contrôle de débit, une attente progressive et des limites. Ne renvoyez pas immédiatement ni sans limite.

Un DLR confirme-t-il toujours la réception sur le téléphone ?

Pas nécessairement. La signification de chaque état dépend du contrat et des informations disponibles dans la chaîne de livraison. Vous devez distinguer l’acceptation par le fournisseur, l’envoi vers un opérateur, la réception d’un DLR et toute confirmation de livraison signalée. Un DLR ne doit pas être interprété au-delà de sa sémantique documentée.

Les callbacks remplacent-ils la réponse de création du message ?

Non. La réponse synchrone et les callbacks remplissent des fonctions différentes. Certains fournisseurs n’émettent pas de callback pour l’état initial ; vous devez donc persister la réponse de création et réconcilier les changements ultérieurs via des callbacks, des consultations ou des DLR.

Que faire si le fournisseur ne prend pas en charge les clés d’idempotence ?

Contrôlez l’intention et les états dans votre propre système avant d’appeler le fournisseur. En cas de timeout ambigu, privilégiez la consultation à l’aide des identifiants disponibles et la réconciliation des événements. S’il n’existe aucun mécanisme distant de déduplication ou de consultation, documentez cette limite et soyez particulièrement restrictif avant de créer une seconde requête.

Sources consultées

  1. RFC 9110: HTTP SemanticsIETF / RFC Editor
  2. SMPP Protocol Specification v3.4, Issue 1.2SMPP Developers Forum
  3. Messages resourceTwilio
  4. Outbound Message Status in Status CallbacksTwilio
  5. Messaging ServicesTwilio
  6. Cloud Control API ReferenceAmazon Web Services
  7. AWS Well-Architected Framework: Reliability PillarAmazon Web Services