Callback DLR negli SMS A2P: progettazione idempotente per duplicati e disordine
Progetta la ricezione dei callback DLR come uno storico verificabile e una proiezione di stato idempotente, in grado di gestire ritentativi, duplicati, eventi tardivi e sequenze fuori ordine.

La domanda operativa: cosa accade se il callback arriva due volte, tardi o in un ordine diverso?
Un callback di delivery receipt (DLR) non deve essere trattato come un aggiornamento unico, ordinato e definitivo. In un'integrazione HTTP, una comunicazione può fallire prima che il mittente ottenga la risposta e la richiesta può essere ripetuta. RFC 9110 definisce l'idempotenza come la proprietà per cui più richieste identiche producono lo stesso effetto previsto di una sola.
In pratica, il destinatario deve poter accettare la stessa notifica più di una volta senza conteggiarla ripetutamente né modificare impropriamente lo stato del messaggio. Deve inoltre poter registrare una ricevuta che arriva dopo un altro evento osservato in precedenza, senza eliminare l'evidenza né presumere che l'ordine di ricezione corrisponda all'ordine reale di elaborazione nella catena di messaggistica.
La regola operativa è semplice: conserva ogni ricezione, deduci lo stato corrente con regole esplicite e tieni separate le due cose. Lo storico risponde a cosa è stato ricevuto e quando; la proiezione di stato risponde a quale sia il risultato operativo calcolato dal sistema sulla base delle evidenze disponibili.
- Non presumere la consegna perché un callback è stato ricevuto dal tuo endpoint.
- Non presumere che l'ultimo callback ricevuto rappresenti necessariamente l'ultimo fatto avvenuto nella rete.
- Non applicare un aggiornamento irreversibile basandoti solo su destinatario, contenuto o una corrispondenza approssimativa.
- Rispondi in modo sicuro alle ripetizioni: il ritentativo non deve generare effetti duplicati contabili, analitici od operativi.

Cosa rappresenta un DLR e cosa non rappresenta
In SMPP, un DLR viene richiesto in submit_sm tramite registered_delivery. La ricevuta può essere restituita all'ESME tramite deliver_sm o data_sm. La disponibilità delle ricevute dipende quindi dal fatto che siano state richieste e dalla configurazione o dal comportamento della piattaforma di messaggistica.
SMPP distingue un SMSC Delivery Receipt da una notifica intermedia tramite la codifica di esm_class. Le notifiche intermedie sono un tipo distinto e il loro supporto dipende dall'implementazione dell'SMSC; non devono essere interpretate automaticamente come risultati finali.
Un DLR comunica lo stato riportato dalla catena di messaggistica all'interno di quel flusso. Non equivale a una verifica indipendente del fatto che una persona abbia visto, letto o compreso il contenuto sul proprio terminale. Nella semantica SMPP, DELIVERED indica la consegna al destinatario; nello standard citato non esiste uno stato di lettura umana o di contenuto visualizzato.
- Distingui l'accettazione di submit_sm, che conferma la presentazione al sistema che risponde, da un DLR successivo.
- Memorizza se è stato richiesto un DLR e con quale modalità di registered_delivery, quando questo dato è disponibile.
- Etichetta il risultato come stato riportato dalla messaggistica, non come prova di lettura da parte del destinatario.
- Tratta le notifiche intermedie come eventi separati dagli stati finali.

