Torna al blog Connettività e operazioni SMS

Come progettare retry idempotenti in un’API SMS per evitare invii duplicati

Guida operativa per gestire timeout e risultati ambigui negli SMS senza trasformare un retry tecnico in un secondo messaggio. Include chiavi di idempotenza, stati persistenti, riconciliazione con callback o DLR e controlli specifici per gli OTP.

Diagramma di flusso per retry idempotenti in un’API SMS

Perché un timeout non dimostra che l’SMS non sia stato accettato

Un timeout indica che il client non ha ricevuto una risposta entro il tempo configurato. Da solo, non dimostra che il server non abbia ricevuto la richiesta, non l’abbia elaborata o non abbia accettato il messaggio per il successivo invio.

In un’operazione HTTP, tutti i byte della richiesta potrebbero essere stati trasmessi e il messaggio remoto potrebbe essere stato creato prima che la risposta andasse persa. Può anche accadere che la connessione fallisca prima che il provider riceva la richiesta. Dal punto di vista dell’applicazione client, entrambi gli scenari possono apparire identici: non è disponibile alcuna risposta utilizzabile.

Per questo motivo, un timeout successivo all’avvio della trasmissione deve essere classificato come risultato ambiguo. Ripetere immediatamente una creazione del messaggio con una nuova identità può produrre un secondo SMS, anche se il primo esiste già o è stato accettato.

  • Non trattare l’assenza di risposta come un rifiuto definitivo.
  • Distinguere, quando la libreria e la telemetria lo consentono, tra fallimento prima dell’invio della richiesta, fallimento durante la trasmissione e fallimento dopo l’invio.
  • Progettare il flusso affinché una stessa intenzione di business possa essere consultata, riconciliata o ripetuta senza creare una nuova operazione.
Perché un timeout non dimostra che l’SMS non sia stato accettato

Il rischio operativo dei duplicati

Un duplicato non è solo un costo aggiuntivo. In un flusso OTP può confondere l’utente se riceve codici diversi, soprattutto se il sistema invalida il codice precedente quando ne genera uno nuovo. Negli avvisi operativi, due notifiche possono innescare azioni ripetute. Nelle notifiche transazionali, il destinatario può interpretare il secondo messaggio come un errore o un tentativo di frode.

La prevenzione non deve essere confusa con una soppressione indiscriminata. Due azioni di business realmente distinte possono richiedere due SMS allo stesso numero e con contenuti simili. L’obiettivo è deduplicare la ripetizione tecnica della stessa intenzione, non impedire comunicazioni legittime, consensuali e necessarie.

  • Un retry tecnico deve conservare l’identità dell’intenzione originale.
  • Una nuova verifica OTP, una nuova operazione o una nuova versione deliberata del messaggio deve avere una nuova identità.
  • La deduplicazione deve essere applicata su una finestra temporale e una semantica documentate, non solo sul numero di destinazione.
Il rischio operativo dei duplicati

Definire un modello di stati che separi accettazione e consegna

Un modello minimo deve distinguere ciò che la vostra applicazione sa da ciò che il provider conferma e, successivamente, da ciò che viene comunicato dal ciclo di consegna. L’accettazione da parte di un provider o di un carrier a monte non equivale necessariamente alla consegna finale al destinatario.

Una sequenza interna pratica può includere: creato, tentativo in corso, inviato al provider, accettazione confermata, risultato in attesa e risultato finale. Il nome esatto è meno importante del mantenimento di transizioni persistenti, verificabili e prive di ambiguità.

Il risultato finale può rappresentare una consegna segnalata, una mancata consegna, un errore definitivo o l’esaurimento di una finestra di riconciliazione. Se ricevete DLR o callback, registrate l’evento originale e il momento della sua ricezione, oltre allo stato consolidato.

  • Creato: l’intenzione di business è già persistita, ma l’invio non è stato avviato.
  • Tentativo in corso: è stato riservato un tentativo identificabile prima di aprire la connessione.
  • Accettazione confermata: il provider ha risposto e ha restituito una conferma o un identificatore conforme al suo contratto.
  • Risultato in attesa: esiste un’accettazione o un risultato ambiguo e si attende una query, un callback, un DLR o un’altra riconciliazione.
  • Risultato finale: il flusso ha raggiunto uno stato terminale secondo le regole documentate per tale integrazione.

Usare una chiave di idempotenza per un’intenzione specifica

Una chiave di idempotenza consente a un’API di distinguere la ripetizione di una richiesta precedente da una nuova operazione. Per funzionare, il client deve riutilizzare la stessa chiave quando riprova esattamente la stessa intenzione di business.

