Voltar para Central de ajuda
FT-RETRY-WORKER· 1.14.0Integrações

Pipeline de mensagens — fluxos e cenários

Doc canônico do pipeline de mensagens com 7 cenários (sucesso, retry-success, retry-DLQ, retry-manual, 4xx não-retentável, cert-expirando, cancelar). Estados, classifier de erros, onde ver tudo.

Atualizado em 18/10/2026
Documento canônico de como uma mensagem caminha pelo Lefia, do recebimento até estado final. Cobre os 7 cenários possíveis e mostra os componentes envolvidos em cada um.

Visão geral do pipeline



[parceiro/SAP] → POST /api/mapeamentos/receive-message ou /api/webhook/[id]
   ↓
[insert messages com pipeline_status='received']
   ↓
[processMessage síncrono em src/lib/pipeline/process-message.ts]
   ├─ duplicate check (por message_type)
   ├─ map (se há mapping configurado)
   └─ send (se há connection destino)
        ├─ sucesso → pipeline_status='sent' / dest_status='200'
        └─ falha → pipeline_status='error' / dest_status_text=erro
             ↓
        [tryEnqueueRetry em src/lib/queue/retry-orchestrator.ts]
             ├─ retry_auto_enabled=false → fica em 'error' (usuário)
             ├─ erro não retentável → fica em 'error'
             ├─ retry_count<max → 'retry_pending' + job em job_queue
             └─ esgotou → 'dead_letter' (se DLQ on) ou 'error'


O worker /api/cron/process-queue puxa jobs prontos da job_queue (RPC dequeue_jobs com FOR UPDATE SKIP LOCKED) e re-executa processMessage pra cada um. Em nova falha, chama tryEnqueueRetry de novo. Se a tentativa agendada é interrompida (o worker cai no meio e o reaper fecha o job, ou a gravação fica sem resposta), ela conta como tentativa e a política decide de novo — a mensagem não fica em retry_pending sem job. A rede de segurança (requeue-stalled-messages, a cada 10 min) recolhe o retry órfão que ainda sobrar: retry_pending vencida há mais de 15 min, sem job, fora de lote de envio e da fila de trabalho → error com o registro na linha do tempo, e a política decide. A Idempotency-Key do envio (REST, OData e webhook) leva a revisão do documento (nexio-<mensagem>-r<instante da leitura>): o retry da mesma revisão repete a chave e o destino deduplica; a revisão nova do documento usa chave nova e não é descartada como duplicata. Resultado do envio incerto (timeout ou reset depois de enviar, 502/504, job do bridge já assumido, lote sem resposta): o destino pode ter recebido. A CONEXÃO de destino (REST ou webhook) escolhe, em "Quando o resultado do envio for incerto": reenviar com a chave de idempotência (padrão, o comportamento de sempre; arrisca duplicar se o destino não honra a chave — marque "O destino honra a Idempotency-Key" só quando confirmado), consultar o destino (REST: caminho, campo da resposta com a referência e, opcional, atraso de consistência; só conta como entregue com 2xx E a referência no corpo; sem prova vai para conferência) ou conferência humana (error com dest_status = delivery_uncertain, classe DELIVERY_UNCERTAIN, não retentável; reprocesso em massa e leitura completa não reenviam; reprocessar uma mensagem exige a confirmação explícita de reenvio). A conferência de "entregou e não gravou" usa como início da tentativa o maior entre o último job e messages.revision_at (a releitura do documento): a entrega da revisão anterior não conta. A exceção DEPOIS do 2xx do destino grava delivered_unrecorded na hora (não MAPPING_ERROR nem erro de envio retentável); se o sent já estava gravado, fica como está. Resposta que não chega (timeout, reset) segue retentável, protegida só pela chave de idempotência do conector. Se a tentativa interrompida registrou a entrega (message_deliveries sent depois do início dela) e só o desfecho não foi gravado, nada reenvia: error com dest_status = delivered_unrecorded (classe DELIVERED_UNRECORDED), para conferir no destino. Worker morto entre o HTTP e qualquer gravação: pelo menos uma vez, como received.

