Arquitetura
O mxout recebe por dois canais, autentica quem envia, assina com DKIM e entrega direto no MX do destinatário. Desde a v0.9.0 ele guarda estado: a mensagem aceita sobrevive a restart e a crash.
Diagrama de fluxo
aplicação aplicação
│ SMTP AUTH (2525) │ POST /send-raw (Basic)
▼ ▼
┌──────────────────────────────────────────────────────────────┐
│ mxout │
│ │
│ 1. Autentica o CLIENTE (bcrypt) │
│ 2. Autoriza o domínio do envelope-from │
│ 3. Garante Date e Message-ID │
│ 4. Verifica alinhamento do header From │
│ 5. Cota do domínio (hora e dia) │
│ │
│ ── síncrono ────────── ou ── assíncrono ──────────────── │
│ entrega na requisição grava estado + corpo, responde │
│ 202 e sai no tick do cron │
│ │
│ Por destinatário: │
│ 6. Assina DKIM (RSA-SHA256) ← ledger │
│ 7. Resolve MX │
│ 8. Entrega: EHLO → STARTTLS? → MAIL/RCPT/DATA │
│ 5xx → falha permanente · 4xx → reagenda │
│ backoff 1m → 5m → 15m → 1h → 4h ← ledger │
└──────────────────────────────────────────────────────────────┘
│
▼
MX do domínio destinoOnde o estado mora
banco embarcado → clientes, estado da entrega, tentativas, último erro
arquivo (spool) → o MIME cru, lido só na hora de entregar
arquivo (ledger) → o histórico permanente, uma linha JSON por eventoO banco é SQL embarcado em Rust puro, sem dependência externa: sobe o binário e funciona. Ele é residente em memória, e é por isso que o corpo da mensagem fica fora dele — com teto de 5 MiB por mensagem, uma fila cheia consumiria RAM demais.
A ordem de gravação é a garantia de corretude: o corpo vai para o disco e é sincronizado antes da linha de estado. Um crash entre as duas coisas deixa um arquivo órfão, que a varredura recolhe; a ordem inversa deixaria estado sem corpo, ou seja, mensagem impossível de entregar.
Quem drena a fila
O cron, chamando `mxout tick`. O tick é uma chamada HTTP no loopback — quem trabalha é o próprio servidor, porque o banco embarcado aceita um processo por vez.
Ciclo por destinatário
Uma única requisição POST /send pode conter múltiplos destinatários. Para cada um, o mxout executa o ciclo abaixo de forma independente.
1. Montagem MIME
O mxout constrói o e-mail com os dados recebidos. Gera o Message-ID no formato <unixts.token@dominio-do-from> e o Date antes de qualquer assinatura. Isso é necessário porque esses campos são assinados — gerá-los depois invalidaria a assinatura.
2. Assinatura DKIM
A chave privada RSA-2048 do domínio (indicada pelo kit configurado) assina os headers From, To, Subject, Date e Message-ID. O header DKIM-Signature resultante é inserido no e-mail.
3. Resolução de MX
O mxout consulta o DNS pelo registro MX do domínio do destinatário. Os servidores são ordenados por prioridade (menor valor = maior prioridade). Se não houver registro MX, o RFC 5321 permite usar o registro A como MX implícito — o mxout segue essa regra.
4. Loop de retry
Tentativas: imediata, +5 s, +10 s, +30 s (4 ao total). As tentativas ficam em memória; não há persistência.
- Resposta
5xxdo servidor remoto indica erro permanente. O destinatário é descartado imediatamente. - Resposta
4xxou falha de conexão indica erro temporário. O mxout retenta até esgotar as tentativas. - Esgotadas as tentativas sem sucesso, o erro é registrado no ledger e o destinatário é descartado.
Stateless e ledger
O mxout não mantém banco de dados nem fila em disco. O ledger gravado em stdout (JSON por linha) é a fonte de verdade de cada envio: contém o message_id, o destinatário, o resultado e os detalhes de cada tentativa.
- Ledger — formato completo dos eventos registrados.
- Entrega MX direta — detalha resolução de MX, STARTTLS e retry.