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>.eml

O 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>&1

O 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 → 4h

Depois 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.

By Borlot.com.br on 02/08/2026