AutomationFlowsSlack & Telegram › Error Handler

Error Handler

007-Error-Handler. Uses errorTrigger, googleSheets, telegram. Event-driven trigger; 19 nodes.

Event trigger★★★★☆ complexity19 nodesError TriggerGoogle SheetsTelegram
Slack & Telegram Trigger: Event Nodes: 19 Complexity: ★★★★☆ Added:

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 →

Download .json
{
  "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.

Pro

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 →

More Slack & Telegram workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Slack & Telegram

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

Telegram Trigger, HTTP Request, Google Calendar +3
Slack & Telegram

99_global_error_handler. Uses errorTrigger, telegram, googleSheets, stickyNote. Event-driven trigger; 4 nodes.

Error Trigger, Telegram, Google Sheets
Slack & Telegram

Deal-Finder. Uses executeWorkflowTrigger, googleSheets, perplexity, httpRequest. Event-driven trigger; 49 nodes.

Execute Workflow Trigger, Google Sheets, Perplexity +2
Slack & Telegram

checkProcess(old). Uses googleSheets, httpRequest, telegram, @n-octo-n/n8n-nodes-json-database. Event-driven trigger; 40 nodes.

Google Sheets, HTTP Request, Telegram +3
Slack & Telegram

checkProcess. Uses googleSheets, httpRequest, telegram, @n-octo-n/n8n-nodes-json-database. Event-driven trigger; 40 nodes.

Google Sheets, HTTP Request, Telegram +3