This workflow follows the Error Trigger → Gmail recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"nodes": [
{
"parameters": {
"content": "```\nstatus retry_count error_type severity is_retryable suggested_action failed_node\n-------------------------------------------------------------------------------------------------------\npending_retry 0 unknown medium FALSE manual_review \npending_retry 0 unknown medium FALSE manual_review \npending_retry 0 unknown medium FALSE manual_review \npending_retry 0 unknown medium FALSE manual_review \npending_retry 0 parse_error medium FALSE check_llm_output \npending_retry 0 parse_error medium FALSE check_llm_output Accountant-concierge-LM\npending_retry 0 parse_error medium FALSE check_llm_output Accountant-concierge-LM\npending_retry 0 parse_error medium FALSE check_llm_output Accountant-concierge-LM\npending_retry 0 parse_error medium FALSE check_llm_output Call 'smart-table-fill'\npending_retry 0 parse_error medium FALSE check_llm_output Call 'smart-table-fill'\n```",
"height": 256,
"width": 1008
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
224,
64
],
"id": "31d79e15-243e-4416-a1eb-ef083978548f",
"name": "Sticky Note"
},
{
"parameters": {
"content": "## Error Handler Workflow\n\n**Purpose:** Catch failed executions from all workflows, classify errors, log to Google Sheets, send Telegram alerts.\n\n**Setup:**\n1. Activate this workflow\n2. In n8n Settings \u2192 Error Workflow \u2192 Select this workflow\n3. Create `FailedItems` sheet tab with headers\n\n**CODE RED Alert:**\nIf the failing workflow is \"8-hour Task Resolver\", sends an immediate CODE RED Telegram alert and then continues into normal logging + alerts (FailedItems, Telegram, email). This catches the watchdog-failure scenario where automatic error recovery is broken.\n\n**Error Types:**\n- `auth_error` (401/403) \u2192 Critical, refresh credentials\n- `rate_limit` (429) \u2192 High, wait and retry\n- `network_error` \u2192 High, retry with backoff\n- `llm_schema_error` \u2192 High, auto-retry after 8h (retryable)\n- `parse_error` \u2192 Medium, check LLM output\n- `validation_error` \u2192 Medium, check input data\n- `unknown` \u2192 Medium, manual review\n\n**Long Message Support:**\nMessages over 4000 chars are split into chunks and sent as multiple Telegram messages with part numbers [1/3], [2/3], etc.\n\n\nuse a code-node like this to test:\n```\n// Test Error Handler - uncomment ONE at a time\n\n// Rate Limit (retryable)\nthrow new Error(\"429 Too Many Requests - rate limit exceeded\"); \n\n// Auth Error (critical)\n// throw new Error(\"401 Unauthorized - invalid credentials\"); \n\n// Network Error (retryable)\n// throw new Error(\"ETIMEDOUT - connection timed out\");\n\n// Parse Error\n// throw new Error(\"Unexpected token in JSON at position 0\"); \n\n// Validation Error\n// throw new Error(\"Required field 'email' is missing\");\n\n// Resource Error (critical)\n// throw new Error(\"Out of memory - cannot allocate buffer\"); \n\n// Unknown Error\n// throw new Error(\"Something weird happened\");\n```",
"height": 944,
"width": 480
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-1312,
144
],
"id": "97da6e44-5c8f-4f09-b9a8-85dfd7b87899",
"name": "Overview"
},
{
"parameters": {},
"type": "n8n-nodes-base.errorTrigger",
"typeVersion": 1,
"position": [
-768,
592
],
"id": "ac4bfaba-1429-46fb-a165-c58f6302ab7a",
"name": "Error Trigger"
},
{
"parameters": {
"jsCode": "// Prepare & Classify Error\n// 1) Extracts execution context from the error trigger (record fields)\n// 2) Classifies error type/severity/retryability, LLM cost estimate, auto-retry registry\n\nconst execution = $json.execution || {};\nconst workflow = $json.workflow || {};\nconst error = $json.error || {};\n\n// ====== 1) Prepare record ======\n\n// Extract failing node name from multiple possible locations\nconst failedNode = execution.lastNodeExecuted ||\n error.node ||\n error.nodeName ||\n (error.context && error.context.nodeName) ||\n 'unknown';\n\n// Extract error message from multiple possible locations\nlet rawErrorMsg = error.message;\nif (!rawErrorMsg) {\n rawErrorMsg = error.description || error.reason || error.cause ||\n error.details || error.errorMessage;\n}\n// If still no message but error has a name that's descriptive, use it\nif (!rawErrorMsg && error.name && error.name !== 'Error' && error.name !== 'NodeOperationError') {\n rawErrorMsg = error.name;\n}\n// Last resort: stringify, but check if it's actually useful\nif (!rawErrorMsg) {\n const stringified = JSON.stringify(error);\n if (stringified && stringified !== '{}' && stringified !== 'null' && stringified.length > 2) {\n rawErrorMsg = stringified;\n }\n}\n// Final fallback with specific hint\nif (!rawErrorMsg) {\n rawErrorMsg = 'LLM output did not match required schema - check model response format';\n}\n\n// Extract retry_input - data needed to restart workflow from scratch\nlet retryInputData = {};\nconst runData = execution.data?.resultData?.runData || {};\nconst nodesToCheck = ['Set File ID', 'Gmail', 'Gmail Trigger', 'Clean Email', 'Manual Trigger'];\nfor (const nodeName of nodesToCheck) {\n const nodeData = runData[nodeName]?.[0]?.data?.main?.[0]?.[0]?.json;\n if (nodeData) {\n if (nodeData.email_ID) retryInputData.email_ID = nodeData.email_ID;\n if (nodeData.id && !retryInputData.email_ID) retryInputData.email_ID = nodeData.id;\n if (nodeData.messageId) retryInputData.messageId = nodeData.messageId;\n break; // Found data, stop searching\n }\n}\n\n// ====== Extract attachment count for LLM cost estimation ======\nlet attachmentCount = 0;\n\n// Strategy 1: Check Gmail node for binary attachments\nconst gmailNodeData = runData['Gmail']?.[0]?.data?.main?.[0]?.[0];\nif (gmailNodeData?.binary) {\n attachmentCount = Object.keys(gmailNodeData.binary).length;\n}\n// Strategy 2: Check Gmail Trigger node\nif (attachmentCount === 0) {\n const gmailTriggerData = runData['Gmail Trigger']?.[0]?.data?.main?.[0]?.[0];\n if (gmailTriggerData?.binary) {\n attachmentCount = Object.keys(gmailTriggerData.binary).length;\n }\n}\n// Strategy 3: Check \"sp\" (Split Out) node output count\nif (attachmentCount === 0) {\n const spNodeData = runData['sp'];\n if (spNodeData && spNodeData[0]?.data?.main?.[0]) {\n attachmentCount = spNodeData[0].data.main[0].length;\n }\n}\n// Strategy 4: Check Clean Email object for attachmentNames\nif (attachmentCount === 0) {\n const cleanEmailData = runData['Clean Email object']?.[0]?.data?.main?.[0]?.[0]?.json;\n if (cleanEmailData?.data?.[0]?.attachmentNames) {\n attachmentCount = cleanEmailData.data[0].attachmentNames.length;\n }\n}\n// Default to 1 if we couldn't determine (conservative estimate)\nif (attachmentCount === 0) {\n attachmentCount = 1;\n}\n\nconst record = {\n timestamp: new Date().toISOString(),\n workflow_id: workflow.id || 'unknown',\n workflow_name: workflow.name || 'unknown',\n execution_id: execution.retryOf || execution.id || 'unknown',\n execution_mode: execution.mode || 'unknown',\n failed_node: failedNode,\n retry_of: execution.retryOf || '',\n error_message: rawErrorMsg.substring(0, 500),\n error_stack: (error.stack || '').substring(0, 1000),\n error_name: error.name || 'Error',\n started_at: execution.startedAt || '',\n status: 'pending_retry',\n retry_count: 0,\n retry_input: JSON.stringify(retryInputData),\n attachment_count: attachmentCount\n};\n\n// ====== 2) Classify ======\n\nconst errorMsg = record.error_message.toLowerCase();\nconst errorMsgOriginal = record.error_message;\nconst errorName = record.error_name.toLowerCase();\nconst workflowName = record.workflow_name;\n\n// ====== Auto-Retry Registry ======\n// Workflows that support automatic retry on rate limits\nconst AUTO_RETRY_REGISTRY = [\n 'smart-folder2table',\n 'any-file2json-converter',\n // Add more as needed\n];\n\nconst canAutoRetry = AUTO_RETRY_REGISTRY.some(name =>\n workflowName.toLowerCase().includes(name.toLowerCase())\n);\n\nlet errorType = 'unknown';\nlet severity = 'medium';\nlet isRetryable = false;\nlet suggestedAction = 'manual_review';\n\n// Auth errors (401, 403, credential issues)\nif (errorMsg.includes('401') || errorMsg.includes('403') ||\n errorMsg.includes('unauthorized') || errorMsg.includes('forbidden') ||\n errorMsg.includes('credential') || errorMsg.includes('token expired') ||\n errorMsg.includes('invalid_grant') || errorMsg.includes('authentication')) {\n errorType = 'auth_error';\n severity = 'critical';\n suggestedAction = 'refresh_credentials';\n isRetryable = false;\n}\n// Rate limits (429)\nelse if (errorMsg.includes('429') || errorMsg.includes('rate limit') ||\n errorMsg.includes('too many requests') || errorMsg.includes('quota') ||\n errorMsg.includes('throttl')) {\n errorType = 'rate_limit';\n severity = 'high';\n suggestedAction = 'wait_and_retry';\n isRetryable = true;\n}\n// Network/timeout errors\nelse if (errorMsg.includes('timeout') || errorMsg.includes('etimedout') ||\n errorMsg.includes('econnrefused') || errorMsg.includes('econnreset') ||\n errorMsg.includes('network') || errorMsg.includes('enotfound') ||\n errorMsg.includes('socket hang up') || errorMsg.includes('502') ||\n errorMsg.includes('503') || errorMsg.includes('504')) {\n errorType = 'network_error';\n severity = 'high';\n suggestedAction = 'retry_with_backoff';\n isRetryable = true;\n}\n// LLM Schema errors (specific pattern - retryable because LLMs are non-deterministic)\nelse if (errorMsg.includes('llm output did not match required schema')) {\n errorType = 'llm_schema_error';\n severity = 'high';\n suggestedAction = 'auto_retry_8h';\n isRetryable = true;\n}\n// Parse errors (JSON, schema) - general case, NOT retryable\nelse if (errorMsg.includes('json') || errorMsg.includes('parse') ||\n errorMsg.includes('unexpected token') || errorMsg.includes('schema') ||\n errorMsg.includes('syntax error') || errorName.includes('syntaxerror')) {\n errorType = 'parse_error';\n severity = 'medium';\n suggestedAction = 'check_llm_output';\n isRetryable = false;\n}\n// Validation errors\nelse if (errorMsg.includes('required') || errorMsg.includes('missing') ||\n errorMsg.includes('invalid') || errorMsg.includes('validation') ||\n errorMsg.includes('not found') || errorMsg.includes('does not exist')) {\n errorType = 'validation_error';\n severity = 'medium';\n suggestedAction = 'check_input_data';\n isRetryable = false;\n}\n// Resource errors\nelse if (errorMsg.includes('memory') || errorMsg.includes('disk') ||\n errorMsg.includes('storage') || errorMsg.includes('out of space')) {\n errorType = 'resource_error';\n severity = 'critical';\n suggestedAction = 'check_resources';\n isRetryable = false;\n}\n\n// ====== LLM Cost Estimation by Provider ======\n// Rate limits are per API provider, so we track them separately:\n// - Groq: 5 req/min (bottleneck causing 429 errors)\n// - Google Gemini: Separate limit for OCR (not typically the bottleneck)\nconst LLM_COST_BY_NODE = {\n // Early failures - most work remaining\n 'Gmail Trigger': { groq_base: 1, groq_per_att: 1, google_per_att: 1 },\n 'Clean Email object': { groq_base: 1, groq_per_att: 1, google_per_att: 1 },\n\n // Classification stage (Groq)\n 'subject-classifier-LM': { groq_base: 1, groq_per_att: 1, google_per_att: 0 },\n\n // Attachment processing stage\n 'any-file2json-converter': { groq_base: 0, groq_per_att: 1, google_per_att: 1 },\n 'Create Attachment Profile':{ groq_base: 0, groq_per_att: 1, google_per_att: 1 },\n\n // Invoice extraction stage (Groq only)\n 'Accountant-concierge-LM': { groq_base: 0, groq_per_att: 1, google_per_att: 0 },\n\n // smart-table-fill (Groq, batched) - 4 batches typical\n 'Extract Data from String':{ groq_base: 0, groq_per_att: 4, google_per_att: 0 },\n \"Call 'smart-table-fill'\": { groq_base: 0, groq_per_att: 4, google_per_att: 0 },\n 'smart-table-fill': { groq_base: 0, groq_per_att: 4, google_per_att: 0 },\n\n // Default for unknown nodes\n '_default': { groq_base: 1, groq_per_att: 1, google_per_att: 1 }\n};\n\nconst cost = LLM_COST_BY_NODE[record.failed_node] || LLM_COST_BY_NODE['_default'];\nconst expected_groq_requests = cost.groq_base + (attachmentCount * cost.groq_per_att);\nconst expected_google_requests = attachmentCount * cost.google_per_att;\n\n// Package LLM cost estimate as single JSON field\n// Different workflows have different cost structures, so keep as flexible package\nconst llmCostEstimate = {\n attachment_count: attachmentCount,\n groq: expected_groq_requests,\n google: expected_google_requests\n};\n\n// ====== Extract Retry Timing for Rate Limits ======\nlet retryAfterSeconds = 60; // default\nif (errorType === 'rate_limit') {\n // Pattern: \"retry in 55.251598518s\" or \"retry in 55 seconds\"\n const retryMatch = errorMsgOriginal.match(/retry in (\\d+(?:\\.\\d+)?)\\s*s/i);\n if (retryMatch) {\n retryAfterSeconds = Math.ceil(parseFloat(retryMatch[1]));\n }\n}\n\nreturn [{\n json: {\n ...record,\n error_type: errorType,\n severity: severity,\n is_retryable: isRetryable,\n suggested_action: suggestedAction,\n retry_params: JSON.stringify(llmCostEstimate),\n can_auto_retry: canAutoRetry,\n retry_after_seconds: retryAfterSeconds\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-320,
592
],
"id": "fc3a9fa9-042f-4b1c-a01e-6dad08795d7e",
"name": "Prepare & Classify Error"
},
{
"parameters": {
"operation": "appendOrUpdate",
"documentId": {
"__rl": true,
"value": "YOUR_FAILEDITEMS_SHEET_ID",
"mode": "list",
"cachedResultName": "007_Error-handler.n8n-sheet",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_FAILEDITEMS_SHEET_ID/edit?usp=drivesdk"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "FailedItems",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_FAILEDITEMS_SHEET_ID/edit#gid=0"
},
"columns": {
"mappingMode": "autoMapInputData",
"value": {},
"matchingColumns": [
"execution_id"
],
"schema": [
{
"id": "timestamp",
"displayName": "timestamp",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "workflow_id",
"displayName": "workflow_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "workflow_name",
"displayName": "workflow_name",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "execution_id",
"displayName": "execution_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "execution_mode",
"displayName": "execution_mode",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "failed_node",
"displayName": "failed_node",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "retry_of",
"displayName": "retry_of",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "error_message",
"displayName": "error_message",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "error_stack",
"displayName": "error_stack",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "error_name",
"displayName": "error_name",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "started_at",
"displayName": "started_at",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "status",
"displayName": "status",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "retry_count",
"displayName": "retry_count",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "error_type",
"displayName": "error_type",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "severity",
"displayName": "severity",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "is_retryable",
"displayName": "is_retryable",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "suggested_action",
"displayName": "suggested_action",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "retry_input",
"displayName": "retry_input",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4,
"position": [
352,
592
],
"id": "44879a1e-9691-43e0-978f-2ed0026a8572",
"name": "Append to FailedItems",
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Format Telegram Alert with Severity Emoji\n// Outputs FULL message (no truncation) - chunking handled downstream\n// Includes LLM cost estimates for rate-limit awareness\n\nconst esc = (s) => String(s).replace(/_/g, '\\\\_');\n\nconst severityEmoji = {\n critical: '\\uD83D\\uDD34', // Red circle\n high: '\\uD83D\\uDFE0', // Orange circle\n medium: '\\uD83D\\uDFE1', // Yellow circle\n low: '\\uD83D\\uDFE2' // Green circle\n};\n\nconst emoji = severityEmoji[$json.severity] || '\\u26AA'; // White circle default\n\n// Parse LLM cost estimate JSON\nconst llmCost = JSON.parse($json.retry_params || '{}');\n\n// Build full message with failed node prominently displayed\n// Added attachment count and LLM estimates for rate limit visibility\nconst msg = `${emoji} ${esc($json.error_type).toUpperCase()}\n\\u274C Workflow: ${esc($json.workflow_name)}\n\\uD83D\\uDCCD Node: ${esc($json.failed_node || 'unknown')}\n---\nError: ${esc($json.error_message)}\n---\nExecution: ${$json.execution_id}\nSeverity: ${$json.severity}\nRetryable: ${$json.is_retryable ? 'Yes' : 'No'}\nAction: ${esc($json.suggested_action)}\nAttachments: ${llmCost.attachment_count || 0}\nLLM calls: Groq=${llmCost.groq || 0} | Google=${llmCost.google || 0}\nStack: ${esc($json.error_stack || 'N/A')}`;\n\nreturn [{\n json: {\n fullMessage: msg\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
576,
592
],
"id": "e7f5d957-8e4d-49d7-a9a0-d00fc403e7f0",
"name": "Format Telegram Alert"
},
{
"parameters": {
"jsCode": "// Split Long Message into Chunks\n// Telegram max message length is ~4096 chars, using 4000 to be safe\n\nconst message = $json.fullMessage || 'No error message';\nconst maxLength = 4000;\nconst chunks = [];\n\nfor (let i = 0; i < message.length; i += maxLength) {\n chunks.push(message.substring(i, i + maxLength));\n}\n\nreturn chunks.map((chunk, idx) => ({\n json: {\n chunk: chunk,\n part: idx + 1,\n total: chunks.length\n }\n}));"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
800,
592
],
"id": "fbc7df72-a695-42f1-b1a5-29f9055b93fd",
"name": "Split Long Message"
},
{
"parameters": {
"options": {}
},
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
1024,
592
],
"id": "ff1ddec4-fe63-4059-ba4f-b8fb2b711ef0",
"name": "Loop Over Chunks"
},
{
"parameters": {
"chatId": "YOUR_CHAT_ID_1",
"text": "={{ $json.total > 1 ? '[' + $json.part + '/' + $json.total + '] ' : '' }}{{ $json.chunk }}",
"additionalFields": {
"appendAttribution": false
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
1248,
592
],
"id": "cf9f8582-c986-49e3-9ddc-d35bbb196100",
"name": "Send Telegram Alert",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "resolver-failure-check",
"leftValue": "={{ $json.workflow_name }}",
"rightValue": "8-hour Task Resolver",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
128,
592
],
"id": "300a706d-0ff2-4945-ae32-b1b0efcbd59e",
"name": "Is Resolver Failure?"
},
{
"parameters": {
"chatId": "YOUR_CHAT_ID_1",
"text": "=\ud83d\udea8\ud83d\udea8\ud83d\udea8 CODE RED \ud83d\udea8\ud83d\udea8\ud83d\udea8\n\n\u26a0\ufe0f ERROR HANDLER DOWN \u26a0\ufe0f\n\nThe 8-hour Task Resolver has FAILED!\nThis means automatic error recovery is broken.\n\nFailed Node: {{ $json.failed_node }}\nError: {{ $json.error_message }}\nExecution: {{ $json.execution_id }}\n\n\ud83d\udd25 IMMEDIATE ATTENTION REQUIRED \ud83d\udd25",
"additionalFields": {
"appendAttribution": false
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
128,
784
],
"id": "ac4c8411-3c5f-4d31-bd8e-6d108fe297f3",
"name": "CODE RED Alert",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "rate-limit-check",
"leftValue": "={{ $json.error_type }}",
"rightValue": "rate_limit",
"operator": {
"type": "string",
"operation": "equals"
}
},
{
"id": "auto-retry-check",
"leftValue": "={{ $json.can_auto_retry }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-96,
592
],
"id": "eh-if-rate-limit-auto-retry",
"name": "IF: Rate Limit Auto-Retry?"
},
{
"parameters": {
"jsCode": "// Extract Config for Execute Workflow retry\n// Gets original Config values from failed execution's runData\nconst execution = $('Error Trigger').first().json.execution || {};\nconst runData = execution.data?.resultData?.runData || {};\n\n// Extract Config node output from smart-folder2table\nconst configData = runData['Config']?.[0]?.data?.main?.[0]?.[0]?.json || {};\n\nreturn [{\n json: {\n // Pass original config values\n folder_id: configData.folder_id || '',\n spreadsheet_id: configData.spreadsheet_id || '',\n data_sheet_name: configData.data_sheet_name || 'Sheet1',\n source_file_column: configData.source_file_column || 'source_file',\n match_column: configData.match_column || 'source_file',\n batch_size: configData.batch_size || 10,\n schema_sheet_name: configData.schema_sheet_name || 'Description_hig7f6',\n // Add new rate limit param from error classification\n rate_limit_wait_seconds: $('Prepare & Classify Error').first().json.retry_after_seconds || 60\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-96,
976
],
"id": "eh-extract-config-for-retry",
"name": "Extract Config for Retry"
},
{
"parameters": {
"amount": "={{ $('Prepare & Classify Error').first().json.retry_after_seconds }}",
"unit": "seconds"
},
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [
128,
976
],
"id": "eh-wait-rate-limit",
"name": "Wait (Rate Limit)"
},
{
"parameters": {
"workflowId": {
"__rl": true,
"value": "={{ $('Prepare & Classify Error').first().json.workflow_id }}",
"mode": "id"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"folder_id": "={{ $json.folder_id }}",
"spreadsheet_id": "={{ $json.spreadsheet_id }}",
"data_sheet_name": "={{ $json.data_sheet_name }}",
"source_file_column": "={{ $json.source_file_column }}",
"match_column": "={{ $json.match_column }}",
"batch_size": "={{ $json.batch_size }}",
"schema_sheet_name": "={{ $json.schema_sheet_name }}",
"rate_limit_wait_seconds": "={{ $json.rate_limit_wait_seconds }}"
},
"matchingColumns": [],
"schema": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {
"waitForSubWorkflow": true
}
},
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [
352,
976
],
"id": "eh-execute-smart-folder2table",
"name": "Retry Workflow (Execute)"
},
{
"parameters": {
"chatId": "YOUR_CHAT_ID_1",
"text": "=\u23f3 Rate limit hit in {{ $('Prepare & Classify Error').first().json.workflow_name }}\n\nRestarting via Execute Workflow after {{ $('Prepare & Classify Error').first().json.retry_after_seconds }}s delay...\n\nFailed node: {{ $('Prepare & Classify Error').first().json.failed_node }}\nrate_limit_wait_seconds={{ $('Prepare & Classify Error').first().json.retry_after_seconds }}\n\nResumability: Already-processed files will be skipped.",
"additionalFields": {
"appendAttribution": false
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
576,
976
],
"id": "eh-auto-retry-alert",
"name": "Send Auto-Retry Alert",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"content": "## Auto-Retry for Rate Limits\n\n**Registry-based:** only workflows in `AUTO_RETRY_REGISTRY` (in the `Prepare & Classify Error` code node) get immediate retry; others go through the normal flow \u2192 8-hour resolver. Current registry: smart-folder2table, any-file2json-converter.\n\n**How:** extract original Config values from the failed execution's runData \u2192 wait \"retry in Xs\" from the error message (default 60s) \u2192 re-run the SAME workflow (dynamic workflow_id) with the original config + rate_limit_wait_seconds.\n\n**Resumability:** restarted workflows check the sheet and skip already-processed files.",
"height": 300,
"width": 940
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-160,
912
],
"id": "eh-sticky-auto-retry",
"name": "Sticky Note - Auto-Retry"
},
{
"parameters": {
"content": "### If stops here\n\nSet document \u2192 your FailedItems spreadsheet,\nsheet \u2192 `FailedItems`,\nmatch column \u2192 `execution_id`",
"height": 140,
"width": 220,
"color": 3
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
352,
432
],
"id": "eh-sticky-faileditems-setup",
"name": "Sticky Note - FailedItems Setup"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": false,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "runner-down-check",
"leftValue": "={{ ($json.execution?.error?.message || '').toLowerCase() }}",
"rightValue": "task request timed out|task runner|no runner|not ready|at capacity|task broker",
"operator": {
"type": "string",
"operation": "regex"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "b1f00d00-0000-4000-8000-000000000001",
"name": "Runner/Infra Down?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-544,
592
]
},
{
"parameters": {
"chatId": "YOUR_CHAT_ID_1",
"text": "=\ud83d\udea8 n8n ALERT \u2014 infra / task-runner failure\n\nA workflow failed with a Code-node / task-runner timeout, so the normal error pipeline (which starts with Code nodes) cannot run and would NOT alert you.\n\nWorkflow: {{ $json.workflow?.name || $json.workflow?.id }}\nFailed node: {{ $json.execution?.error?.node?.name }}\nError: {{ $json.execution?.error?.message }}\nExecution: {{ $json.execution?.id }}\n\nLikely cause: external task runner is down or version-drifted vs primary.\nFix: Railway \u2192 redeploy n8n-runner + primary.",
"additionalFields": {
"appendAttribution": false
}
},
"id": "b1f00d00-0000-4000-8000-000000000002",
"name": "\ud83d\udea8 Runner-Proof Alert",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
-320,
400
],
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"content": "## \ud83d\udee1\ufe0f Runner-proof alert branch\n\nThe normal pipeline starts with **Code nodes** (Prepare & Classify Error). When the **task runner is down**, those Code nodes time out (~60s) and this workflow dies BEFORE reaching any Telegram node \u2014 so you get total silence.\n\nThis IF checks the raw error message for runner/timeout signatures. If matched, it sends a Telegram alert built with **expressions only** (no Code node \u2192 runs in n8n's main process \u2192 survives a runner outage).\n\nFALSE \u2192 normal rich pipeline (unchanged).",
"height": 320,
"width": 560,
"color": 4
},
"id": "b1f00d00-0000-4000-8000-000000000003",
"name": "Sticky Note - Runner-Proof Branch",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-384,
240
]
},
{
"parameters": {
"operation": "send",
"sendTo": "YOUR_ALERT_EMAIL",
"subject": "=\ud83d\udea8 n8n infra/runner failure \u2014 {{ $json.workflow?.name || $json.workflow?.id }}",
"emailType": "text",
"message": "=\ud83d\udea8 n8n ALERT \u2014 infra / task-runner failure\n\nA workflow failed with a Code-node / task-runner timeout, so the normal error pipeline (Code nodes) cannot run and would NOT alert you.\n\nWorkflow: {{ $json.workflow?.name || $json.workflow?.id }}\nFailed node: {{ $json.execution?.error?.node?.name }}\nError: {{ $json.execution?.error?.message }}\nExecution: {{ $json.execution?.id }}\n\nLikely cause: external task runner down or version-drifted vs primary.\nFix: Railway \u2192 redeploy n8n-runner + primary.",
"options": {}
},
"id": "b1f00d00-0000-4000-8000-000000000004",
"name": "\ud83d\udce7 Runner-Proof Email",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
-96,
400
],
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "send",
"sendTo": "YOUR_ALERT_EMAIL",
"subject": "=\u26a0\ufe0f n8n workflow failure alert",
"emailType": "text",
"message": "={{ $json.fullMessage }}",
"options": {}
},
"id": "b1f00d00-0000-4000-8000-000000000005",
"name": "\ud83d\udce7 Email Alert",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
576,
784
],
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
}
}
],
"connections": {
"Error Trigger": {
"main": [
[
{
"node": "Runner/Infra Down?",
"type": "main",
"index": 0
}
]
]
},
"IF: Rate Limit Auto-Retry?": {
"main": [
[
{
"node": "Extract Config for Retry",
"type": "main",
"index": 0
}
],
[
{
"node": "Is Resolver Failure?",
"type": "main",
"index": 0
}
]
]
},
"Extract Config for Retry": {
"main": [
[
{
"node": "Wait (Rate Limit)",
"type": "main",
"index": 0
}
]
]
},
"Wait (Rate Limit)": {
"main": [
[
{
"node": "Retry Workflow (Execute)",
"type": "main",
"index": 0
}
]
]
},
"Retry Workflow (Execute)": {
"main": [
[
{
"node": "Send Auto-Retry Alert",
"type": "main",
"index": 0
}
]
]
},
"Append to FailedItems": {
"main": [
[
{
"node": "Format Telegram Alert",
"type": "main",
"index": 0
}
]
]
},
"Format Telegram Alert": {
"main": [
[
{
"node": "Split Long Message",
"type": "main",
"index": 0
},
{
"node": "\ud83d\udce7 Email Alert",
"type": "main",
"index": 0
}
]
]
},
"Split Long Message": {
"main": [
[
{
"node": "Loop Over Chunks",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Chunks": {
"main": [
[],
[
{
"node": "Send Telegram Alert",
"type": "main",
"index": 0
}
]
]
},
"Send Telegram Alert": {
"main": [
[
{
"node": "Loop Over Chunks",
"type": "main",
"index": 0
}
]
]
},
"Is Resolver Failure?": {
"main": [
[
{
"node": "CODE RED Alert",
"type": "main",
"index": 0
},
{
"node": "Append to FailedItems",
"type": "main",
"index": 0
}
],
[
{
"node": "Append to FailedItems",
"type": "main",
"index": 0
}
]
]
},
"Runner/Infra Down?": {
"main": [
[
{
"node": "\ud83d\udea8 Runner-Proof Alert",
"type": "main",
"index": 0
},
{
"node": "\ud83d\udce7 Runner-Proof Email",
"type": "main",
"index": 0
}
],
[
{
"node": "Prepare & Classify Error",
"type": "main",
"index": 0
}
]
]
},
"Prepare & Classify Error": {
"main": [
[
{
"node": "IF: Rate Limit Auto-Retry?",
"type": "main",
"index": 0
}
]
]
}
},
"meta": {
"templateCredsSetupCompleted": true
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
gmailOAuth2googleSheetsOAuth2ApitelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
010-Error-Handler. Uses errorTrigger, googleSheets, telegram, gmail. Event-driven trigger; 23 nodes.
Source: https://github.com/runfish5/micro-services/blob/main/projects/n8n/10_error-handler/workflows/010-error-handler.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This template is designed for n8n users running workflows in production who need reliable and structured error monitoring, not just basic alerts.
This n8n workflow provides a robust error handling and notification system for your n8n workflows. When an error occurs, it automatically logs the error details to Google Sheets, sends a notification
Telegram Code. Uses stickyNote, telegramTrigger, telegram, googleDrive. Event-driven trigger; 37 nodes.
Sync your Google Calendar events with Google Sheets and get daily Slack summaries with meeting statistics. FEATURES:
Instant enquiry/booking system. Uses telegramTrigger, googleSheets, telegram, gmail. Event-driven trigger; 28 nodes.