← Back to blog

n8n dead letter queue: an exercise in selective replay

Build an editorial n8n dead letter queue exercise: preserve failed records, replay one corrected payload against WireMock, and check request counts.

A repaired parcel leaves a holding tray while a completed parcel stays untouched, depicting n8n dead letter queue replay.

Checked against the cited sources on .

Editorial exercise: task and boundaries

Build an n8n dead letter queue that stores failed synthetic records with reasons and correlation IDs, then manually replay only a corrected record. Compare WireMock request counts to check that previously successful and unselected records receive no additional requests. This is an editorial exercise; its implementation and expected results have not been executed or validated here.

You need an n8n environment with Data Tables, basic REST knowledge, and a facilitator-provided WireMock server reachable from n8n. Data Tables support persistence across workflows in the same project and conditional row operations. They suit light to moderate storage, have instance storage limits, and are accessible to project members by default.

Keep processing manual and serial: disable automatic request retries and allow no concurrent table editing. Concurrent changes can make paginated Data Table reads inconsistent. Concurrent recovery, automatic redrive, production deployment and exactly-once guarantees are outside scope. We think a deliberately narrow recovery exercise is more useful than a production-looking design whose guarantees remain unclear.

Sources: n8n-docs/docs/build/work-with-data/data-tables.md at main · n8n-io/n8n-docs · GitHub, n8n-docs/docs/integrations/builtin/core-nodes/n8n-nodes-base.datatable/rows.md at main · n8n-io/n8n-docs · GitHub, Running as a Standalone Process | WireMock

Prepare the records and deterministic mock

Three synthetic order parcels show acceptance, an invalid quantity and a simulated destination outage.
The authored fixture separates a valid order, a correctable rejection and an unselected outage.

Use the following authored fixture, with POST requests and JSON bodies. Set X-Correlation-ID to the record's ID. Ask the facilitator to configure exact JSON body matches for these responses, plus a fallback response of 400 with {"reason":"fixture mismatch"}. These are authored exercise inputs and expected responses.

Authored fixture: match each path, correlation header and body exactly
Record / headerPathInitial bodyInitial responseCorrected body / response
A/orders/A{"quantity":1}201; {"accepted":true}No correction
B/orders/B{"quantity":0}422; {"reason":"quantity must be positive"}{"quantity":2}; 201; {"accepted":true}
C/orders/C{"quantity":3}503; {"reason":"simulated outage"}No correction

WireMock supports header and body matching. n8n supports custom headers and JSON bodies, although the supplied configuration source is HTTP Request V3 code on master rather than a pinned release. In this exercise, the correlation header identifies requests for counting; it does not implement idempotency.

Sources: n8n/packages/nodes-base/nodes/HttpRequest/V3/Description.ts at master · n8n-io/n8n · GitHub, Request Matching | WireMock

Capture failures in your n8n dead letter queue

An order payload is copied before a request gate and its rejected version is stored with a reason slip.
Preserve the input before sending, then store enough context to inspect a failure.

For this n8n dead letter queue, create a failures Data Table with correlationId, path, originalPayloadJson, replayPayloadJson, failureReason, httpStatus, status and replayOutcome fields. Keep a separate outcomes table for initial results, including successes. These fields are an editorial schema; conditional insert, get and update operations are documented capabilities, not transactional replay guarantees.

Build the capture workflow in this order. Inspect actual node outputs before writing expressions: an error branch is not guaranteed to retain the original business payload.

  1. Preserve each record's ID, path and original JSON before HTTP Request.
  2. Send the fixture request with Include Response Headers and Status enabled and Never Error off.
  3. Set On Error to Continue (using error output), and connect that output explicitly.
  4. Inspect both outputs: classify successful responses on the regular branch and the fixture's 422 and 503 HTTP failures on the error branch.
  5. Map the actual available HTTP status and reason fields, recovering the preserved ID and payload through valid item linking. Stop if a fixture response's status, reason or original payload cannot be recovered; do not guess field paths.
  6. Handle transport errors separately because they may have no HTTP response. Record every initial outcome; insert failures with status failed, their payloads and a nonempty reason.

For linked inputs, n8n documents $('Preserve input').item.json when that node name and item linking are valid. Single-input, single-output linking supports this exercise's serial design, but new items or changed item counts can break the link. We'd stop on a missing payload rather than substitute the first item from a mixed batch.

