Voltar ao blog Conectividade e operações de SMS

Como conceber tentativas idempotentes numa API de SMS para evitar envios duplicados

Guia operacional para lidar com timeouts e resultados ambíguos em SMS sem transformar uma nova tentativa técnica numa segunda mensagem. Inclui chaves de idempotência, estados persistentes, reconciliação com callbacks ou DLR e controlos específicos para OTP.

Diagrama de fluxo para tentativas idempotentes numa API de SMS

Porque é que um timeout não prova que o SMS não foi aceite

Um timeout indica que o cliente não recebeu uma resposta dentro do tempo configurado. Por si só, não prova que o servidor não recebeu o pedido, não o processou ou não aceitou a mensagem para envio posterior.

Numa operação HTTP, todos os bytes do pedido podem ter sido transmitidos e a mensagem remota pode ter sido criada antes de a resposta se perder. Também pode acontecer que a ligação falhe antes de o fornecedor receber o pedido. Do ponto de vista da aplicação cliente, ambos os cenários podem parecer idênticos: não existe uma resposta utilizável.

Por este motivo, um timeout após o início da transmissão deve ser classificado como um resultado ambíguo. Repetir imediatamente uma criação de mensagem com uma nova identidade pode produzir um segundo SMS, mesmo que o primeiro já exista ou tenha sido aceite.

  • Não trate a ausência de resposta como uma rejeição definitiva.
  • Diferencie, sempre que a sua biblioteca e telemetria o permitirem, entre falha antes de enviar o pedido, falha durante a transmissão e falha após o envio.
  • Conceba o fluxo para que a mesma intenção de negócio possa ser consultada, reconciliada ou repetida sem criar uma nova operação.
Porque é que um timeout não prova que o SMS não foi aceite

O risco operacional dos duplicados

Um duplicado não é apenas um custo adicional. Num fluxo de OTP, pode confundir a pessoa utilizadora se receber códigos diferentes, sobretudo se o sistema invalidar o código anterior ao gerar um novo. Em alertas operacionais, dois avisos podem desencadear ações repetidas. Em notificações transacionais, o destinatário pode interpretar a segunda mensagem como um erro ou uma tentativa de fraude.

A prevenção não deve ser confundida com supressão indiscriminada. Duas ações de negócio realmente distintas podem exigir dois SMS para o mesmo número e com conteúdo semelhante. O objetivo é deduplicar a repetição técnica da mesma intenção, e não impedir comunicações legítimas, consentidas e necessárias.

  • Uma nova tentativa técnica deve preservar a identidade da intenção original.
  • Um novo desafio OTP, uma nova operação ou uma nova versão deliberada da mensagem deve ter uma nova identidade.
  • A deduplicação deve ser aplicada com base numa janela temporal e numa semântica documentadas, e não apenas no número de destino.
O risco operacional dos duplicados

Defina um modelo de estados que separe aceitação e entrega

Um modelo mínimo deve distinguir o que a sua aplicação sabe, o que o fornecedor confirma e, posteriormente, o que o ciclo de entrega informa. A aceitação por um fornecedor ou por uma operadora a montante não equivale necessariamente à entrega final ao destinatário.

Uma sequência interna prática pode incluir: criado, tentativa em curso, enviado ao fornecedor, aceitação confirmada, resultado pendente e resultado final. A designação exata é menos importante do que manter transições persistentes, auditáveis e sem ambiguidade.

O resultado final pode representar uma entrega reportada, uma não entrega, uma falha definitiva ou o esgotamento de uma janela de reconciliação. Se receber DLR ou callbacks, registe o evento original e o respetivo momento de receção, além do estado consolidado.

  • Criado: a intenção de negócio já foi persistida, mas o envio ainda não foi iniciado.
  • Tentativa em curso: foi reservada uma tentativa identificável antes de abrir a ligação.
  • Aceitação confirmada: o fornecedor respondeu e devolveu uma confirmação ou identificador de acordo com o seu contrato.
  • Resultado pendente: existe aceitação ou um resultado ambíguo, e aguarda-se consulta, callback, DLR ou outra reconciliação.
  • Resultado final: o fluxo atingiu um estado terminal segundo as regras documentadas para essa integração.

Utilize uma chave de idempotência para uma intenção específica