Estados de pipeline_status



StatusSignificadoPróximo passo
receivedMensagem entrou no banco; processMessage ainda não rodouPipeline executa
mappedField mapping aplicado, aguardando enviosendToDestination
sentEntregue ao destino com sucessoFinal
errorFalhou ou esgotou retries (sem DLQ)Humano clica reprocessar
retry_pendingFalhou mas retry automático agendadoWorker pega no run_after
dead_letterEsgotou retries com DLQ habilitadoAlerta + revisão manual em /admin/queue
cancelledHumano cancelou retry em vôoFinal
received_observedModo observability — recebeu mas não enviouFinal (modo monitor)


7 cenários de execução



Cenário A — Sucesso direto



Mensagem entra, mapeia, envia, destino retorna 200.

  • pipeline_status: received → mapped → sent
  • dest_status: "200"
  • Webhook event message.sent é disparado pra integrações de saída
  • Logs em message_logs step=sent status=success


Cenário B — Erro 5xx + retry automático ON + sucesso na 2ª tentativa



Integração com retry_auto_enabled=true, destino retorna 503.

  • 1ª tentativa: pipeline_status: received → error (dest='503')
  • tryEnqueueRetry classifica http_5xx (retryable), calcula nextRetryAt (ex: +60s exponential), atualiza:
- pipeline_status: 'retry_pending' - retry_count: 1 - next_retry_at: now+60s - last_retry_error_class: 'http_5xx'
  • Job inserido em job_queue com run_after=now+60s
  • Worker executa, processMessage funciona desta vez → pipeline_status: 'sent'


Cenário C — Erro 5xx + retry automático ON + esgota tentativas → DLQ



Mesmo cenário B, mas todas as N tentativas falham.

  • Tentativas 1..retry_max: retry_pending com backoff (30s, 60s, 120s, ...)
  • Após retry_max: tryEnqueueRetry retorna exhausted_dlq
  • Se retry_dead_letter=true: pipeline_status='dead_letter'
  • Senão: fica em 'error'
  • Cron /api/cron/dlq-alerts agrupa por integração + bucket horário, dispara Slack e email pra RESEND_NOTIFY_EMAIL
  • Aparece em /admin/queue aba "Dead-letter" com botões "Reprocessar" e "Cancelar"


Cenário D — Retry automático OFF (default) → fica parado pro usuário



Integração com retry_auto_enabled=false (o padrão de produto).

  • Falha → pipeline_status: 'error' (sem retry, sem job na fila)
  • Mensagem fica visível no monitor
  • Operador clica "Reprocessar" → /api/mapeamentos/reprocess-message reseta status e re-executa pipeline
  • Se reprocessar manual também falha, mesmo fluxo: ou volta pro usuário (auto OFF) ou enfileira retry (auto ON)


Cenário E — Erro 4xx (payload inválido) → não retenta nunca



Destino retorna 400/403/404/422 — problema do payload, não transiente.

  • tryEnqueueRetry classifica como http_4xx ou syntax
  • retryable=false — independente de retry_auto_enabled, não enfileira
  • Atualiza apenas last_retry_error_class
  • Mensagem fica em error pro usuário corrigir o mapping/payload
  • 401 é exceção: classificado como http_401 retryable (token pode ter renovado)
  • 429 também: classificado como rate_limited, parseia Retry-After do response


Cenário F — Certificado digital expirando (alerta proativo)



Não é fluxo de mensagem, mas afeta integrações que usam mTLS (SEFAZ, etc).

  • Cron /api/cron/cert-expiry-alerts roda 07:00 UTC
  • Varre connections.certificates.client.not_after
  • Buckets: 30, 14, 7, 1 e 0 dias
  • 1 alerta por bucket via Slack (config SLACK_ALERTS_WEBHOOK_URL)
  • UI: integration-card mostra badge "Cert vence em Xd" amarelo (≤30d) ou vermelho (≤7d)
  • Renovação automática: trigger connections_cert_expiry_alerts_reset apaga rows antigas quando not_after muda — buckets resetam sozinhos


