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 doMAIL 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_ide cliente.
Notas
- Não é MX de entrada: a porta SMTP é submissão autenticada, só para sair.
- Recusa
550quando o headerFromdiverge 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 responde202e a entrega sai no tick. Sem a variável, o comportamento síncrono de sempre. POST /queue/tick(reentrante) eGET /queue, mais o subcomandomxout tickpara 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
sendingvoltam 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. EndpointsGET/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_tlsglobal 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-Unsubscribee headers próprios. OPOST /sendremonta a mensagem a partir de campos e perde tudo isso.DateeMessage-IDgarantidos antes de assinar: sem eles o DKIM não os cobre e a entregabilidade cai.Message-IDdo cliente é preservado e vira a chave de correlação.mx_overridena 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 headerFrom, mas a chave usada é a do domínio do envelope. Divergindo, a assinatura não fechava no destino — agora a mensagem é recusada com400em 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 comopending),GET /domainseGET /domains/{dominio}/instructions. - Controle de rate limit:
GET /ratelimitePUT /ratelimit(cota por domínio, throttle de API e delay de envio). - IP allowlist:
GET /allowlistePUT /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
pendinge oGET /checko promove aactivequando DKIM, SPF e DMARC passam. DomíniopendingnoPOST /sendretorna403. - O
mxout.jsonpassou a ser gravável: a API atualiza domínios, limites e allowlist e persiste no próprio arquivo. Config antiga (semstatus) carrega comoactive.
[0.3.0] - 2026-06-02
Adicionado
- Validação de DNS embarcada:
mxout check [dominio](CLI) eGET /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_ipnomxout.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) +HEALTHCHECKna imagem, para a imagemscratchficar 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 pormessage_id— mecanismo de controle e auditoria. message_idpróprio, gerado por requisição, presente no headerMessage-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_DNSpara 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.jsone passou para a variável de ambienteMXOUT_AUTH_TOKEN. - Dependências atualizadas (axum 0.8, hickory-resolver, rustls 0.23, mail-builder 0.4).
Corrigido
DateeMessage-IDagora 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