La chiave non dovrebbe rappresentare solo il numero di telefono. Uno stesso destinatario può ricevere più comunicazioni legittime. Deve essere associata a un evento stabile: per esempio, l’identificatore interno di una notifica, una specifica verifica OTP, il destinatario, il canale SMS e la versione del contenuto da inviare.

Se il provider supporta un token di idempotenza, rispettatene il contratto: formato, posizione nella richiesta, gestione di parametri non corrispondenti, ambito della deduplicazione e periodo di conservazione. Se non lo supporta, l’idempotenza deve essere controllata principalmente nel vostro sistema e i retry dopo un risultato ambiguo richiedono ancora maggiore prudenza.

  • Generare la chiave una sola volta per intenzione, non una volta per ogni tentativo di rete.
  • Persistire la chiave prima della chiamata remota.
  • Conservare un’impronta o una versione dei parametri rilevanti per rilevare il riutilizzo della chiave con un’operazione diversa.
  • Non riutilizzare la chiave per una nuova verifica OTP né per una comunicazione di business indipendente.
  • Non presumere che tutti i provider deduplicano con la stessa durata o semantica.

Persistire l’intenzione prima di chiamare HTTP o SMPP

La persistenza deve avvenire prima dell’operazione di rete. Se chiamate prima l’API e salvate un record solo in seguito, un’interruzione tra le due azioni può lasciare un messaggio remoto senza un’intenzione locale che permetta di tracciarlo o riconciliarlo.

In una transazione locale, create il record di invio, assegnate l’identificatore interno, la chiave di idempotenza, i parametri necessari per riprodurre la richiesta e lo stato creato. Successivamente, un processo di invio può prelevare quel record, contrassegnare il tentativo come in corso ed effettuare la chiamata al provider.

Quando ricevete una risposta valida, persistete l’identificatore remoto e il significato della risposta. In SMPP, una risposta positiva a submit_sm restituisce un message_id assegnato dall’SMSC; questo identificatore deve essere conservato per correlare le operazioni e le ricevute successive.

  • Identificatore interno dell’intenzione.
  • Chiave di idempotenza e impronta della richiesta.
  • Destinazione e contenuto, oppure riferimento sicuro alla relativa versione autorizzata.
  • Timestamp di creazione, avvio e fine di ogni tentativo.
  • Identificatore restituito dal provider o dall’SMSC, quando disponibile.
  • Stato corrente, cronologia delle transizioni e motivo di ogni stato terminale.

Classificare i risultati prima di decidere se riprovare

Una politica di retry sicura non si basa sul principio “qualsiasi errore viene inviato di nuovo”. Deve separare risposte definitive, errori recuperabili e risultati ambigui. Le categorie specifiche devono derivare dal contratto di ciascun provider e dal protocollo utilizzato.

Le risposte definitive richiedono normalmente di terminare o restituire il controllo al flusso di business: per esempio, credenziali non valide, parametri non validi, formato non supportato o un indirizzo di destinazione dichiarato non valido. Riprovare senza modificare la causa non aggiunge affidabilità e può aumentare traffico non necessario.

Gli errori recuperabili possono includere indisponibilità temporanea, limitazione del throughput o errori di rete temporanei esplicitamente documentati. In SMPP, ESME_RTHROTTLED indica che sono stati superati i limiti di messaggi consentiti; la reazione appropriata consiste nel ridurre la pressione e applicare un’attesa controllata, non nel reinviare in un ciclo.

I risultati ambigui includono timeout, disconnessioni e risposte perse dopo l’avvio dell’invio. Non devono essere trattati automaticamente come errori recuperabili, perché il provider potrebbe avere accettato il messaggio.

  • Definitivo: terminare, registrare la causa e correggere la richiesta o il flusso prima di creare una nuova intenzione.
  • Recuperabile: pianificare un retry limitato con la stessa chiave di idempotenza.
  • Ambiguo: eseguire una query se possibile, attendere un segnale di riconciliazione e riutilizzare la stessa identità solo se il provider offre una deduplicazione compatibile.
  • Throttling: applicare controllo del tasso e attesa progressiva; non concentrare i retry nello stesso istante.

Applicare limiti, attesa progressiva e una finestra di validità

Un retry deve essere limitato per numero di tentativi, tempo totale e validità dell’intenzione di business. L’attesa progressiva evita di concentrare richieste dopo un incidente o una limitazione di throughput. Potete aggiungere una variazione casuale controllata per evitare che molti worker ritentino in modo sincronizzato.