Modello dati minimo per una tracciabilità utile
Il progetto deve preservare i dati necessari per ricostruire una decisione. Per ogni messaggio inviato, conserva un identificatore interno immutabile e l'identificatore esterno restituito dal sistema di messaggistica. In SMPP, receipted_message_id identifica il messaggio oggetto della ricevuta e corrisponde all'identificatore opaco message_id restituito alla conferma della presentazione originaria.
Memorizza il destinatario in una rappresentazione normalizzata e conserva anche il contesto di indirizzamento ricevuto o inviato. E.164 definisce il piano pubblico internazionale di numerazione; la sua normalizzazione aiuta a evitare variazioni di formato, ma non deve sostituire i valori originali necessari per il debug.
Il formato storico del DLR in short_message può includere identificatore, date di presentazione e completamento, stato ed errore. Tuttavia, le sue particolarità possono essere specifiche del gateway o dell'SMSC. Per questo, estrai campi normalizzati per le operazioni, ma conserva sempre il payload originale.
- Messaggio: ID interno, ID esterno o message_id, mittente, destinatario normalizzato, valori originali di indirizzamento, rotta o contesto di invio e data di presentazione.
- Evento ricevuto: ID evento del provider, se presente, data e ora di ricezione, data e ora riportate dal mittente quando presenti, tipo di evento, stato grezzo, codice di errore grezzo e payload originale.
- Proiezione: stato calcolato, motivazione della decisione, evento o eventi che la supportano e data di aggiornamento.
- Audit: versione del parser o delle regole applicate, risultato della correlazione ed eventuali eccezioni rilevate.
Correlazione: dai priorità a chiavi stabili e rifiuta le corrispondenze deboli
La correlazione tra il messaggio originario e il DLR deve basarsi anzitutto sull'identificatore esterno assegnato dall'SMSC. SMPP definisce receipted_message_id come l'identificatore del messaggio a cui si riferisce la ricevuta. Inoltre, query_sm usa il message_id assegnato dall'SMSC insieme all'indirizzo sorgente come meccanismo di corrispondenza.
Non usare destinatario, testo, finestra temporale o mittente come sostituti automatici dell'ID esterno. Questi attributi possono ripetersi tra messaggi legittimi e produrre un'attribuzione errata. Una correlazione sbagliata è più dannosa di un evento in attesa: può trasformare lo stato di un altro messaggio in una consegna o in un errore che non gli è mai appartenuto.
Quando la correlazione non è conclusiva, registra il callback senza perderlo e indirizzalo a una coda o registro delle eccezioni. Questa scelta consente di indagare modifiche di formato, ID troncati, variazioni di codifica o altre particolarità di un'integrazione senza contaminare la proiezione dei messaggi noti.
- Prima opzione: associa receipted_message_id al message_id memorizzato all'accettazione dell'invio.
- Conserva il valore esterno esattamente come ricevuto, oltre a qualunque forma normalizzata richiesta dall'integrazione.
- Usa mittente, destinatario, date e rotta come verifiche ausiliarie, non come chiave unica di assegnazione.
- Se vi sono più candidati o nessuno, contrassegna la correlazione come non conclusiva e non materializzare lo stato su un messaggio specifico.
Schema idempotente per ricevere ed elaborare callback
L'idempotenza non richiede di ignorare ogni ripetizione. Consente di conservare ogni richiesta ricevuta come evidenza e, allo stesso tempo, di impedire che la ripetizione alteri più volte il risultato operativo. RFC 9110 chiarisce che un server può registrare ogni singola richiesta anche se l'effetto previsto dell'operazione è idempotente.
Implementa due livelli. Il primo è un registro delle ricezioni, preferibilmente immutabile, che conserva payload, intestazioni rilevanti disponibili, momento della ricezione e risultato del parser. Il secondo è l'applicazione degli effetti: deduplicazione, correlazione e calcolo dello stato. Solo questo secondo livello deve essere protetto dall'applicazione ripetuta dello stesso evento logico.
Se il mittente fornisce un ID evento stabile, usalo come chiave di deduplicazione nell'ambito corretto dell'integrazione. Se non esiste, crea un'impronta a partire da attributi stabili presenti nel callback, conserva i componenti utilizzati e mantieni il payload originale. Non basare l'impronta su campi che possono cambiare per trasformazione locale o su attributi ambigui senza documentarne il rischio.
- 1. Ricevi il callback e persisti la ricezione prima di eseguire effetti di business.
- 2. Valida ed estrai i campi disponibili senza scartare il payload originale.
- 3. Determina se esiste un ID evento stabile; altrimenti, calcola un'impronta documentata per l'evento logico.
- 4. Inserisci o rileva l'evento in modo atomico nel registro degli eventi logici.
- 5. Correla tramite ID esterno e applica le regole di transizione una sola volta per evento logico.
- 6. Restituisci una risposta HTTP coerente dopo aver persistito il risultato necessario affinché un ritentativo sia sicuro.
Macchina a stati: rendi esplicite le transizioni consentite
Una macchina a stati evita che la logica dipenda dall'ordine accidentale di arrivo. La guida al formato SMPP classifica ENROUTE come stato intermedio e DELIVERED, EXPIRED, DELETED e UNDELIVERABLE come stati finali. Indica inoltre che un messaggio in ritentativo può rimanere ENROUTE e concludersi successivamente con EXPIRED o DELIVERED.
Rappresenta gli stati ricevuti senza riscriverli e definisci un livello di stato operativo calcolato. Nel modello SMPP descritto, gli stati finali non progrediscono verso altri stati. Pertanto, un evento successivo che sembri contraddire un esito finale già calcolato non deve sostituirlo silenziosamente: deve essere conservato e aprire un'eccezione per riconciliazione o indagine.
Non generalizzare i codici di errore da una piattaforma all'altra. I codici di rete o dell'SMSC possono essere specifici del gateway o della piattaforma. Memorizzali come evidenza originale e crea classificazioni interne solo quando le relative regole sono documentate per l'integrazione specifica.
- Stato grezzo: il valore ricevuto, senza reinterpretazione distruttiva.
- Stato normalizzato: una categoria interna documentata, se l'integrazione consente una mappatura affidabile.
- Stato calcolato: risultato dell'applicazione di regole di precedenza e transizione sugli eventi correlati.
- Eccezione: conflitto tra eventi, evento finale contraddittorio, assenza di correlazione o formato non riconosciuto.
- Regola conservativa: uno stato finale già materializzato non deve diventare un altro stato finale solo perché arriva un callback successivo.
Eventi tardivi e fuori ordine: conserva l'evidenza e calcola con prudenza
Conserva almeno due tempi distinti: quando il tuo sistema ha ricevuto il callback e quale orario riporta il callback, se lo riporta. Può inoltre essere necessario mantenere le date di presentazione e completamento incluse nei formati di ricevuta. Questi riferimenti temporali non sono intercambiabili: uno descrive l'osservazione locale e l'altro il dato comunicato dalla piattaforma di messaggistica.
Per decidere lo stato calcolato, non ordinare ciecamente in base all'istante di arrivo. Applica una policy documentata che consideri il tipo di stato, il suo carattere intermedio o finale e la qualità della correlazione. Se non esiste una base affidabile per ordinare due eventi, non inventare una sequenza: mantieni entrambi e segnala il conflitto.
L'output operativo deve distinguere tra lo storico completo e il riepilogo corrente. Un pannello può mostrare uno stato calcolato finale e, contemporaneamente, indicare che vi sono stati eventi ripetuti, tardivi o in conflitto. Questo approccio riduce la tentazione di nascondere segnali che in seguito risultano essenziali per assistenza, riconciliazione o analisi della qualità.
- Conserva l'ordine di ricezione locale, gli orari riportati e il payload originale.
- Non eliminare un evento duplicato: contrassegnalo come ripetizione dello stesso evento logico, quando appropriato.
- Non sostituire un esito finale con un altro esito finale incompatibile senza una regola contrattuale o tecnica verificabile per quella integrazione.
- Usa una coda di indagine per conflitti e messaggi non correlati.
- Mostra nell'audit l'evento che supporta lo stato calcolato e gli eventi che non hanno potuto essere applicati.
Checklist per la produzione
Prima di collegare i callback DLR a metriche, fatturazione, avvisi o decisioni di campagna, testa l'integrazione come un sistema di eventi e non soltanto come una chiamata HTTP riuscita. La priorità è evitare che un ritentativo, un payload inatteso o una ricevuta senza correlazione si trasformino in un'affermazione errata di consegna.
BulkSMSMarket descrive connettività HTTP e SMPP, oltre a strumenti orientati alla qualità e alla coerenza dei DLR. In ogni connessione, conferma la semantica concreta dei campi, i formati e le regole delle ricevute della controparte prima di trasformarli in automazioni critiche.
- Prova lo stesso callback ripetuto e conferma che lo stato calcolato e i contatori non cambiano per effetto della ripetizione.
- Prova un evento intermedio che arriva dopo uno stato finale e conferma che non degrada né sovrascrive il risultato finale.
- Prova due esiti finali incompatibili e verifica che il secondo sia preservato e contrassegnato per indagine.
- Prova callback senza ID evento, con ID esterno non trovato e con formati di data o payload non riconosciuti.
- Verifica che i messaggi non correlati non vengano assegnati automaticamente in base a destinatario o contenuto.
- Controlla che i report distinguano accettazione, stato DLR riportato ed eventuale verifica indipendente che possa esistere al di fuori del DLR.
Domande frequenti
Un callback DLR duplicato significa che l'SMS è stato consegnato due volte?
No. Può trattarsi della ripetizione di una notifica. Deve essere conservato come ricezione osservata, ma l'effetto sullo stato calcolato e sui contatori deve essere applicato una sola volta all'evento logico.
Posso correlare un DLR usando solo il numero del destinatario?
Non è consigliabile. Il destinatario può ripetersi in più messaggi. La base principale della correlazione deve essere l'ID esterno assegnato al messaggio, come il message_id SMPP associato a receipted_message_id. Il destinatario serve come verifica ausiliaria.
DELIVERED conferma che il destinatario ha letto il messaggio?
No. DELIVERED è uno stato di consegna riportato nel flusso di messaggistica. Non costituisce una conferma indipendente di lettura umana né di visualizzazione del contenuto sul terminale.
Cosa devo fare se arriva uno stato finale dopo un altro stato finale diverso?
Conserva entrambi gli eventi e contrassegna il conflitto per indagine. Non sostituire silenziosamente uno stato finale con un altro stato finale incompatibile solo in base all'ordine di ricezione del callback.
È necessario conservare il payload originale del DLR?
Sì. Formati e codici possono variare tra piattaforme. Il payload originale consente di verificare il parser, esaminare campi non normalizzati e indagare discrepanze senza perdere l'evidenza ricevuta.
Fonti consultate
- SMPP Protocol Specification v3.4, Issue 1.2SMPP Developers Forum
- SMPP Delivery Receipt FormatSMPP Developers Forum
- RFC 9110: HTTP SemanticsIETF / RFC Editor
- Recommendation ITU-T E.164: The international public telecommunication numbering planInternational Telecommunication Union