Changelog

Formato baseado em Keep a Changelog.

[1.0.0] - 2026-08-02

O mxout deixa de ser só o motor de entrega e passa a ser o serviço inteiro. A camada intermediária que existia antes (um worker que recebia por SMTP e enfileirava) foi absorvida e descontinuada.

Adicionado

  • Porta SMTP de submissão (smtp_listen, padrão 2525): EHLO → STARTTLS → AUTH PLAIN|LOGIN → MAIL/RCPT/DATA, com AUTH obrigatório antes do MAIL FROM. Autentica com a mesma credencial do canal HTTP. TLS com certificado montado ou auto-assinado gerado no boot.
  • Retenção: mensagem terminada fica consultável por retention_days (padrão 7) e depois sai do banco; o registro permanente é o ledger em arquivo.
  • Filtros de diagnóstico no GET /queue: por destinatário, message_id e cliente.

Notas

  • Não é MX de entrada: a porta SMTP é submissão autenticada, só para sair.
  • Recusa 550 quando o header From diverge do envelope — sem isso seria possível enviar mensagem que aparenta vir de domínio alheio.

[0.9.0] - 2026-08-01

Adicionado

  • Fila persistente: estado em banco embarcado (SQL em Rust puro, sem dependência externa) e corpo da mensagem em arquivo. A mensagem aceita sobrevive a restart e a crash.
  • Modo assíncrono, ligado pela presença de MXOUT_REDIS: a requisição responde 202 e a entrega sai no tick. Sem a variável, o comportamento síncrono de sempre.
  • POST /queue/tick (reentrante) e GET /queue, mais o subcomando mxout tick para o cron chamar. O tick é HTTP no loopback: o banco aceita um processo por vez.
  • Backoff persistente: 1min, 5min, 15min, 1h, 4h. Greylisting rejeita a primeira tentativa por prática — sem retry longo, entrega legítima se perdia.
  • Recuperação de crash: no boot, entregas presas em sending voltam para a fila.

Modificado

  • O retry deixou de ser em memória. Antes eram três tentativas em menos de um minuto e, falhando, a mensagem morria.

[0.8.0] - 2026-08-01

Adicionado

  • Clientes: registro de quem pode enviar, com credencial própria (cli_<id> + senha em bcrypt) e lista de domínios autorizados. Endpoints GET/POST /clients, GET/PATCH/DELETE /clients/{id}, POST /clients/{id}/rotate, PUT /clients/{id}/domains.

Modificado

  • O token de admin não envia mais. Os poderes foram separados: o token administra e a credencial de cliente envia. Antes era um segredo só para as duas coisas, e por isso ele não podia ser entregue a nenhuma aplicação — quem o recebesse poderia enviar por qualquer domínio.
  • A autorização de domínio roda antes de consultar a configuração, para a recusa não revelar quais domínios existem a quem não tem acesso a eles.
  • Autenticação falha com a mesma resposta e no mesmo tempo para credencial inexistente, senha errada e cliente desabilitado — a diferença enumeraria clientes.

[0.7.0] - 2026-07-31

Adicionado

  • DELETE /domains/{dominio}.

[0.6.1] - 2026-07-31

Adicionado

  • Ledger opcional em arquivo com rotação (ledger_file, ledger_max_bytes), para o histórico não morrer junto com o container no redeploy.

[0.6.0] - 2026-07-31

Adicionado

  • require_tls global e por domínio: com ele, destino que não anuncie STARTTLS vira erro temporário em vez de a mensagem sair em claro. É política do remetente — não confundir com MTA-STS, que é publicada pelo destinatário.

[0.5.0] - 2026-07-31

Adicionado

  • POST /send-raw: entrega o MIME já montado pela aplicação, preservando anexos, Reply-To, List-Unsubscribe e headers próprios. O POST /send remonta a mensagem a partir de campos e perde tudo isso.
  • Date e Message-ID garantidos antes de assinar: sem eles o DKIM não os cobre e a entregabilidade cai. Message-ID do cliente é preservado e vira a chave de correlação.
  • mx_override na configuração: entrega direta em host:porta, sem consultar DNS. Existe para teste ponta a ponta contra um sink local.