Uma chave de idempotência permite que uma API distinga a repetição de um pedido anterior de uma nova operação. Para funcionar, o cliente deve reutilizar a mesma chave ao repetir exatamente a mesma intenção de negócio.

A chave não deve representar apenas o número de telefone. Um mesmo destinatário pode receber várias comunicações legítimas. Deve estar associada a um evento estável: por exemplo, o identificador interno de uma notificação, um desafio OTP específico, o destinatário, o canal SMS e a versão de conteúdo que se pretende enviar.

Se o fornecedor suportar um token de idempotência, respeite o seu contrato: formato, localização no pedido, tratamento de parâmetros divergentes, âmbito da deduplicação e período de retenção. Se não o suportar, a idempotência deve ser controlada sobretudo no seu próprio sistema, e as novas tentativas após ambiguidade exigem ainda mais prudência.

  • Gere a chave uma vez por intenção, e não uma vez por tentativa de rede.
  • Persista a chave antes da chamada remota.
  • Guarde uma impressão digital ou versão dos parâmetros relevantes para detetar se alguém tenta reutilizar a chave numa operação diferente.
  • Não reutilize a chave para um novo desafio OTP nem para uma comunicação de negócio independente.
  • Não presuma que todos os fornecedores deduplicam durante a mesma duração ou com a mesma semântica.

Persista a intenção antes de chamar HTTP ou SMPP

A persistência deve ocorrer antes da operação de rede. Se chamar primeiro a API e só depois guardar um registo, uma interrupção entre ambas as ações pode deixar uma mensagem remota sem uma intenção local que permita rastreá-la ou reconciliá-la.

Numa transação local, crie o registo de envio, atribua o identificador interno, a chave de idempotência, os parâmetros necessários para reproduzir o pedido e o estado criado. Depois, um processo de envio pode assumir esse registo, marcar a tentativa como em curso e realizar a chamada ao fornecedor.

Ao receber uma resposta válida, persista o identificador remoto e o significado da resposta. Em SMPP, uma resposta bem-sucedida a submit_sm devolve um message_id atribuído pelo SMSC; este identificador deve ser conservado para correlacionar operações e recibos posteriores.

  • Identificador interno da intenção.
  • Chave de idempotência e impressão digital do pedido.
  • Destino e conteúdo, ou referência segura à respetiva versão autorizada.
  • Marca temporal de criação, início e fim de cada tentativa.
  • Identificador devolvido pelo fornecedor ou SMSC, quando existir.
  • Estado atual, histórico de transições e motivo de qualquer estado terminal.

Classifique os resultados antes de decidir se deve repetir

Uma política de novas tentativas segura não se baseia em “qualquer erro é reenviado”. Deve separar respostas definitivas, falhas recuperáveis e resultados ambíguos. As categorias concretas devem resultar do contrato de cada fornecedor e do protocolo utilizado.

As respostas definitivas exigem normalmente terminar ou devolver o controlo ao fluxo de negócio: por exemplo, credenciais inválidas, parâmetros inválidos, formato não suportado ou um endereço de destino declarado inválido. Repetir sem alterar a causa não aumenta a fiabilidade e pode aumentar tráfego desnecessário.

As falhas recuperáveis podem incluir indisponibilidade temporária, limitação de débito ou erros temporários de rede explicitamente documentados. Em SMPP, ESME_RTHROTTLED indica que foram excedidos os limites de mensagens permitidos; a reação adequada é reduzir a pressão e aplicar uma espera controlada, e não reenviar em ciclo.

Os resultados ambíguos incluem timeouts, desligamentos e respostas perdidas depois de o envio ter começado. Não devem ser tratados automaticamente como falhas recuperáveis, porque o fornecedor pode já ter aceite a mensagem.

  • Definitivo: termine, registe a causa e corrija o pedido ou o fluxo antes de criar uma nova intenção.
  • Recuperável: agende uma nova tentativa limitada com a mesma chave de idempotência.
  • Ambíguo: consulte quando possível, aguarde um sinal de reconciliação e reutilize a mesma identidade apenas se o fornecedor oferecer deduplicação compatível.
  • Throttling: aplique controlo de taxa e espera progressiva; não concentre novas tentativas no mesmo instante.

Aplique limites, espera progressiva e uma janela de validade

Uma nova tentativa deve ser limitada pelo número de tentativas, pelo tempo total e pela validade da intenção de negócio. A espera progressiva evita concentrar pedidos após uma incidência ou uma limitação de débito. Pode adicionar variação aleatória controlada para evitar que muitos trabalhadores repitam de forma sincronizada.