Cenário G — Operador cancela retry em vôo



Mensagem em retry_pending (ex: retry_count=2 of 5), usuário decide parar.

  • Em /admin/queue aba "Aguardando" ou "Dead-letter", botão X (cancelar)
  • POST /api/admin/messages/[id]/cancel-retry:
- Apaga jobs pendentes de job_queue (started_at IS NULL)
- pipeline_status: 'cancelled' - next_retry_at: null - Audit event message.retry_cancelled registrado
  • Worker eventual nunca executa o job (porque já foi removido)


Cenário H — Modo observability (read-only)



Integração com mode='observability' — default pra novos tenants.

  • Mensagem chega via webhook/API → processMessage roda normal
  • Mapping aplica (se configurado) → pipeline_status: 'mapped'
  • Antes de chamar sendToDestination, helper skipSend intercepta:
- Atualiza pipeline_status: 'received_observed' - dest_status: 'skipped_observability' - dest_status_text: 'Modo observability — envio ao destino bloqueado' - Log step=observability registrado
- Não chama sendData — destino do cliente fica intocado
  • tryEnqueueRetry no-op (não há erro pra retentar; mensagem foi observada com sucesso)
  • Card da integração mostra badge READ-ONLY azul persistente


Use pra: rollout enterprise progressivo. Cliente roda Lefia em paralelo com iPaaS atual sem risco de duplicar dado. Quando confiar, alterna pra orchestration no edit da integração — UI esconde retry policy enquanto observability (irrelevante).

Cenário I — Reentrega exata bloqueada (idempotency)



Parceiro reenvia exatamente o mesmo payload (timeout, falha de rede do lado dele).

Camadas de proteção (do mais cedo pro mais tarde):

  1. Header HTTP Idempotency-Key — se cliente passou (Stripe-style)
- Sanitizado em sanitizeIdempotencyKey (RFC: ASCII printable, ≤255 chars)
- UNIQUE INDEX (tenant_id, integration_id, idempotency_key) - Bate → retorna mensagem original sem reinserir, response com deduplicated: true, deduplicated_by: 'idempotency_key'
  1. Fingerprint computada — sempre, mesmo sem header
- SHA-256 de (message_type_id + duplicate_key_fields normalizados + hash payload canônico) - Reaproveita os duplicate_key_fields da UI "Tratamento de Duplicatas"
- UNIQUE INDEX (tenant_id, integration_id, fingerprint) - Bate → retorna original com deduplicated_by: 'fingerprint'
  1. Race-condition fallback — se 2 requests concorrentes passaram pelo pré-check
- INSERT cai em 23505 (unique violation) — handler busca o winner e devolve com deduplicated_by: 'race_winner'

Distinção importante: idempotency NÃO substitui a UI "Tratamento de Duplicatas" (duplicate_handling por message_type). Eles cobrem cenários diferentes:
  • Idempotency: mesmo payload byte-a-byte (reentrega exata)
  • duplicate_handling: mesmas chaves de negócio mas conteúdo distinto (ex: 2 PO-12345 com itens diferentes — operador decide accept/reject/overwrite)


Idempotency atua antes do INSERT; duplicate_handling atua dentro do processMessage depois de inserir.

Onde ver o que



O queOnde
Mensagens em retry, jobs pendentes, DLQ/admin/queue (super_admin)
Toggle de retry automático por integraçãoEdit da integração → seção "Política de Retry"
Janela de horário (afeta retry)Edit da integração → seção "Agendamento"
Histórico de logs por mensagem/mapeamentos/integracao/[id]
Erros do Lefia em si (não de integração)/admin/errors + Sentry
Alertas Slackcanal configurado em SLACK_ALERTS_WEBHOOK_URL
Email de DLQRESEND_NOTIFY_EMAIL


Plataforma SEFAZ — caso especial



