This workflow follows the Error Trigger → Google Sheets 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": [
-48,
80
],
"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 immediate CODE RED Telegram alert before normal processing. This catches the watchdog-failure scenario where the error handler itself is down.\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": [
-1088,
144
],
"id": "97da6e44-5c8f-4f09-b9a8-85dfd7b87899",
"name": "Overview"
},
{
"parameters": {},
"type": "n8n-nodes-base.errorTrigger",
"typeVersion": 1,
"position": [
-544,
592
],
"id": "ac4bfaba-1429-46fb-a165-c58f6302ab7a",
"name": "Error Trigger"
},
{
"parameters": {
"jsCode": "// Prepare Error Record\n// Extracts execution context from n8n error trigger\n// Also extracts attachment count for LLM cost estimation\n\nconst execution = $json.execution || {};\nconst workflow = $json.workflow || {};\nconst error = $json.error || {};\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 errorMsg = error.message;\n\n// If no message, try alternative properties n8n might use\nif (!errorMsg) {\n errorMsg = error.description || error.reason || error.cause ||\n error.details || error.errorMessage;\n}\n\n// If still no message but error has a name that's descriptive, use it\nif (!errorMsg && error.name && error.name !== 'Error' && error.name !== 'NodeOperationError') {\n errorMsg = error.name;\n}\n\n// Last resort: stringify, but check if it's actually useful\nif (!errorMsg) {\n const stringified = JSON.stringify(error);\n if (stringified && stringified !== '{}' && stringified !== 'null' && stringified.length > 2) {\n errorMsg = stringified;\n }\n}\n\n// Final fallback with specific hint\nif (!errorMsg) {\n errorMsg = 'LLM output did not match required schema - check model response format';\n}\n\n// Extract retry_input - data needed to restart workflow from scratch\n// Searches execution runData for email_ID or other identifying info\nlet retryInputData = {};\nconst runData = execution.data?.resultData?.runData || {};\n\n// Search common nodes that hold identifying data\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\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\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\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\n// Default to 1 if we couldn't determine (conservative estimate)\nif (attachmentCount === 0) {\n attachmentCount = 1;\n}\n\nreturn [{\n json: {\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: errorMsg.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}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-320,
592
],
"id": "c4ae7909-8ba0-413b-babf-efff2a799d07",
"name": "Prepare Error Record"
},
{
"parameters": {
"jsCode": "// Classify Error by Type\n// Determines severity, retryability, suggested action\n// Also calculates expected LLM requests for rate-limit-aware retries\n\nconst errorMsg = ($json.error_message || '').toLowerCase();\nconst errorMsgOriginal = $json.error_message || '';\nconst errorStack = ($json.error_stack || '').toLowerCase();\nconst errorName = ($json.error_name || '').toLowerCase();\nconst failedNode = $json.failed_node || 'unknown';\nconst attachmentCount = $json.attachment_count || 1;\nconst workflowName = $json.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[failedNode] || 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 ...($json),\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": [
-96,
592
],
"id": "fc3a9fa9-042f-4b1c-a01e-6dad08795d7e",
"name": "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": [
128,
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 chatId: 'YOUR_TELEGRAM_CHAT_ID',\n fullMessage: msg\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
352,
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 chatId = $json.chatId;\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 chatId: chatId,\n chunk: chunk,\n part: idx + 1,\n total: chunks.length\n }\n}));"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
576,
592
],
"id": "fbc7df72-a695-42f1-b1a5-29f9055b93fd",
"name": "Split Long Message"
},
{
"parameters": {
"options": {}
},
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
800,
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": [
1024,
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": [
192,
976
],
"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": [
192,
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,
976
],
"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: $('Classify Error').first().json.retry_after_seconds || 60\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
128,
1168
],
"id": "eh-extract-config-for-retry",
"name": "Extract Config for Retry"
},
{
"parameters": {
"amount": "={{ $('Classify Error').first().json.retry_after_seconds }}",
"unit": "seconds"
},
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [
352,
1168
],
"id": "eh-wait-rate-limit",
"name": "Wait (Rate Limit)"
},
{
"parameters": {
"workflowId": {
"__rl": true,
"value": "={{ $('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": [
576,
1168
],
"id": "eh-execute-smart-folder2table",
"name": "Retry Workflow (Execute)"
},
{
"parameters": {
"chatId": "YOUR_CHAT_ID_1",
"text": "=\u23f3 Rate limit hit in {{ $('Classify Error').first().json.workflow_name }}\n\nRestarting via Execute Workflow after {{ $('Classify Error').first().json.retry_after_seconds }}s delay...\n\nFailed node: {{ $('Classify Error').first().json.failed_node }}\nrate_limit_wait_seconds={{ $('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": [
800,
1168
],
"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 get immediate retry.\nOthers go through normal flow \u2192 8-hour resolver.\n\n**Current registry:**\n- smart-folder2table\n- any-file2json-converter\n\n**Timing:** Extracts \"retry in Xs\" from error message, defaults to 60s.\n\n**How it works:**\n1. Extracts original Config values from failed execution's runData\n2. Waits for rate limit period\n3. Calls the SAME workflow that failed (by ID from error data):\n - Original config (folder_id, spreadsheet_id, etc.)\n - rate_limit_wait_seconds = extracted retry timing\n\n**Workflow-agnostic:** Uses dynamic workflow_id from Classify Error,\nso any workflow in the registry can be auto-retried.\n\n**Resumability:**\nWorkflows check sheet for already-processed files.\nAfter restart, completed items are skipped.",
"height": 480,
"width": 380
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-280,
1080
],
"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": 80,
"width": 150
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
100,
560
],
"id": "eh-sticky-faileditems-setup",
"name": "Sticky Note - FailedItems Setup"
}
],
"connections": {
"Error Trigger": {
"main": [
[
{
"node": "Prepare Error Record",
"type": "main",
"index": 0
}
]
]
},
"Prepare Error Record": {
"main": [
[
{
"node": "Classify Error",
"type": "main",
"index": 0
}
]
]
},
"Classify Error": {
"main": [
[
{
"node": "IF: Rate Limit Auto-Retry?",
"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
}
]
]
},
"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
}
]
]
},
"Sticky Note - FailedItems Setup": {}
},
"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.
googleSheetsOAuth2ApitelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
007-Error-Handler. Uses errorTrigger, googleSheets, telegram. Event-driven trigger; 19 nodes.
Source: https://github.com/runfish5/micro-services/blob/main/projects/n8n/07_error-handler/workflows/007-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.
clients kept booking meetings during my prayer times. i'd either miss a prayer or scramble to reschedule. the problem wasn't the clients — it was that my calendar had no blocked windows for salah. i n
99_global_error_handler. Uses errorTrigger, telegram, googleSheets, stickyNote. Event-driven trigger; 4 nodes.
Deal-Finder. Uses executeWorkflowTrigger, googleSheets, perplexity, httpRequest. Event-driven trigger; 49 nodes.
checkProcess(old). Uses googleSheets, httpRequest, telegram, @n-octo-n/n8n-nodes-json-database. Event-driven trigger; 40 nodes.
checkProcess. Uses googleSheets, httpRequest, telegram, @n-octo-n/n8n-nodes-json-database. Event-driven trigger; 40 nodes.