ADR-0029 Email transacional
Padrão da empresa para o email das apps — templates React Email enviados com Nodemailer por SMTP, a partir de um job em segundo plano; o fornecedor SMTP é escolhido por projeto.
- Decisão
- React Email + Nodemailer por SMTPConstruir a app
- Estado
- Aceite
- Data
- Âmbito
- Predefinição da empresa para todas as novas apps
Ligações
Contexto
Seção intitulada “Contexto”Todas as apps enviam email. O Better Auth chama o nosso código para enviar mensagens de verificação, de reposição de palavra-passe e de magic link (ADR-0009), e os produtos acrescentam as suas notificações.
- O fornecedor varia por projeto: o tenant Microsoft 365 ou Azure de um cliente, um relay que ele já paga, ou um escolhido por nós. Não há um fornecedor da empresa, nem vai haver
- Fazemos self-host (ADR-0003), pelo que o deployment não traz nenhum serviço de email da plataforma
- Os templates são código: recebem dados tipados, são revistos e têm de renderizar no Outlook e no Gmail, não só num browser
- Um envio falhado não pode fazer falhar o pedido que o originou, e tem de ser repetido
O que está em aberto: como se escrevem os templates, como o email sai da app e o que fica para cada projeto.
Opções consideradas
Seção intitulada “Opções consideradas”1. A API HTTP e o SDK de um fornecedor (Resend, Postmark, SES, Azure Communication Services)
Seção intitulada “1. A API HTTP e o SDK de um fornecedor (Resend, Postmark, SES, Azure Communication Services)”Prós
- Webhooks de bounces e de queixas, estatísticas de entrega, listas de supressão
- Sem ligações SMTP para gerir
Contras
- Um SDK por fornecedor: um projeto que tenha de usar o fornecedor do cliente reescreve o módulo de email
- Chaves, limites e funcionalidades variam por fornecedor, pelo que nada é um padrão da empresa
2. SMTP com Nodemailer, templates em React Email
Seção intitulada “2. SMTP com Nodemailer, templates em React Email”Prós
- Todos os fornecedores falam SMTP: mudar de fornecedor é mudar variáveis de ambiente
- O Nodemailer não tem dependências de runtime e cobre pooling, OAuth2 e assinatura DKIM
- O React Email 6 é um só pacote: componentes,
render()para HTML e texto simples, e um servidor de pré-visualização. A documentação inclui um guia para o Nodemailer - Os templates são React com props tipadas — a linguagem que a equipa já escreve (ADR-0001)
Contras
- Sem informação de bounces ou queixas por SMTP; isso continua a depender do fornecedor
- As repetições, os limites de ligações e a renovação de tokens OAuth2 são nossos
- O React Email é mantido pela Resend, um fornecedor que podemos não usar
3. Templates HTML escritos à mão ou em MJML
Seção intitulada “3. Templates HTML escritos à mão ou em MJML”Prós: sem React no caminho do email; o MJML é maduro. Contras: uma segunda linguagem de templates, dados sem verificação de tipos e nada que o React Email não cubra já.
Decisão
Seção intitulada “Decisão”React Email para os templates e Nodemailer por SMTP para a entrega são o padrão da empresa. O fornecedor SMTP é escolhido por projeto.
Em concreto:
- Pacotes:
react-email6.x (6.11.12 à data de escrita) enodemailer10.x (10.5.0). Desde a 6.0 oreact-emailcontém os componentes, orendere a CLI — não instalar o@react-email/components - Fornecedor por projeto, definido apenas por variáveis de ambiente: host SMTP, porta, credenciais e endereço do remetente, declarados em
server/env.ts(ADR-0026). Nada no código nomeia um fornecedor - TLS sempre: porta 465 com
secure: true, ou 587 comrequireTLS: true. Autenticação por palavra-passe, ou OAuth2 (auth.type: "OAuth2") onde o fornecedor o exija — o Microsoft 365 precisa de um token com o scopehttps://outlook.office.com/SMTP.Send - Um só módulo de email,
src/server/mail/, é o único código que importa onodemailer. Cria um transporter com pool e expõe umsendMailtipado. O código das funcionalidades recebe-o como parâmetro (ADR-0027) - Os templates vivem em
src/server/mail/templates/como componentes React Email com props tipadas. Todas as mensagens são enviadas com as duas partes:render(<Template />)para HTML erender(<Template />, { plainText: true })para texto. Só componentes do React Email — os clientes de email não correm o nosso CSS, pelo que o shadcn/ui (ADR-0011) fica de fora. Pré-visualizar comemail dev --dir src/server/mail/templates - O email é enviado a partir de um job (ADR-0028): o pedido coloca em fila, o worker renderiza e envia, e uma falha de SMTP é repetida com backoff
- Os callbacks do Better Auth (
sendVerificationEmail,sendResetPassword, magic links) colocam em fila e devolvem sem esperar — a documentação avisa que esperar pelo envio expõe o tempo de resposta - A entregabilidade configura-se por deployment com o fornecedor: SPF, DKIM e DMARC para o domínio de envio. A opção
dkimdo Nodemailer assina as mensagens quando o relay não o faz - Os logs levam o ID do utilizador e o nome do template, nunca o endereço do destinatário nem o corpo
- Ler uma caixa de correio é opt-in por produto: IMAP através do
imapflow2.x, a correr num worker - Os testes passam um transporter criado com
jsonTransport: true: nada sai do processo. SMTP não é HTTP, pelo que a regra do MSW do ADR-0014 não o vê
Os fatores decisivos: um só caminho de email que funciona com o fornecedor que cada projeto trouxer, templates na linguagem que já usamos e repetições a partir da fila que já corremos. Aceitamos tratar dos bounces e dos limites do fornecedor por projeto.
Consequências
Seção intitulada “Consequências”Mais fácil:
- Mudar um produto para outro fornecedor é uma alteração de configuração
- O email de autenticação e o email do produto partilham um módulo, um estilo de templates e uma política de repetições
- Os templates têm verificação de tipos e pré-visualizam-se sem enviar
Mais difícil:
- Os bounces, as queixas e os limites de envio variam por fornecedor e tratam-se por projeto
- OAuth2 para o Microsoft 365 significa obter e renovar tokens nós próprios
- O HTML de email é um ofício à parte: testar os templates em clientes reais, não só na pré-visualização
Do que abdicamos: das estatísticas, dos webhooks e das listas de supressão dos fornecedores como padrão. Aceite — um projeto que precise deles acrescenta a API do seu fornecedor ao lado deste módulo.

