Ledger — mecanismo de controle
O ledger é o registro auditável de tudo que acontece com cada e-mail no mxout. Como o mxout é stateless (sem banco de dados, sem fila persistida), o ledger é a única fonte de verdade sobre o destino de cada mensagem.
Por que o ledger existe
O mxout não armazena estado entre requisições. Para saber se um e-mail foi entregue, adiado ou falhou, é preciso observar os eventos emitidos em tempo real. O ledger fornece essa trilha de auditoria sem introduzir dependências externas.
Formato
O mxout emite uma linha de log JSON por evento no stdout, no tracing target mxout::ledger.
Cada linha segue o envelope do tracing-subscriber em formato JSON:
{
"timestamp": "2026-06-01T14:23:05.412Z",
"level": "INFO",
"target": "mxout::ledger",
"fields": {
"ledger": "{\"event\":\"delivered\",\"message_id\":\"abc123@mail.exemplo.com\",\"rcpt\":\"destino@outro.com\",\"host\":\"mx.outro.com\",\"attempt\":1,\"tls\":true,\"smtp_response\":\"250 OK\"}"
}
}O campo fields.ledger é uma string JSON contendo o evento. Um log shipper filtra por target == "mxout::ledger" e faz parse do campo ledger.
Importante: o mxout nunca lê o ledger de volta. Ele é write-only por design.
Os 8 tipos de evento
event |
Quando é emitido | Campos presentes |
|---|---|---|
received |
Requisição aceita na borda HTTP | message_id, from, from_domain, rcpt_count, subject_len, body_bytes |
rejected |
Requisição recusada (auth, validação, domínio sem kit) | message_id, reason, http_status |
signed |
Assinatura DKIM aplicada à mensagem | message_id, domain, selector |
mx_resolved |
MX do destinatário resolvido via DNS | message_id, rcpt, mx_hosts (lista) |
attempt |
Início de uma tentativa de entrega SMTP | message_id, rcpt, host, attempt (base 1) |
delivered |
Mensagem aceita pelo servidor remoto | message_id, rcpt, host, attempt, tls, smtp_response |
deferred |
Falha temporária; haverá nova tentativa | message_id, rcpt, host, tls, error, next_wait_s |
failed |
Desistiu após esgotar tentativas ou 5xx permanente | message_id, rcpt, permanent, error |
Nos eventos rejected, quando ainda não existe um envio em andamento, message_id é "-".
Sequência típica de um envio bem-sucedido
Os eventos abaixo usam o mesmo message_id, permitindo correlacionar todo o ciclo:
{"event":"received","message_id":"abc123@mail.exemplo.com","from":"remetente@exemplo.com","from_domain":"exemplo.com","rcpt_count":1,"subject_len":32,"body_bytes":512}
{"event":"signed","message_id":"abc123@mail.exemplo.com","domain":"exemplo.com","selector":"mail"}
{"event":"mx_resolved","message_id":"abc123@mail.exemplo.com","rcpt":"destino@outro.com","mx_hosts":["mx1.outro.com","mx2.outro.com"]}
{"event":"attempt","message_id":"abc123@mail.exemplo.com","rcpt":"destino@outro.com","host":"mx1.outro.com","attempt":1}
{"event":"delivered","message_id":"abc123@mail.exemplo.com","rcpt":"destino@outro.com","host":"mx1.outro.com","attempt":1,"tls":true,"smtp_response":"250 OK"}Cada linha acima é o valor do campo fields.ledger após parse. O campo timestamp e demais campos do envelope ficam na linha externa.
Como consumir
Acompanhar em tempo real com Docker
docker logs -f mxout | grep 'mxout::ledger'Extrair e parsear o campo ledger com jq
docker logs mxout \
| jq -c 'select(.target == "mxout::ledger") | .fields.ledger | fromjson'Filtrar só falhas
docker logs mxout \
| jq -c 'select(.target == "mxout::ledger") | .fields.ledger | fromjson | select(.event == "failed")'Integrar com log shipper
Configure o shipper (Fluent Bit, Vector, Promtail, etc.) para:
- Coletar stdout do container
mxout. - Filtrar linhas onde
target == "mxout::ledger". - Fazer parse do campo
fields.ledgercomo JSON aninhado. - Indexar os campos do evento (
message_id,event,rcpt, etc.) para busca.
Correlação por message_id
O message_id é gerado pelo mxout no momento em que a requisição é aceita. Ele aparece:
- No header
Message-IDdo e-mail entregue. - No corpo da resposta HTTP do POST /send.
- Em todos os eventos do ledger referentes a esse envio.
Use message_id para rastrear o ciclo completo de uma mensagem, do received ao delivered ou failed.
Verbosidade com RUST_LOG
A variável de ambiente RUST_LOG controla o nível de log do mxout. O valor padrão é info.
Os eventos do ledger são emitidos no nível info. Para ver apenas o ledger sem outros logs:
RUST_LOG=mxout::ledger=infoPara mais detalhes internos do mxout (debug de DNS, TLS, SMTP):
RUST_LOG=mxout=debug