Em "Plataforma" do form de criar/editar integração, escolher SEFAZ automaticamente:
  • Troca o protocolo pra SOAP (webservice nativo)
  • Mostra hint "SEFAZ exige mTLS — anexe o certificado digital A1 (.pfx/.p12)"
  • Campos clientCertPfx (file upload) e clientCertPassword ficam visíveis
  • Server-side: parseia PKCS#12, valida senha, encripta blob+senha (AES-256-GCM), grava em connections.certificates


Erros mapeados pelo classifier



src/lib/integrations/retry-policy.ts → classifyError():

SinalClasseRetentável?
HTTP 5xxhttp_5xxsim
HTTP 429 + Retry-Afterrate_limitedsim (respeita header)
HTTP 401http_401sim (uma vez, token pode renovar)
HTTP 422syntaxNÃO (payload/schema inválido)
HTTP 4xx outroshttp_4xxNÃO
ETIMEDOUT, msg "timeout"timeoutsim
ECONNRESET, ECONNREFUSED, ENOTFOUNDconnectionsim
msg com "schema/parse/invalid xml"syntaxNÃO
outrosunknownsim (default seguro)


Privacidade no retry



  • Cert digital: blob e senha sempre encriptados em repouso (src/lib/crypto.ts AES-256-GCM)
  • Payload da mensagem: redaction de PII aplicada antes de qualquer chamada à IA do Lefia (src/lib/ai/redaction.ts) — vide help article pii-redaction-llm
  • Logs de retry: apenas classe do erro + dest_status_text (texto do destino), sem credenciais


Limitações conhecidas (deste commit)



  • Schedule do worker /api/cron/process-queue é diário em Vercel Hobby. Pra retries com delay < 24h o operador precisa cron externo (cron-job.org bate com Bearer CRON_SECRET) ou subir pra Vercel Pro (cron a cada 1min).
  • Idempotency em mensagens de integração não é universal — backlog #60.
  • A3 (token físico) não suportado — backlog (precisa Lefia Agent on-prem).
  • Validação CRL/OCSP/cadeia não é feita no upload do cert (delegada à contraparte).
  • Logging não-estruturado (Pino) — backlog #59.


Limite de taxa do destino (HTTP 429)



Quando o destino recusa por excesso de requisições, a mensagem fica com a categoria Limite de taxa do destino. O arquivo está certo: o retry automático tenta de novo e espera o tempo que o destino pediu no cabeçalho Retry-After (o mesmo vale para o 503 com Retry-After), até o intervalo máximo de retry configurado na integração. Se o destino pedir mais do que isso, a tentativa sai no intervalo máximo e a linha do tempo da mensagem avisa. Se continuar, aumente o intervalo das tentativas ou reduza as execuções em paralelo da integração.

Tentativas com o destino fora do ar



Na tela da mensagem, Tentativas mostra quantas vezes a entrega foi tentada dentro do limite de tentativas da integração e, entre parênteses, os reenvios feitos enquanto o destino estava fora do ar, que não contam nesse limite (por exemplo, "3 tentativas (+5 sem contar durante queda do destino)"). Enquanto o destino estiver fora, a tela diz desde quando e até quando as novas tentativas não contam. Reprocessar a mensagem zera essa contagem.