Corrigido

  • Corpo vazio produzia assinatura DKIM inválida: a canonicalização relaxed exige entrada nula, e estávamos gerando CRLF (regra do canon simple).
  • O d= da assinatura sai do header From, mas a chave usada é a do domínio do envelope. Divergindo, a assinatura não fechava no destino — agora a mensagem é recusada com 400 em vez de sair quebrada.
  • Header repetido no conjunto assinado passa a ser recusado: assinávamos a primeira ocorrência e o verificador usa a última (RFC 6376 §5.4.2).
  • Cadastro do primeiro domínio de uma instância falhava: o diretório de chaves era derivado de um domínio já existente e caía num caminho relativo inexistente. Novo campo kits_dir.

[0.4.0] - 2026-06-07

Adicionado

  • Gestão de domínios via API (atrás do X-Auth-Token): POST /domains (gera o kit DKIM e registra o domínio como pending), GET /domains e GET /domains/{dominio}/instructions.
  • Controle de rate limit: GET /ratelimit e PUT /ratelimit (cota por domínio, throttle de API e delay de envio).
  • IP allowlist: GET /allowlist e PUT /allowlist (lista vazia = aberta).
  • Freios anti-produção no POST /send: cota por domínio (hora e dia → 429), throttle de API por IP (→ 429), envio serializado e delay forçado por mensagem.

Modificado

  • Domínio só envia depois de validado: um domínio novo nasce pending e o GET /check o promove a active quando DKIM, SPF e DMARC passam. Domínio pending no POST /send retorna 403.
  • O mxout.json passou a ser gravável: a API atualiza domínios, limites e allowlist e persiste no próprio arquivo. Config antiga (sem status) carrega como active.

[0.3.0] - 2026-06-02

Adicionado

  • Validação de DNS embarcada: mxout check [dominio] (CLI) e GET /check / GET /check/{dominio} (HTTP, atrás do X-Auth-Token). Confere DKIM (chave publicada bate com a privada), SPF (IP do servidor autorizado) e DMARC (existe e bem-formado) de cada domínio remetente.
  • Campo opcional public_ip no mxout.json (usado pelo check para avaliar o SPF).

[0.2.2] - 2026-06-01

Corrigido

  • Chave pública DKIM publicada em SubjectPublicKeyInfo (SPKI), não PKCS#1 — corrige DKIM=FAIL em validadores estritos como o Gmail.

[0.2.1] - 2026-06-01

Adicionado

  • Subcomando mxout healthcheck (autoverificação HTTP) + HEALTHCHECK na imagem, para a imagem scratch ficar saudável sob o orquestrador (Runner honra o HC da imagem).

[0.2.0] - 2026-06-01

Adicionado

  • Ledger de eventos write-only em JSON no stdout (target mxout::ledger), correlacionado por message_id — mecanismo de controle e auditoria.
  • message_id próprio, gerado por requisição, presente no header Message-ID (assinado por DKIM), no ledger e na resposta HTTP.
  • Autenticação por MXOUT_AUTH_TOKEN (variável de ambiente), com comparação em tempo constante.
  • MXOUT_DNS para definir o resolver DNS explicitamente (útil em containers sem /etc/resolv.conf).
  • Limite de 5 MiB no corpo do POST /send.
  • Imagem Docker scratch (binário estático musl, ~6 MB, não-root).

Modificado

  • Token de autenticação saiu do mxout.json e passou para a variável de ambiente MXOUT_AUTH_TOKEN.
  • Dependências atualizadas (axum 0.8, hickory-resolver, rustls 0.23, mail-builder 0.4).

Corrigido

  • Date e Message-ID agora são sempre gerados antes da assinatura DKIM, garantindo headers estáveis e assinados.

[0.1.0] - 2026-06-01

Adicionado

  • Versão inicial: POST /send, assinatura DKIM RSA-SHA256 por domínio, resolução de MX, entrega SMTP com STARTTLS oportunístico e retry em memória.
By Borlot.com.br on 01/06/2026