Não existe um número universal de tentativas nem uma espera válida para todas as rotas e casos de utilização. Defina-os com base na criticidade da mensagem, no comportamento documentado do fornecedor, nos limites de débito contratados ou técnicos e na vida útil do conteúdo.

A política deve parar ao atingir um estado final, ao expirar a janela de negócio ou quando o orçamento de tentativas for excedido. Uma nova tentativa posterior à utilidade da mensagem pode ser pior do que uma falha: um alerta tardio ou um OTP expirado não resolve a necessidade original.

  • Defina um máximo de tentativas e um limite de tempo total por intenção.
  • Aumente o intervalo entre tentativas perante falhas temporárias ou throttling.
  • Mantenha uma fila diferida em vez de bloquear o fluxo principal com esperas ativas.
  • Registe cada decisão: porque foi repetida, quanto tempo se esperou e que regra interrompeu o processo.
  • Não transforme uma recuperação técnica num envio indefinido.

Trate os OTP como um caso de segurança e experiência do utilizador

Em OTP, a intenção não é “enviar um texto para um número”, mas entregar um código associado a um desafio específico e com validade limitada. A chave de idempotência deve corresponder a esse desafio, e não a um pedido HTTP individual.

O sistema deve definir claramente se uma nova tentativa de verificação reutiliza o mesmo desafio ou cria outro. Se criar outro código e deixar o anterior ativo, o utilizador pode receber vários códigos válidos. Se invalidar o anterior, um SMS atrasado pode conter um código que já não funciona. Ambas as decisões são de produto e segurança, mas devem ser coerentes com a política de envio.

A janela de tentativas deve terminar antes de o desafio expirar. Deve também ser coordenada com qualquer período de validade de fila configurado no fornecedor. Não é adequado continuar a tentar entregar um OTP depois de já não poder ser verificado.

  • Associe cada OTP a um identificador de desafio persistente.
  • Repita o mesmo envio com a mesma chave quando se tratar da mesma intenção.
  • Documente quando é gerado um novo código e o que acontece aos anteriores.
  • Interrompa tentativas e envios pendentes quando o desafio expirar.
  • Meça os duplicados, os atrasos e os abandonos sem armazenar mais dados pessoais do que o necessário.
FAQ

Perguntas frequentes

Um timeout numa API de SMS significa que a mensagem não foi enviada?

Não. Pode significar que a ligação falhou antes de o fornecedor receber o pedido, mas também que o fornecedor o recebeu e processou enquanto a resposta se perdeu ou foi atrasada. Trate-o como um resultado ambíguo até conseguir reconciliá-lo.

O que deve conter uma chave de idempotência para SMS?

Deve identificar uma intenção de negócio específica. Pode estar associada a um identificador interno de evento ou desafio OTP, destinatário, canal e versão de conteúdo. Não deve ser apenas o número de destino nem ser regenerada em cada nova tentativa.

Devo repetir um erro de throttling?

Pode ser candidato a uma nova tentativa controlada se o contrato do fornecedor o classificar como temporário. Aplique controlo de taxa, espera progressiva e limites. Não reenvie imediatamente nem de forma ilimitada.

Um DLR confirma sempre a receção no telemóvel?

Não necessariamente. O significado de cada estado depende do contrato e da informação disponível na cadeia de entrega. Deve distinguir entre aceitação pelo fornecedor, envio para uma operadora, DLR recebido e qualquer confirmação de entrega reportada. Um DLR não deve ser interpretado para além da sua semântica documentada.

Os callbacks substituem a resposta de criação da mensagem?

Não. A resposta síncrona e os callbacks têm funções diferentes. Alguns fornecedores não emitem callback para o estado inicial, pelo que deve persistir a resposta de criação e reconciliar as alterações posteriores através de callbacks, consultas ou DLR.

O que fazer se o fornecedor não suportar chaves de idempotência?

Controle a intenção e os estados no seu próprio sistema antes de chamar o fornecedor. Perante um timeout ambíguo, dê prioridade à consulta através dos identificadores disponíveis e à reconciliação de eventos. Se não existir um mecanismo remoto de deduplicação ou consulta, documente essa limitação e seja especialmente restritivo ao criar um segundo pedido.

Fontes consultadas

  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