Design de um sistema de notificações: arquitetura para push, e-mail e SMS

Projete um sistema de notificações push, e-mail e SMS: uma API, preferências e horário de silêncio, deduplicação, filas por canal, retries e rastreio de entregas.

· Fundador e desenvolvedor

Publicado em

Design de um sistema de notificações: arquitetura para push, e-mail e SMS

Notificações começam como uma linha de código: quando um pedido é despachado, mande um e-mail. Um ano depois, a mesma empresa envia push, e-mail e SMS de uma dúzia de serviços, os usuários recebem o mesmo alerta três vezes, ninguém sabe dizer se um e-mail de redefinição de senha foi entregue e mensagens de marketing acordam as pessoas às 3 da manhã.

Um sistema de notificações resolve isso colocando um único serviço entre o código que decide que algo aconteceu e os provedores que entregam as mensagens. Este artigo projeta esse serviço: a API, as checagens que rodam antes de qualquer envio, a entrega por canal com retries e o rastreio. O diagrama perto do fim mostra o fluxo inteiro.

Requisitos

  • Serviços de produto enviam uma notificação com uma única chamada, sem saber quais canais o usuário ativou.
  • Push (iOS e Android), e-mail e SMS, com templates por idioma.
  • Os usuários controlam canais e assuntos, e o horário de silêncio é respeitado.
  • O mesmo evento nunca gera a mesma mensagem duas vezes.
  • Toda mensagem tem um status: enviada, entregue, devolvida, com falha.
  • Um provedor lento nunca atrasa os outros canais.

A API: eventos, não mensagens

Serviços de produto não devem montar e-mails. Eles enviam um evento com os fatos e uma chave de idempotência:

POST /notifications
{
  "idempotencyKey": "order-8812-shipped",
  "userId": "u_7",
  "type": "order_shipped",
  "data": {"orderId": "8812", "carrier": "UPS", "eta": "2026-10-30"}
}

O serviço de notificações cuida de todo o resto: quais canais esse tipo usa, qual template, qual idioma e se o usuário quer receber. Quando um time quer mudar o texto ou adicionar SMS a um tipo de notificação, nada muda no serviço de pedidos.

Antes de enviar qualquer coisa

Quatro checagens rodam na API, nesta ordem, antes que o trabalho chegue a um canal:

  1. Deduplicar. Guarde a chave de idempotência com um TTL e descarte repetições. Retries em serviços de origem são normais e não devem chegar aos usuários como duplicatas.
  2. Checar preferências. Consulte os canais do usuário, os cancelamentos por assunto e o horário de silêncio. Alertas de segurança, como redefinições de senha, costumam ignorar cancelamentos de marketing; marketing nunca ignora. Escreva essa regra.
  3. Limitar o volume. Limite as mensagens por usuário, por canal e por hora. Um bug que dispara 500 eventos não deve virar 500 notificações push.
  4. Renderizar. Escolha o template pelo tipo de notificação, pelo canal e pelo idioma do usuário, e preencha com os dados do evento.

O horário de silêncio precisa do fuso do usuário. Segure a mensagem e agende-a para o fim da janela em vez de descartá-la, a menos que ela seja urgente.

Uma fila e um pool de workers por canal

Depois das checagens, a API grava um job por canal na fila daquele canal. Filas separadas são a decisão mais importante do design: quando o provedor de SMS fica lento, os jobs de SMS se acumulam na própria fila enquanto push e e-mail continuam fluindo.

Os workers de cada canal chamam os provedores e lidam com as manias deles:

  • Push. Dispositivos iOS passam pelo Apple Push Notification service e Android pela API HTTP v1 do FCM. Tokens de dispositivo envelhecem: o APNs responde 410 para um token que não está mais ativo e o FCM retorna um erro unregistered. Apague esses tokens, ou você continua pagando latência por mensagens que ninguém recebe.
  • E-mail. Envie por um provedor e reporte devoluções e reclamações. Se você envia e-mail em massa, adicione os cabeçalhos de descadastro em um clique definidos na RFC 8058. As diretrizes para remetentes do Gmail exigem descadastro em um clique nas mensagens de marketing e de assinatura de quem envia mais de 5.000 mensagens por dia.
  • SMS. O canal mais caro por mensagem e o mais regulado. Reserve-o para códigos e alertas urgentes, e confira as regras de cada país para onde você envia (este artigo não é aconselhamento jurídico).

Os workers repetem falhas temporárias com backoff exponencial e jitter, e movem um job para uma dead letter queue depois de um número fixo de tentativas. Falhas permanentes, como um endereço inválido, não são repetidas.

Rastreando a entrega

Os provedores informam o que aconteceu por webhooks ou códigos de resposta: aceita, entregue, devolvida, aberta. Grave cada mudança de status em um log de entregas indexado pelo ID da notificação. Assim o suporte responde em segundos se o usuário recebeu o e-mail de redefinição, e você consegue medir as taxas de entrega por canal e provedor.

As atualizações de status chegam fora de ordem e às vezes em dobro. Guarde-as como eventos com timestamp e derive o estado atual a partir deles, em vez de sobrescrever uma coluna de status.