Sources: n8n-docs/docs/integrations/builtin/core-nodes/n8n-nodes-base.datatable/rows.md at main · n8n-io/n8n-docs · GitHub, HTTP Request | Nodes | n8n Docs, n8n-docs/docs/build/understand-workflows/workflow-components/work-with-nodes.md at main · n8n-io/n8n-docs · GitHub, n8n-docs/docs/build/work-with-data/reference-data/reference-previous-nodes.md at main · n8n-io/n8n-docs · GitHub, How items link through workflows | Build | n8n Docs

Correct B and replay only eligible rows

A selection gate admits one corrected failed order while completed and unselected orders remain stored.
The replay selection admits only the chosen record with an eligible status.

Inspect B's stored reason and original payload. Set replayPayloadJson to {"quantity":2}, preserving originalPayloadJson unchanged. Leave C unselected. Before replay, save request counts for A, B and C using POST /__admin/requests/count with bodies such as {"method":"POST","urlPath":"/orders/A","headers":{"X-Correlation-ID":{"equalTo":"A"}}}; substitute B and C consistently.

Build a separate manual workflow that retrieves only correlationId B with status failed. Send its corrected payload to its stored path with the same header. Carry over Include Response Headers and Status, leave Never Error off, disable automatic retries, and connect Continue (using error output) to a failure handler. Inspect both outputs and map their actual status and reason fields, as in capture. Only after inspecting a confirmed 201 response, update that row to status replayed and record the outcome. On failure, preserve the reason and keep the row eligible for inspection.

Run the same selection again after success: it should return no eligible B row and send no request. In our view, eligibility belongs in the retrieval conditions, where a reviewer can inspect it before any destination call.

Sources: n8n-docs/docs/integrations/builtin/core-nodes/n8n-nodes-base.datatable/rows.md at main · n8n-io/n8n-docs · GitHub, n8n/packages/nodes-base/nodes/HttpRequest/V3/Description.ts at master · n8n-io/n8n · GitHub, HTTP Request | Nodes | n8n Docs, n8n-docs/docs/build/understand-workflows/workflow-components/work-with-nodes.md at main · n8n-io/n8n-docs · GitHub, Request Matching | WireMock, Verifying | WireMock

Verify request counts and reflect on uncertainty

A person compares two open journals beside a pink gate to verify selective replay requests.
Compare journal evidence before and after replay, checking the request body and recorded response against the fixture.

Repeat the identical count queries and compare against your saved baseline. WireMock verification depends on its in-memory request journal: resets, disabled journaling or discarded entries invalidate the comparison.

Also save a corrected-body count before replay by posting {"method":"POST","urlPath":"/orders/B","headers":{"X-Correlation-ID":{"equalTo":"B"}},"bodyPatterns":[{"equalToJson":{"quantity":2}}]} to /__admin/requests/count on your WireMock server. Repeat it after replay: the expected increase is one, then zero after selecting already replayed B. Keep the broader B query too, so additional requests with any payload remain visible.

Reconcile each recorded response status and reason against the fixture: initial A should have 201, initial B 422 with quantity must be positive, initial C 503 with simulated outage, and corrected B 201. Treat any 400 with fixture mismatch, missing reason, or unexpected response as a failed check. Use the following expected checks as completion criteria.

If a check fails, inspect mock matching, capture branches and payload linking before proceeding. Reflect on this question: what happens if the destination accepts B but recording success fails? This n8n dead letter queue exercise demonstrates selective request routing under its stated constraints; production recovery also needs destination-specific handling of uncertain outcomes, a recovery owner and a retention policy.

Sources: n8n-docs/docs/build/understand-workflows/workflow-components/work-with-nodes.md at main · n8n-io/n8n-docs · GitHub, n8n-docs/docs/build/work-with-data/reference-data/reference-previous-nodes.md at main · n8n-io/n8n-docs · GitHub, Request Matching | WireMock, Verifying | WireMock

Put this into practice

Hands-on n8n challenges

Pick a challenge and build a working workflow in your own n8n environment, with five progressive tips per challenge.

Try a hands-on challenge

For your team

Custom n8n training programs for one team or department, run on your own n8n instance with your own tools and data.

Training for your team

n8n skills assessment

A free quiz of about 20 minutes: 30 questions in 10 skill areas and a level from Beginner to Expert. On AI Adoption Score, another project by the author of this site.

Get your n8n level