Fila
Até a v0.8.0 o mxout era stateless: o retry vivia na memória, três tentativas em menos de um minuto, e falhando a mensagem morria. Como greylisting rejeita a primeira tentativa por prática, entrega legítima se perdia.
Desde a v0.9.0 existe fila persistente. A mensagem sobrevive a restart e a crash.
Os dois modos
Sem MXOUT_REDIS |
Com MXOUT_REDIS |
|
|---|---|---|
| Comportamento | entrega dentro da requisição | aceita, persiste e responde 202 |
| Resposta | 200 com o resultado real |
202 com "enfileirado" |
| Quando sai | na hora | no próximo tick |
A variável funciona como chave de modo. Para envio que não pode esperar — recuperação de senha, 2FA — o modo síncrono é o certo.
Onde o estado mora
Divisão deliberada:
banco embarcado → clientes, estado da entrega, tentativas, último erro
arquivo → o MIME cru, em <config>/spool/<id>.emlO banco é residente em memória. Estado é pequeno e cabe; corpo de mensagem, com teto de
5 MiB cada, encheria a RAM. O .eml é lido só na hora de entregar e apagado quando a
entrega termina.
A ordem de gravação é a garantia de corretude: o corpo é escrito e sincronizado
antes da linha de estado. Um crash entre as duas coisas deixa um .eml órfão, que a
varredura recolhe. A ordem inversa deixaria estado sem corpo — mensagem impossível de
entregar.
POST /queue/tick
Drena o que venceu. Exige o token de admin.
curl -X POST http://localhost:8080/queue/tick -H "X-Auth-Token: {TOKEN_ADMIN}"{"ok": true, "ocupado": false, "tentados": 2, "entregues": 2, "falhos": 0, "reagendados": 0}É reentrante: um tick disparado enquanto outro roda responde "ocupado": true e sai,
sem duplicar trabalho. Uma entrega lenta pode segurar o tick por minutos, e o agendador
não espera.
Na prática quem chama é o cron, pelo subcomando `mxout tick`:
* * * * * docker exec <container> /mxout tick >/dev/null 2>&1O tick é HTTP no loopback, e não um processo separado — o banco embarcado aceita um processo por vez, então quem drena é o próprio servidor, provocado de fora.
GET /queue
Consulta o estado das entregas. Exige o token de admin.
| Parâmetro | Efeito |
|---|---|
estado |
queued | sending | sent | failed |
rcpt |
busca parcial no destinatário |
message_id |
todas as entregas de uma mensagem |
client_id |
filtra por cliente |
limite |
teto de 500 |
{
"queue": [{
"pk": 12,
"message_id": "<1785619031.Ta4eiIKR@minhaempresa.com.br>",
"client_id": "cli_a3f9c1d2e4b6",
"envelope_from": "no-reply@minhaempresa.com.br",
"rcpt": "cliente@exemplo.com",
"estado": "failed",
"attempts": 5,
"next_attempt_at": 1785620000,
"last_error": "550 5.1.1 caixa inexistente",
"created_at": 1785619031
}]
}Horários em epoch (segundos) — é o formato do banco.
Retry e backoff
Falha temporária reagenda com espera crescente:
1min → 5min → 15min → 1h → 4hDepois da quinta tentativa, failed com o erro guardado. Não há reenvio manual: o mxout
retenta sozinho, e reenviar por cima criaria entrega dupla. O que dá para fazer é adiantar
a próxima drenagem com o tick.
Recuperação de crash: no boot, tudo que ficou preso em sending volta para queued.
Sem isso, um processo morto no meio de uma entrega deixaria a linha travada para sempre.
Retenção
Mensagem terminada (entregue ou falha) fica consultável por 7 dias e depois sai do banco. O registro permanente é o ledger em arquivo.
O teto existe porque o banco é residente em memória: histórico sem limite seria consumo
sem limite. O ajuste é o campo retention_days da configuração.