Non esiste un numero universale di tentativi né un’attesa valida per tutte le rotte e i casi d’uso. Definiteli in base alla criticità del messaggio, al comportamento documentato del provider, ai limiti di throughput contrattuali o tecnici e alla durata utile del contenuto.

La politica deve interrompersi al raggiungimento di uno stato finale, alla scadenza della finestra di business o quando viene superato il budget di retry. Un retry successivo all’utilità del messaggio può essere peggiore di un errore: un avviso tardivo o un OTP scaduto non risolvono l’esigenza originale.

  • Stabilire un numero massimo di tentativi e un limite di tempo totale per intenzione.
  • Aumentare l’intervallo tra i retry in presenza di errori temporanei o throttling.
  • Mantenere una coda differita invece di bloccare il flusso principale con attese attive.
  • Registrare ogni decisione: perché è stato effettuato il retry, quanto si è atteso e quale regola ha interrotto il processo.
  • Non trasformare un ripristino tecnico in un invio indefinito.

Trattare gli OTP come un caso di sicurezza e di esperienza utente

Negli OTP, l’intenzione non è “inviare un testo a un numero”, ma consegnare un codice associato a una verifica specifica e con validità limitata. La chiave di idempotenza deve corrispondere a quella verifica, non a una singola richiesta HTTP.

Il sistema deve definire chiaramente se un nuovo tentativo di verifica riutilizza la stessa verifica o ne crea un’altra. Se crea un altro codice e lascia attivo il precedente, l’utente può ricevere più codici validi. Se invalida il precedente, un SMS in ritardo può contenere un codice che non funziona più. Entrambe le decisioni riguardano prodotto e sicurezza, ma devono essere coerenti con la politica di invio.

La finestra di retry deve terminare prima della scadenza della verifica. Deve inoltre essere coordinata con qualsiasi periodo di validità della coda configurato nel provider. Non è appropriato continuare a tentare la consegna di un OTP quando non può più essere verificato.

  • Associare ogni OTP a un identificatore di verifica persistente.
  • Ritentare lo stesso invio con la stessa chiave quando si tratta della stessa intenzione.
  • Documentare quando viene generato un nuovo codice e cosa accade ai codici precedenti.
  • Interrompere retry e invii in sospeso alla scadenza della verifica.
  • Misurare duplicati, ritardi e abbandoni senza memorizzare più dati personali del necessario.
FAQ

Domande frequenti

Un timeout in un’API SMS significa che il messaggio non è stato inviato?

No. Può significare che la connessione è fallita prima che il provider ricevesse la richiesta, ma anche che il provider l’ha ricevuta e elaborata mentre la risposta veniva persa o ritardata. Trattatelo come un risultato ambiguo fino a quando non potrete riconciliarlo.

Cosa deve contenere una chiave di idempotenza per SMS?

Deve identificare un’intenzione di business specifica. Può essere collegata a un identificatore interno di evento o di verifica OTP, al destinatario, al canale e alla versione del contenuto. Non deve essere soltanto il numero di destinazione né essere rigenerata a ogni retry.

Devo ritentare un errore di throttling?

Può essere candidato a un retry controllato se il contratto del provider lo classifica come temporaneo. Applicate controllo del tasso, attesa progressiva e limiti. Non reinviate immediatamente né in modo illimitato.

Un DLR conferma sempre la ricezione sul telefono?

Non necessariamente. Il significato di ogni stato dipende dal contratto e dalle informazioni disponibili nella catena di consegna. Dovete distinguere tra accettazione da parte del provider, invio verso un carrier, DLR ricevuto e qualsiasi conferma di consegna segnalata. Un DLR non deve essere interpretato oltre la sua semantica documentata.

I callback sostituiscono la risposta di creazione del messaggio?

No. La risposta sincrona e i callback svolgono funzioni diverse. Alcuni provider non emettono callback per lo stato iniziale, quindi dovete persistere la risposta di creazione e riconciliare le modifiche successive tramite callback, query o DLR.

Cosa fare se il provider non supporta chiavi di idempotenza?

Controllate l’intenzione e gli stati nel vostro sistema prima di chiamare il provider. In presenza di un timeout ambiguo, date priorità alla query tramite gli identificatori disponibili e alla riconciliazione degli eventi. Se non esiste un meccanismo remoto di deduplicazione o query, documentate questo limite e adottate criteri particolarmente restrittivi prima di creare una seconda richiesta.

Fonti consultate

  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