Some failures cannot be automatically retried or recovered.
Some failures cannot be automatically retried or recovered. For these, route failed items to a dead letter queue (DLQ) -- a Google Sheet, database table, or dedicated queue where failed items wait for manual review and reprocessing. This prevents data loss and gives your team a clear remediation process.
Real-world example: A data migration workflow processes 10,000 records. 47 fail due to data quality issues (missing required fields, invalid formats). Rather than losing these records, route them to a DLQ sheet for the data team to fix and reprocess.
// Code node: "Prepare DLQ Entry"
// This runs on the failure branch after the "Split Successes/Failures" node
const failedItems = $input.all();
return failedItems.map(item => ({
json: {
// Identification
dlq_id: `DLQ-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`,
created_at: new Date().toISOString(),
// Source information
workflow_name: $workflow.name,
execution_id: $execution.id,
source_node: 'Enrich Company Data',
// Original data (for reprocessing)
original_payload: JSON.stringify(item.json),
// Error details
error_message: item.json.error?.message || 'Unknown error',
error_code: item.json.error?.statusCode || null,
// Triage fields
status: 'pending', // pending | investigating | resolved | ignored
retryable: item.json.retryable || false,
assigned_to: null,
resolution_notes: null
}
}));
```text
Write to a Google Sheet (Dead Letter Queue):
```yaml
Google Sheets node configuration:
Operation: Append
Sheet: "Dead Letter Queue"
Columns:
A: dlq_id
B: created_at
C: workflow_name
D: execution_id
E: source_node
F: original_payload
G: error_message
H: error_code
I: status
J: retryable
K: assigned_to
L: resolution_notes
```text
Build a reprocessing workflow:
```yaml
# Separate workflow: "DLQ Reprocessor"
# Triggered manually or on a schedule
[Manual Trigger or Schedule]
--> [Google Sheets: Read rows where status = 'pending' AND retryable = true]
--> [Code: Parse original_payload back to JSON]
--> [Original processing logic (copied or via Execute Workflow)]
--> [IF: Success?]
True: [Google Sheets: Update status to 'resolved']
False: [Google Sheets: Update status to 'investigating', increment retry_count]
```text
```sql
-- Postgres DLQ alternative (better for high volume)
CREATE TABLE dead_letter_queue (
id SERIAL PRIMARY KEY,
dlq_id TEXT UNIQUE NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
workflow_name TEXT NOT NULL,
execution_id TEXT,
source_node TEXT,
original_payload JSONB NOT NULL,
error_message TEXT,
error_code INTEGER,
status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'investigating', 'resolved', 'ignored')),
retryable BOOLEAN DEFAULT false,
retry_count INTEGER DEFAULT 0,
assigned_to TEXT,
resolved_at TIMESTAMPTZ,
resolution_notes TEXT
);
CREATE INDEX idx_dlq_status ON dead_letter_queue(status);
CREATE INDEX idx_dlq_created ON dead_letter_queue(created_at);
```text
No data is lost. Every failed item is accounted for and has a clear path to resolution, whether automatic retry or manual intervention.
**Related:** [Use "Pin Data" to Freeze Node Output](../testing-and-debugging/01-use-pin-data-to-freeze-node-output.md) | [Break Large Workflows into Sub-Workflows](../workflow-architecture/01-break-large-workflows-into-sub-workflows.md)
I build production n8n and Cloudflare automation for teams — the same engineering behind HarperFlow. Fixed-price, escrow-protected, US-based.