Notificações dentro do app

Muitos produtos também mantêm uma caixa de entrada no app: o sininho com um contador. Trate-a como um quarto canal que grava em um banco de dados em vez de chamar um provedor. Guarde uma linha por usuário e notificação com uma flag de lida, indexe por usuário e data, e deixe o app buscar a página mais recente e a contagem de não lidas. Marque os itens como lidos em lote, para que um usuário que abre a caixa não dispare cinquenta escritas.

Contas de guardanapo

Com um milhão de usuários ativos por dia e cinco notificações por usuário por dia, o sistema envia 5 milhões de notificações por dia, cerca de 58 por segundo em média. O que importa são os picos: uma campanha ou um alerta de incidente pode enviar um milhão de mensagens em poucos minutos. Dimensione as filas para a rajada, deixe os workers esvaziá-las no ritmo que cada provedor permite e conte com os provedores limitando você antes que seus próprios workers fiquem sem capacidade.

O diagrama de arquitetura

Serviços de produto enviam eventos a uma API de notificações, que lê as preferências e distribui o trabalho entre filas de push, e-mail e SMS. Cada fila alimenta seus workers, que chamam APNs e FCM ou um provedor de e-mail ou SMS. Os provedores reportam o status a um log de entregas.

14 componentes · 14 conexões

Abrir no Archboard
Conteúdo do diagrama
  • Product services
  • Notification API
  • Preferences
  • Push queue
  • Email queue
  • SMS queue
  • Push workers
  • Email workers
  • SMS workers
  • APNs and FCM
  • Email provider
  • SMS provider
  • Delivery log
  • event
  • opt-outs, quiet hours
  • status
  • webhooks
Um sistema de notificações com uma única API na frente de três canais. A API deduplica, checa preferências e renderiza templates; depois cada canal tem sua fila, seus workers e seu provedor, e os status de entrega voltam para um único log.

Como ler:

  • Esquerda: os serviços de produto enviam eventos com chave de idempotência para a API de notificações. A API lê o armazenamento de preferências e roda as checagens listadas no bloco Markdown.
  • Meio: cada canal tem a própria fila e o próprio pool de workers, então um provedor lento só trava o próprio canal.
  • Direita: os workers chamam APNs e FCM, o provedor de e-mail e o provedor de SMS. As atualizações de status voltam ao log de entregas como arestas pontilhadas, porque chegam depois e de forma assíncrona.

O que colocar no diagrama

  • As checagens e a ordem delas. Deduplicação, preferências, limites e renderização são a lógica real do sistema.
  • Uma raia por canal. Se todos os canais dividem uma fila no seu diagrama, provavelmente dividem em produção também.
  • O comportamento de retry e dead letter. Anote o backoff e o limite de tentativas em cada worker.
  • O caminho de volta dos status. Sem ele, ninguém consegue depurar uma mensagem que sumiu.

Para o lado de filas e consumidores em mais detalhes, veja o artigo sobre arquitetura orientada a eventos. O diagrama de arquitetura de microsserviços mostra onde um serviço compartilhado como este fica entre os outros, e vale ler o artigo sobre mapeamento de dados para o GDPR antes de guardar dados de contato e logs de entrega.

Monte no Archboard

  1. Entre com Google ou GitHub e crie um diagrama.
  2. Adicione um Contêiner do pack Modelo C4 para os serviços de produto e outro para a API de notificações, e um Banco de dados abaixo da API para as preferências.
  3. Adicione três nós Fila do pack Dados, um por canal, e um Contêiner para os workers de cada canal.
  4. Adicione três nós Sistema externo do pack Modelo C4 para os provedores e um Banco de dados para o log de entregas.
  5. Desenhe as arestas de status pontilhadas e adicione um bloco Markdown do pack Básico com as checagens antes do envio.

O exemplo usa 14 nós de diagrama e fica dentro do plano Free.

Perguntas frequentes

Por que não deixar cada serviço enviar as próprias notificações? Porque aí preferências, horário de silêncio, deduplicação e rastreio de entregas precisam ser construídos em cada serviço, e eles divergem. Um único serviço de notificações aplica tudo do mesmo jeito em todo lugar.

Como evito notificações duplicadas? Exija uma chave de idempotência em toda requisição e guarde-a por um tempo. Uma chave repetida é confirmada e descartada.

Push, e-mail e SMS devem dividir uma fila? Não. Filas e pools de workers separados impedem que um provedor lento ou com falha atrase os outros canais.

Como lido com o horário de silêncio? Guarde o fuso e a janela de silêncio de cada usuário, e agende as mensagens não urgentes para o fim da janela em vez de enviá-las ou descartá-las.

Como sei que uma notificação foi entregue? Registre as respostas e os webhooks dos provedores em um log de entregas indexado pelo ID da notificação. “Entregue” significa coisas diferentes em cada canal, então registre os nomes de status do próprio provedor.

Comece grátis — um assento, sem cartão de crédito.

Artigos relacionados

← Voltar ao blog

Abra um canvas em um minuto

Entre com Google ou GitHub. Grátis para um assento. Faça upgrade quando a equipe entrar.