Ir para o conteúdo principal

Devlog · Fase 0 · 4/7

A confirmação por e-mail na lista de espera de aubia.dev

O formulário de aubia.dev registra uma solicitação antes da confirmação por e-mail. Correções de digitação, proteção contra abusos e destino dos endereços que continuam pendentes.

Publicado em 3 de agosto de 2026Atualizado em 13 de setembro de 20268 min de leitura
  • waitlist
  • react
  • security
  • gdpr

Você pode digitar o endereço de e-mail de outra pessoa em um formulário. A aprovação desse endereço na validação não permite saber quem o enviou, nem se o destinatário deseja receber um convite.

Por isso, em aubia.dev, peço uma confirmação por e-mail. O formulário cria uma inscrição pendente em aubia.cloud, e um link recebido por e-mail permite ao destinatário confirmá-la. Esse double opt-in acrescenta uma etapa ao processo, com a possibilidade de a mensagem nunca ser aberta.

O site encaminha a solicitação sem guardar as inscrições em um banco de dados local. O artigo sobre a ausência de banco de dados detalha essa separação entre o site e o cloud.

O fluxo animado da inscrição: envio do formulário para aubia.dev, requisição assinada com HMAC para api.aubia.cloud, e-mail contendo um token e confirmação em até 48 horas.

Corrigir um erro de digitação antes do envio

Um endereço digitado incorretamente pode ser válido e nunca receber a mensagem esperada. O formulário sugere uma correção quando o domínio se parece com o de um serviço de e-mail conhecido: gmial.com pode se tornar gmail.com.

A comparação usa a distância de Levenshtein, que conta as alterações necessárias para transformar uma string em outra. Ela compara o domínio com uma lista limitada e, se não encontrar uma correção, faz o mesmo com o sufixo, usando outra lista. Não consulta a caixa de e-mail nem corrige a parte anterior ao @.

A sugestão aparece quando você sai do campo. Ela também é recalculada no envio, porque enviar pelo teclado não necessariamente tira o foco do campo. Se um provável erro for detectado, o primeiro envio é suspenso para você conferir o endereço.

Você pode aplicar a correção ou manter o que digitou. Os dois botões devolvem o foco ao campo sem enviar o formulário. Você pode enviar o mesmo endereço uma segunda vez mesmo sem escolher nenhuma das opções: a semelhança com um domínio conhecido não basta para bloquear um endereço que você sabe estar correto.

O label é associado ao campo, os erros usam aria-invalid e role="alert", e aria-describedby conecta a sugestão ao campo de entrada. A mensagem de correção precisa ser legível sem depender de uma mudança de cor.

Uma resposta visual imediata, ainda provisória

O componente React envia o formulário pelo Inertia, sem recarregar a página inteira. Assim que o envio começa, ele aciona uma notificação global gerenciada com useOptimistic. A resposta visual aparece antes da resposta do servidor.

Essa exibição antecipa um resultado positivo. Antes da resposta do servidor, o navegador ainda não sabe se o cloud registrou a solicitação nem se o e-mail poderá ser entregue. A resposta efetiva substitui a notificação pela mensagem adequada ou desfaz o estado provisório em caso de erro.

O cloud distingue quatro resultados para uma solicitação aceita:

Resultado Tratamento da solicitação
confirmation_sent Uma inscrição pendente é criada e o envio do e-mail de confirmação é colocado na fila.
confirmation_resent Um novo link é preparado para uma inscrição pendente, depois do intervalo que permite um reenvio.
already_pending Já existe uma solicitação recente; nenhum novo e-mail é disparado.
already_confirmed O endereço já está confirmado; não há uma inscrição a recomeçar.

Cada resultado tem uma mensagem traduzida nos seis idiomas do site. Prefiro informar que o endereço já está confirmado a deixar o destinatário esperando um e-mail que não será enviado.

O proxy também verifica o código de status HTTP. Erros de validação, de limite de requisições e de serviço têm prioridade sobre um eventual status de sucesso no corpo da resposta. O código HTTP 409 acompanhado especificamente de already_confirmed é reconhecido como uma inscrição já confirmada. Outros conflitos não são tratados como sucesso.

Um endereço rejeitado e um limite de requisições excedido têm, cada um, sua própria mensagem. Uma assinatura rejeitada ou um serviço cloud indisponível geram uma mensagem de indisponibilidade temporária; os detalhes técnicos servem ao diagnóstico no servidor.

Nem mesmo confirmation_sent comprova a entrega. Um job na fila cuida do envio: o servidor solicitou sua execução, mas o envio ainda pode falhar ou o serviço de e-mail do destinatário pode filtrar a mensagem.

Limitar abusos sem captcha

Não acrescento um captcha ao formulário. Ele exigiria mais uma verificação do visitante sem tornar impossíveis os envios automatizados. O site combina controles que não exigem nenhuma interação adicional.