Histórico de versões

  • 1.14.0

    Destino fora do ar: a mensagem enviada sozinha (fora do lote) passa a ter a mesma proteção do lote. Quando é certo que nada saiu — falha no login do destino, conexão que nem abriu, ou erro 5xx com o destino confirmado fora pela sonda — ela é reenviada por tempo, sem gastar tentativa, até a janela da conexão ("Destino fora do ar: tentar por até (minutos)", padrão 60), e a conexão pausa para as outras mensagens esperarem. Tempo esgotado depois de enviar (pode ter chegado) e o erro de um destino que está respondendo continuam com o número de tentativas da integração. Antes essa regra existia só atrás de uma chave desligada.

    21/10/2026 · updated

  • 1.13.0

    Reenvio automático do lote que falhou porque o destino ficou fora do ar por mais que a janela de envio em lote. Para a conexão que declara que o destino honra a Idempotency-Key, quando o destino volta e um envio dá certo, o lote que tinha falhado de vez pela janela volta sozinho, um por vez, o mais antigo primeiro, com a mesma chave de idempotência (o destino que a honra não duplica) e uma única vez por lote. Não voltam sozinhos: lote que falhou por erro de conteúdo ou lote envenenado, resultado incerto que foi para conferência, integração com retry desligado, em observabilidade ou com tentativas zero, e conexão que não declarou a idempotência (esses seguem pelo Reprocessar lote). O Reprocessar lote continua valendo e cancela o reenvio automático daquele lote.

    18/10/2026 · updated

  • 1.12.0

    Novas tentativas e reprocesso mais seguros contra envio em dobro. Reprocessar uma mensagem (ou um lote) enquanto a entrega dela está no meio do envio agora espera essa entrega terminar, em vez de mandar de novo. A nova tentativa automática também espera a fila de trabalho que ainda vai processar a mensagem. Na criação de documentos no SAP (OData), uma falha no login do destino passa a ser tratada como "nada foi enviado": a mensagem volta a tentar sozinha em vez de parar para conferência. A tela da mensagem mostra as tentativas contadas e, à parte, os reenvios feitos enquanto o destino estava fora do ar, que não contam no limite da integração.

    18/10/2026 · updated

  • 1.11.1

    Fim do envio em dobro entre o envio imediato e a fila. Na entrega enfileirada, o Lefia tenta enviar a mensagem logo depois de responder ao remetente, e a fila fica como rede de segurança. Antes, se o envio imediato demorasse (por exemplo, um login lento no destino), a fila podia enviar a mesma mensagem ao mesmo tempo e o parceiro recebia duas vezes. Agora o envio imediato só sai depois de assumir a entrega na fila; se a fila já assumiu, ele desiste. Se o envio imediato for interrompido, a fila retoma a mesma entrega depois de 15 minutos. Uma falha do destino no envio imediato segue a política de novas tentativas da integração, como na fila.

    17/10/2026 · updated

  • 1.11.0

    Destino fora do ar também no LOGIN e na mensagem enviada sozinha. Quando o login do destino (usuário e senha que devolvem um token, ou a troca de token OAuth) responde com erro passageiro — 5xx (como o 508 da plataforma), 408, 425, 429, tempo esgotado ou falha de rede/DNS — a entrega trata como destino fora do ar: o lote espera a pausa automática e é reenviado sem gastar tentativa até a janela da conexão passar ("Destino fora do ar: tentar por até (minutos)", padrão 60). Credencial recusada no login (401, 403 e os outros 4xx do login) continua erro, sem nova tentativa, na categoria Autenticação. Para a mensagem que vai sozinha (fora do lote) a mesma regra existe atrás de uma chave por ambiente, desligada por enquanto (toda tentativa avulsa conta); quando ligada, vale quando é certo que nada saiu (falha no login, conexão que nem abriu, ou o 5xx com o destino confirmado fora pela sonda): ela é reenviada por tempo sem gastar tentativa e a conexão pausa para as outras esperarem; tempo esgotado depois de enviar (pode ter chegado) e o erro 500 de um destino que está respondendo — ou de um conector sem sonda — continuam com o número de tentativas da integração. O registro da entrega guarda os cabeçalhos de diagnóstico da resposta de erro (ex.: x-vercel-error), que dizem se o erro veio da plataforma do destino ou da aplicação.

    17/10/2026 · updated

  • 1.10.2

    Correções na entrega: o retry automático não agenda mais uma mensagem que tem uma entrega enfileirada aberta (a checagem agora é refeita no banco, sob a trava da mensagem, e não só antes de chamá-lo), o que evita enviar a mesma mensagem duas vezes. E a sonda do destino com certificado do cliente (mTLS) deixa de dizer "destino fora" quando o servidor derruba a conexão depois do aperto de mão, que é como o TLS 1.3 pode recusar o certificado: o aviso passa a apontar o certificado e o envio real decide.

    17/10/2026 · updated

  • 1.10.1

    Correção na entrega em lote: quando a gravação que põe a mensagem na espera do lote falha (por exemplo, por uma trava momentânea do banco), a mensagem não fica mais parada em "Recebida" sem ninguém cuidar dela — a decisão é refeita, ou a mensagem vai para erro com nova tentativa automática (marcada como falha do Lefia, não do destino). O mesmo vale para a entrega enfileirada e a adiada pela contrapressão, e a entrega enfileirada nunca envia o documento de origem sem mapear quando a integração tem mapeamento: sem o mapeado gravado, a mensagem vai para erro e é decidida de novo. E a sonda que testa o destino depois de uma pausa passou a entender destinos atrás de API gateway: 401, 403, 404 e 405 do gateway e o redirecionamento para a página de login contam como destino no ar (o redirecionamento não é seguido); 502, 503 e 504 do gateway contam como destino fora. Problema de certificado ou de TLS não é tratado como queda (o envio real decide e mostra o erro), e a conexão que cai no meio do aperto de mão TLS conta como destino fora.

    16/10/2026 · updated

  • 1.10.0

    Envio em lote resiliente à queda do destino. Com o destino fora do ar (erro 5xx, tempo esgotado, conexão recusada), o lote não gasta tentativa: ele espera e é reenviado até a janela da conexão passar (padrão 60 min, campo novo "Destino fora do ar: tentar por até (minutos)"). A conexão pausa já no primeiro lote em falha; depois da pausa, uma sonda leve (consulta sem envio) testa o destino e o lote só sai inteiro quando o destino responde. Erro de conteúdo (4xx) e o 5xx de um destino que está respondendo continuam com o número de tentativas da integração. Lote que falha de vez (a janela passou) volta pelo Reprocessar lote, com a mesma chave de idempotência; o orçamento de tentativas recomeça. O lote guarda os cabeçalhos de diagnóstico da resposta de erro (ex.: x-vercel-error), que dizem se o erro veio da plataforma do destino ou da aplicação.

    15/10/2026 · updated

  • 1.9.1

    Pausa automática do destino que cai, revisada: só pausa quando lotes DIFERENTES da conexão falham (a partir de 2) por indisponibilidade do destino (erro 5xx, tempo esgotado, conexão recusada ou DNS), sem nenhum envio bom no meio. Um lote só com erro repetido (conteúdo inválido) não para a conexão. A tela, o aviso de pausa longa e o texto do adiamento passam a dizer que a pausa é automática, não um pedido do destino.

    14/10/2026 · updated

  • 1.9.0

    Envio em lote: quando o destino devolve erro 5xx várias vezes seguidas (sem pedir espera), a conexão faz uma pausa automática crescente (30 s até 5 min, respeitando o Retry-After do destino) e retoma sozinha; os lotes esperam sem gastar tentativa e saem com a mesma chave de idempotência (nada duplica). O reenvio de um lote que falhou com 5xx nunca espera menos de 30 s.

    14/10/2026 · updated

  • 1.8.0

    Correção do envio em lote: um lote aceito e confirmado pelo destino não deixa mais mensagens com o status "enviando". O lote só é montado com mensagens cujo processamento terminou, o envio e a confirmação gravam o lote e as mensagens juntos, e uma verificação periódica acerta qualquer lote cujo estado diverge do das mensagens (inclusive agendando a consulta da confirmação que faltou).

    10/10/2026 · updated

  • 1.7.0

    Correção: em destinos que confirmam a entrega por consulta, uma mensagem enviada cuja consulta de confirmação não chegou a ser agendada (por exemplo, a fila ficou indisponível no instante do envio) deixa de ficar aguardando confirmação para sempre. Uma verificação periódica agenda a consulta que faltou, no máximo uma vez a cada 30 minutos, e as mensagens que dependiam dela seguem quando o destino confirma.

    10/10/2026 · updated

  • 1.6.0

    Refinamento da opção "consultar o destino" quando o resultado do envio é incerto: se o documento já foi entregue antes numa versão anterior, a consulta não é usada (iria achar a versão antiga) e a mensagem vai para conferência; a mensagem já fica marcada para conferência antes de esperar a resposta do destino, então nada é reenviado se o processo for interrompido; e conexões de tipos que não usam chave de idempotência, como o RFC, não oferecem mais o reenvio automático nesse caso.

    10/10/2026 · updated

  • 1.5.0

    Correção: quando um documento é alterado na origem e reenviado, destinos que respeitam a chave de idempotência (Idempotency-Key) não descartam mais a versão nova como se fosse repetição da anterior. A chave agora muda a cada versão do documento e continua a mesma nos reenvios da mesma versão, então a proteção contra envio duplicado segue valendo.

    10/10/2026 · updated

  • 1.4.0

    Nova opção em cada conexão de destino REST ou webhook: "Quando o resultado do envio for incerto (timeout depois de enviar)". Quando o envio termina sem resposta (tempo esgotado ou conexão derrubada depois de enviar, erro 502 ou 504 do gateway, job do bridge SAP já assumido, lote sem resposta), a conexão escolhe entre reenviar com a chave de idempotência (o padrão, que é o comportamento de sempre), consultar o destino para saber se o documento chegou, ou mandar para conferência humana. A consulta só confirma a entrega com resposta de sucesso que traga a referência da mensagem. Nada muda nas conexões existentes até alguém escolher outra opção. A tela explica o risco de cada escolha.

    10/10/2026 · updated

  • 1.3.0

    Correção de segurança de entrega: quando o destino confirma o recebimento (resposta 2xx) e depois o sistema não consegue gravar o resultado (por exemplo, o banco fica indisponível por um instante), a mensagem agora vai para conferência — "Entregue ao destino sem o desfecho gravado" — em vez de ser marcada como erro e reenviada sozinha, o que duplicaria o documento no destino. Quando a resposta do destino não chega (tempo esgotado, conexão derrubada), o reenvio continua como antes, protegido pela chave de idempotência do destino. Também corrigido: quando o documento é alterado na origem e a nova versão falha antes de ser enviada, o reenvio automático segue normalmente; a entrega da versão anterior não faz a mensagem ir para conferência por engano.

    10/10/2026 · updated

  • 1.2.0

    O worker da fila deixa de processar um número fixo de jobs por ciclo. Ele trabalha em rodadas enquanto houver trabalho pronto, tempo sobrando no ciclo e nenhum sinal de freio (jobs adiados ou falhando em massa). As mensagens retidas por dependência e as recolhidas pela rede de segurança voltam a andar na velocidade do ciclo, não a 10 por minuto, sem aumentar a concorrência contra o destino.

    10/10/2026 · updated

  • 1.1.0

    Correção: uma mensagem com nova tentativa agendada não fica mais parada em "aguardando retry" quando essa tentativa é interrompida (por exemplo, o processamento cai no meio). A tentativa interrompida conta como tentativa e a mensagem segue a política de retry da integração — nova tentativa, fila de mensagens mortas ou erro —, e uma verificação periódica recolhe a que ainda assim ficar sem tentativa agendada, com o registro na linha do tempo da mensagem. Mensagens de um lote de envio continuam com o retry do próprio lote. Se a tentativa interrompida chegou a entregar ao destino e só o desfecho não foi gravado, ela não é reenviada: a mensagem fica em erro com o aviso "entregue ao destino, mas o desfecho não foi gravado", para conferir no destino e reprocessar só se faltar.

    09/10/2026 · updated

  • 1.0.0

    Retry worker com fila durável em Postgres. Default retry MANUAL; integração liga "retry automático" explicitamente. Classifier 5xx/timeout/conn retentáveis vs 4xx/syntax não. Backoff fixed/linear/exp com cap. Janela horária respeitada. DLQ com alerta Slack+email agregado por integração+hour_bucket. UI /admin/queue. Cancel-retry com audit. SEFAZ→SOAP default + hint mTLS no form. 7 cenários documentados em help.

    06/05/2026 · new