ediverse Explore the platform

Spotlight PEPPOL BIS Billing 3.0 The EU e-invoicing mandate is here — France Sept 2026, Belgium Jan 2026, Germany 2025.

Dead Letter Channel

When a message cannot be processed — broken syntax, unknown format, failed business validation, exhausted retries — it must go somewhere. The Dead Letter Channel is that canonical somewhere. Without it, messages are lost in silence and incidents only surface at D+30 when the partner calls.

Problem

In an EDI pipeline, several things can make a message untreatable: the envelope is corrupted and the EDIFACT parser refuses, the partner sent an unexpected message type, business validation (unrecognised VAT, unknown GLN) blocks integration, the allowed retry count is exhausted. Without an explicit channel for these cases, two pathologies: (a) the message is silently dropped and the incident only surfaces at D+30, or (b) the pipeline loops indefinitely on the same message and blocks every following one.

Forces

  • Non-loss guarantee. A received message must have an observable destiny, even if that destiny is "human review needed". Silence is unacceptable.
  • Pipeline isolation. A poison message must not block processing of the following ones.
  • Failure context. For an operator to diagnose, the DLC message must carry the reason, the failure stage, the timestamp, and the original payload.
  • Replay possible. Once the problem is fixed (reference updated, parser patched), it must be easy to replay the DLC message into the normal pipeline.

Solution

EIP §119 (Hohpe & Woolf, 2003) models the Dead Letter Channel as a special channel, distinct from the normal pipeline, where unacceptable messages are deposited with enriched error metadata. Every pipeline consumer must have a DLC outlet: parser, validator, mapper, integrator. The DLC is consumed by an operations system (console, alerts, dashboards), not by the business pipeline.

plaintext topology.txt
ORDERS              ┌──────────────┐
   ──────────────────▶│  Translator  │ ──▶ canonical
                       └──────┬───────┘
                              │ parse error

                       ┌──────────────┐
                       │   DLC        │
                       │  /dlq/orders │
                       └──────┬───────┘


                       human operator
                       (replay / discard / fix)

Topology

Three variants:

  • Single DLC. A single dlq.global queue for the whole pipeline. Simple, but hard to monitor by error type.
  • DLC per stage. One queue per step: dlq.parse, dlq.validate, dlq.integrate. Lets each team monitor its domain.
  • DLC per partner. One queue per EDI partner — useful for ops to escalate to the right contact on a partner error spike.

In practice, the three are often combined: one queue per partner with sub-routing by stage, and a global dashboard that aggregates.

EDI implementation

Typical cases that go to DLC:

  • Broken syntax. An ORDERS with missing UNH, an ISA with truncated field, invalid XML. Parser refuses → immediate DLC, and ideally a negative CONTRL (action 7) emitted to the sender.
  • Unknown format. The Normalizer fails to detect the format. DLC + alert.
  • Unresolved identifier. A GLN absent from the partner database. DLC with hint "add partner or reject".
  • Failed business validation. Invalid VAT, quantity/price mismatch, unsupported currency. DLC with detail of violated rules.
  • Exhausted retries. The pipeline tried N times to integrate into the ERP; the ERP keeps refusing. DLC instead of infinite blocking.

Mandatory metadata

A message in DLC must carry at minimum:

  • originalChannel — where the message came from.
  • failedAt — failure timestamp.
  • attemptCount — how many attempts were made.
  • lastError — type, code, human-readable message, optional stack.
  • transport metadata — sender, receiver, interchange ref, size, hash.
  • payloadRef — pointer to the original payload stored in archive (see the Claim Check pattern).
json dlq-message.json
{
  "dlcVersion": "1",
  "originalChannel": "orders.in",
  "errorChannel": "orders.dlq",
  "failedAt": "2026-05-14T15:45:33Z",
  "attemptCount": 5,
  "lastError": {
    "stage": "parser",
    "type": "EDIFACT_SYNTAX",
    "code": "UNH-MISSING",
    "message": "UNH header not found after UNB",
    "stack": "..."
  },
  "metadata": {
    "transport": "AS2",
    "from": "ZZZSENDER",
    "to": "ZZZRECEIVER",
    "interchangeRef": "ORD202605140001",
    "size": 2487,
    "sha256": "9a4b1c2..."
  },
  "payloadRef": "s3://ediverse-archive/dlq/2026/05/14/ord-202605140001.edi"
}

Anti-patterns

  • Log + silence. Logging the error and dropping the message is not a Dead Letter Channel — it is a loss. The DLC must be inspectable and replayable.
  • Infinite retry. Without a retry cap, a poison message blocks the pipeline forever. Always switch to DLC after N attempts.
  • DLC without monitoring. A DLC queue nobody watches is useless. Always alert on size > 0 within a window.
  • No replay mechanism. If each message must be reconstructed by hand to be replayed, the operator will spend the day retyping UNB segments. Provide a replay tool that reads DLC and republishes to the original queue.
  • Shared DLC between prod and test. Mixing environments in DLC is a recipe for confusion. Always silo.
  • Retry & backoff — the policy that decides when to switch to DLC.
  • Exception flow — the escalation matrix that consumes the DLC.
  • Claim Check — to keep the original payload without loading the queue.

Sources