Um honeypot, um campo oculto na tela e excluído da navegação pelo teclado, deve permanecer vazio. Ele permite rejeitar envios de bots que preenchem os campos indiscriminadamente. Uma armadilha temporal compara o timestamp fornecido pelo navegador com o momento do envio e rejeita envios rápidos demais.

O servidor verifica esses valores, mas eles vêm do cliente. Um programa pode deixar o honeypot vazio e fornecer um timestamp aceitável. Essas armadilhas filtram comportamentos simples; não comprovam que uma pessoa preencheu o formulário.

Um rate limiter atua em paralelo por endereço IP, por endereço de e-mail e sobre o volume total de requisições. O limite por e-mail reduz as solicitações repetidas que atingem um mesmo destinatário. O limite global restringe o volume mesmo quando as requisições vêm de vários IPs, com o risco de também rejeitar inscrições legítimas durante um pico de tráfego.

A validação do endereço verifica o formato e, em produção, o DNS do domínio. O DNS pode ajudar a descartar um domínio inadequado para receber e-mails, mas não determina se aquela caixa de e-mail existe.

Depois de aceitar uma solicitação, o site a assina com HMAC antes de encaminhá-la ao cloud. Os dois servidores compartilham um segredo que permite verificar a assinatura do corpo recebido. Essa proteção se aplica à requisição de inscrição entre servidores: impede que uma chamada sem assinatura a esse endpoint substitua o proxy. Nem todas as outras rotas da API exigem essa assinatura, em especial as que usam tokens de confirmação ou de cancelamento da inscrição.

O link recebido por e-mail

O cloud registra o endereço com o status PENDING antes de preparar o e-mail. O link contém um token aleatório, e apenas seu hash é guardado no registro da inscrição. Ele é válido por 48 horas a partir da data de envio registrada pela aplicação, antes da execução do job de e-mail.

O botão de confirmação abre uma página de aubia.dev no idioma usado na inscrição. Após o carregamento, a página lê o token na URL e consulta diretamente a API cloud. Ela exibe o resultado: confirmado, já confirmado, link inválido, não encontrado ou expirado.

A confirmação atualiza o status e sua data uma única vez. Duas requisições concorrentes não devem gerar duas confirmações. Outro clique no mesmo link reconhecido retorna "já confirmado", inclusive depois do prazo de validade original, pois a inscrição já foi concluída.

Para um endereço ainda pendente, um reenvio substitui o hash do token e inicia um novo prazo de validade. O link anterior, portanto, deixa de localizar a inscrição. O reenvio invalida o link anterior assim que é preparado, mesmo que o novo e-mail ainda não tenha chegado.

As páginas de confirmação e de cancelamento da inscrição declaram noindex. Essa diretiva pede aos mecanismos de busca que não as indexem; não restringe o acesso. Os links do seletor de idioma são reconstruídos sem a query string, para evitar copiar o token nas outras cinco URLs. Mudar de idioma, portanto, não dá continuidade à confirmação com aquele token.

Um programa capaz de seguir o link e executar esse processo também pode confirmar. O double opt-in verifica o uso de um link enviado ao endereço informado, sem certificar a identidade nem a presença humana de quem o usa. O timestamp de confirmação ajuda a acompanhar a inscrição; sozinho, não basta para comprovar que todo o tratamento está em conformidade com o RGPD.

Os endereços que continuam pendentes

A expiração do token não exclui o registro. Uma tarefa diária exclui as inscrições ainda com status PENDING cuja data original de inscrição é de mais de 30 dias atrás. Reenviar a confirmação não reinicia esse prazo de retenção.

O cancelamento da inscrição segue outro processo. A página dedicada envia o token ao cloud, que anonimiza o registro: o endereço de e-mail é substituído, os tokens e os dados de atribuição são apagados e o status passa a PURGED. O registro em si permanece, com suas datas.

O botão de confirmação no corpo do e-mail é separado dos cabeçalhos List-Unsubscribe e List-Unsubscribe-Post. Esses cabeçalhos fornecem aos clientes de e-mail instruções para oferecer o cancelamento da inscrição. Sua presença não garante que todos os clientes exibam o comando ou o processem da mesma forma.

O que o contador mede

O contador da página inicial consulta no cloud o número de inscrições com status CONFIRMED. Os endereços pendentes ficam de fora. Convites administrativos registrados diretamente como confirmados também são contados: o total não corresponde exclusivamente a cliques nos links enviados a partir do formulário público.

O valor chega depois da renderização inicial, por uma requisição adiada, com cache no servidor. Ele pode estar defasado em relação a uma confirmação e só é exibido acima de um limite mínimo. O artigo sobre Octane e Inertia SSR detalha o carregamento.

Para medir quantas solicitações do formulário chegam à confirmação, preciso distingui-las dos convites administrativos.

Esta construção é contada aqui, artigo após artigo. O que vem a seguir depende do que você disser sobre ela.

Entrar na lista de espera