Message History
Every message carries the trace of its own journey. When looking for why an INVOIC arrives late or why an ORDERS is rejected, you open the embedded history: list of traversed components, versions, timestamps. The cornerstone of distributed debugging.
Problem
A modern EDI pipeline traverses around 20 components: AS2 gateway, sniffer, parser, validator, enricher, translator, router, ERP adapter, etc. Each has its version, configuration, execution node. When something goes wrong — rejected invoice, abnormal latency, bad translation — you must be able to reconstruct the message's exact path: which components touched it, in what order, at which version, on which node. Without Message History, the investigation turns into a hunt across scattered logs. With it, it is readable at a glance inside the message itself.
Forces
- Self-documentation. The message carries its own path proof; no need to correlate 20 log files.
- Auditability. For fiscal flows, proving who touched what is useful during audits.
- Debugging aid. When a message reproduces a bug, the history points directly to the guilty version.
- Message bloat. Each component adds ~200 bytes; on 20 components = ~4 KB. Acceptable for typical EDI messages (KB-MB), externalise for very small messages.
- GDPR. The history can contain personal data (who processed?). Do not expose to partners without masking.
Solution
EIP §551 (Hohpe & Woolf, 2003) defines Message History as a message header that grows with each component traversed. The contract is simple: each component touching the message appends an entry at the end of the list, with at minimum: its name, version, host, timestamp, action performed. It never removes an existing entry. At pipeline end (or in incident), the list is a faithful image of the path.
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ A │──▶│ B │──▶│ C │──▶│ D │
└────────┘ └────────┘ └────────┘ └────────┘
│ │ │ │
│ │ │ ▼
│ │ │ append { D, ts, version }
│ │ ▼ ────────────────────────
│ │ append { C, ts, version }
│ ▼ ────────────────────────
│ append { B, ts, version }
▼ ────────────────────────
append { A, ts, version }
────────────────────────
The message carries a cumulative list; at the end you can read
who touched it, in what order, when. EDI implementation
Three concrete uses:
{
"messageId": "01HVF8K3X1ABCDE0123456789",
"messageType": "INVOIC",
"messageHistory": [
{
"component": "as2-gateway",
"version": "2.4.1",
"host": "edi-gw-prod-02",
"ts": "2026-05-14T12:00:00.123Z",
"action": "received-from-partner",
"details": { "as2From": "SUPPLIER_GLN", "as2To": "BUYER_GLN" }
},
{
"component": "parser-edifact",
"version": "1.7.0",
"host": "edi-parse-prod-05",
"ts": "2026-05-14T12:00:00.842Z",
"action": "parsed",
"details": { "format": "EDIFACT D96A", "sizeBytes": 4218 }
},
{
"component": "validator-schema",
"version": "1.7.0",
"host": "edi-valid-prod-03",
"ts": "2026-05-14T12:00:01.012Z",
"action": "validated",
"details": { "rulesPassed": 47, "rulesWarned": 1 }
},
{
"component": "translator-canonical",
"version": "3.0.2",
"host": "edi-trans-prod-01",
"ts": "2026-05-14T12:00:01.247Z",
"action": "translated-to-canonical"
},
{
"component": "router-outbound",
"version": "2.1.0",
"host": "edi-rout-prod-04",
"ts": "2026-05-14T12:00:01.318Z",
"action": "routed",
"details": { "destination": "erp.sap.invoice.in" }
}
]
} - CONTRL/997 acknowledgement chain. For each received ack (CONTRL, 997, APERAK), record into the original message's history: "at 12:00:05Z, CONTRL+1 received from partner 5798000000122". The history tells the whole ack cycle.
- Latency diagnosis. An INVOIC takes 3 minutes to land. The history shows the validator took 2m45 at 12:00:01 — bug or backpressure. Immediate trace, no need to correlate 5 systems.
- Fiscal audit. "Can you prove this invoice was emitted without modification?" The history lists each transformation and its author; combined with before/after hashes, this is integrity proof.
- Loop detection. If the same component
appears several times (e.g.
router-outboundtwice), there is a cycle. The pattern detects it automatically.
Articulation with W3C TraceContext
In 2026, you do not write a Message History from scratch; you align with W3C TraceContext (traceparent + tracestate, 2020) so that OpenTelemetry / Jaeger / Tempo / Datadog can correlate traces. Message History serves the business layer (who = which component did what), TraceContext serves the network layer (cross-span correlation). Both coexist in the message's canonical envelope.
Anti-patterns
- History that overwrites. A component replacing the history instead of enriching it destroys the pattern's value. Append-only, never replaced.
- No-version history. Tracking "went through validator" without saying which version loses half the debugging value.
- Too verbose. Including full payload at every step multiplies message size 20×. Keep symbolic, delegate detail to logs.
- History exposed to partners. The partner does not need your internal details (host names, versions). Strip before external emission.
- No normalised timestamp. Mixing timezones and formats turns analysis into a nightmare. ISO 8601 UTC everywhere.
Related patterns
- Message Store — stores the history for long-term audit.
- Process Manager — uses the history to understand cycle state.
- Routing Slip — canonical pairing for observable choreography.
- Acknowledgements — each received ACK is recorded in the original message's history.
Sources
- Hohpe G., Woolf B. — Enterprise Integration Patterns, Message History (§551). enterpriseintegrationpatterns.com — Message History
- W3C Trace Context (W3C Recommendation,
November 2020). Standard format for
traceparentandtracestate, complementing Message History on the network observability side. w3.org/TR/trace-context - OpenTelemetry — Specification. Distributed tracing model adopted by every modern tool, compatible with Message History for business metadata. opentelemetry.io/docs/specs
- UN/EDIFACT — CONTRL and APERAK. The EDIFACT acknowledgement types, whose chain inscribes naturally into Message History.