This workflow corresponds to n8n.io template #17734 — we link there as the canonical source.
This workflow follows the Datatable → HTTP Request 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 →
{
"id": "yc369oQDy9IqFth2",
"name": "Monitor Telegram Channels for Market Intelligence with Apify, OpenAI, Google Sheets and Telegram",
"tags": [],
"nodes": [
{
"id": "71000000-0000-0000-4000-8000-000000000001",
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-2208,
176
],
"parameters": {},
"typeVersion": 1
},
{
"id": "71000000-0000-0000-4000-8000-000000000002",
"name": "Hourly Intelligence Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-2208,
16
],
"parameters": {
"rule": {
"interval": [
{
"field": "hours"
}
]
}
},
"typeVersion": 1.3
},
{
"id": "71000000-0000-0000-4000-8000-000000000003",
"name": "Ensure Delivery Ledger",
"type": "n8n-nodes-base.dataTable",
"position": [
-1968,
80
],
"parameters": {
"columns": {
"column": [
{
"name": "workflowSlug"
},
{
"name": "itemKey"
},
{
"name": "destination"
},
{
"name": "deliveredAt",
"type": "date"
}
]
},
"options": {
"createIfNotExists": true
},
"resource": "table",
"operation": "create",
"tableName": "FetchCat Delivery Ledger"
},
"typeVersion": 1.1
},
{
"id": "71000000-0000-0000-4000-8000-000000000004",
"name": "1. Configure Channel Intelligence",
"type": "n8n-nodes-base.set",
"position": [
-1680,
80
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "monitor-channels",
"name": "channels",
"type": "string",
"value": "durov\ntelegram"
},
{
"id": "monitor-goal",
"name": "Intelligence Goal",
"type": "string",
"value": "Track important product announcements, market shifts, partnerships, risks, and competitor moves that require a business response."
},
{
"id": "monitor-score",
"name": "Minimum Relevance Score",
"type": "number",
"value": 70
},
{
"id": "monitor-per-channel",
"name": "Maximum Posts Per Channel",
"type": "number",
"value": 10
},
{
"id": "monitor-per-run",
"name": "Maximum Posts Per Run",
"type": "number",
"value": 20
},
{
"id": "monitor-alerts",
"name": "Maximum Alerts Per Digest",
"type": "number",
"value": 5
}
]
}
},
"typeVersion": 3.4
},
{
"id": "71000000-0000-0000-4000-8000-000000000005",
"name": "Validate Channels and Build Actor Input",
"type": "n8n-nodes-base.code",
"position": [
-1440,
80
],
"parameters": {
"jsCode": "const input = $input.first()?.json || {};\nconst channels = [...new Set(String(input.channels || '').split(/[\\n,]+/).map((value) => value.trim().replace(/^@/, '')).filter(Boolean))];\nconst intelligenceGoal = String(input['Intelligence Goal'] || '').trim();\nconst minimumRelevanceScore = Math.max(0, Math.min(100, Math.floor(Number(input['Minimum Relevance Score']) || 70)));\nconst maxPostsPerChannel = Math.max(1, Math.min(50, Math.floor(Number(input['Maximum Posts Per Channel']) || 10)));\nconst maximumPostsPerRun = Math.max(1, Math.min(100, Math.floor(Number(input['Maximum Posts Per Run']) || 20)));\nconst maxAlerts = Math.max(1, Math.min(10, Math.floor(Number(input['Maximum Alerts Per Digest']) || 5)));\nif (channels.length === 0) throw new Error('Enter at least one public Telegram channel handle.');\nif (channels.some((value) => !/^[A-Za-z0-9_]{3,64}$/.test(value))) throw new Error('Use public Telegram handles without t.me URLs.');\nif (intelligenceGoal.length < 20 || intelligenceGoal.length > 2000) throw new Error('Intelligence Goal must contain 20 to 2000 characters.');\nreturn [{ json: {\n channels, intelligenceGoal, minimumRelevanceScore, maximumPostsPerRun, maxAlerts,\n actorInput: { channels, maxPostsPerChannel, maxPosts: maximumPostsPerRun, includeComments: false, includeCommentCount: true, includeMedia: true, includeRawHtmlSnippet: false }\n} }];"
},
"typeVersion": 2
},
{
"id": "71000000-0000-0000-4000-8000-000000000006",
"name": "2. Run Telegram Channel Posts Scraper",
"type": "n8n-nodes-base.httpRequest",
"position": [
-1200,
80
],
"parameters": {
"url": "https://api.apify.com/v2/acts/IXhSSJjFf5fz1XAir/runs",
"method": "POST",
"options": {
"timeout": 310000,
"response": {
"response": {
"responseFormat": "json"
}
}
},
"jsonBody": "={{ $json.actorInput }}",
"sendBody": true,
"sendQuery": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "waitForFinish",
"value": "300"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "Accept-Encoding",
"value": "identity"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.3
},
{
"id": "71000000-0000-0000-4000-8000-000000000007",
"name": "Download Telegram Post Results",
"type": "n8n-nodes-base.httpRequest",
"position": [
-960,
80
],
"parameters": {
"url": "=https://api.apify.com/v2/datasets/{{ $json.data.defaultDatasetId }}/items",
"options": {
"timeout": 60000,
"response": {
"response": {
"responseFormat": "json"
}
}
},
"sendQuery": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "clean",
"value": "true"
},
{
"name": "limit",
"value": "={{ $(\"Validate Channels and Build Actor Input\").first().json.maximumPostsPerRun }}"
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.3
},
{
"id": "71000000-0000-0000-4000-8000-000000000008",
"name": "Normalize Public Telegram Posts",
"type": "n8n-nodes-base.code",
"position": [
-720,
80
],
"parameters": {
"jsCode": "\nconst config = $('Validate Channels and Build Actor Input').first().json;\nconst payloads = $input.all().flatMap((item) => {\n let value = item.json?.data ?? item.json;\n if (typeof value === 'string') {\n try { value = JSON.parse(value); } catch { throw new Error('Apify returned invalid JSON.'); }\n }\n return Array.isArray(value) ? value : [value];\n});\nconst sheetsEpochOffset = 25569;\nconst serial = (value) => {\n const date = new Date(value || Date.now());\n return Number.isFinite(date.getTime()) ? date.getTime() / 86400000 + sheetsEpochOffset : null;\n};\nconst numberOrNull = (value) => Number.isFinite(Number(value)) ? Number(value) : null;\nconst formula = (url, label) => url ? '=HYPERLINK(\"' + String(url).replace(/\"/g, '\"\"') + '\",\"' + String(label).replace(/\"/g, '\"\"') + '\")' : '';\nconst seen = new Set();\nconst rows = [];\nfor (const post of payloads) {\n const channelHandle = String(post.channelHandle || '').trim().replace(/^@/, '').toLowerCase();\n const postId = String(post.postId || '').trim();\n const postUrl = String(post.postUrl || (channelHandle && postId ? 'https://t.me/' + channelHandle + '/' + postId : ''));\n const text = String(post.text || '').trim();\n if (!channelHandle || !postId || !postUrl) continue;\n const itemKey = channelHandle + ':' + postId;\n if (seen.has(itemKey)) continue;\n seen.add(itemKey);\n rows.push({ json: {\n itemKey,\n collectedAt: serial(post.scrapedAt),\n channel: '@' + channelHandle,\n channelHandle,\n channelTitle: String(post.channelTitle || ''),\n subscribers: numberOrNull(post.subscriberCount),\n postDate: serial(post.date),\n postDateIso: post.date ? new Date(post.date).toISOString() : '',\n postLink: formula(postUrl, 'Open post'),\n postUrl,\n postId,\n text: text.slice(0, 12000),\n views: numberOrNull(post.views),\n reactions: numberOrNull(post.reactionCount),\n replies: numberOrNull(post.replyCount),\n commentsScraped: numberOrNull(post.commentsScraped),\n mediaCount: Array.isArray(post.mediaUrls) ? post.mediaUrls.length : 0,\n links: Array.isArray(post.links) ? post.links.join('\\n') : '',\n intelligenceGoal: config.intelligenceGoal,\n minimumRelevanceScore: config.minimumRelevanceScore,\n maxAlerts: config.maxAlerts\n } });\n if (rows.length >= config.maximumPostsPerRun) break;\n}\nif (rows.length === 0) throw new Error('No public Telegram posts were returned. Check the channel handles and date window.');\nreturn rows;"
},
"typeVersion": 2
},
{
"id": "71000000-0000-0000-4000-8000-000000000009",
"name": "Keep Undelivered Posts",
"type": "n8n-nodes-base.dataTable",
"position": [
-480,
80
],
"parameters": {
"filters": {
"conditions": [
{
"keyName": "workflowSlug",
"keyValue": "telegram-channel-intelligence-monitor"
},
{
"keyName": "itemKey",
"keyValue": "={{ $json.itemKey }}"
}
]
},
"matchType": "allConditions",
"operation": "rowNotExists",
"dataTableId": {
"__rl": true,
"mode": "name",
"value": "FetchCat Delivery Ledger"
}
},
"typeVersion": 1.1
},
{
"id": "71000000-0000-0000-4000-8000-000000000010",
"name": "Build One Intelligence Batch",
"type": "n8n-nodes-base.code",
"position": [
-240,
80
],
"parameters": {
"jsCode": "const posts = $input.all().map((item) => item.json);\nif (posts.length === 0) return [];\nreturn [{ json: {\n posts: posts.map((post) => ({ itemKey: post.itemKey, channel: post.channel, date: post.postDateIso, text: post.text, views: post.views, reactions: post.reactions, replies: post.replies })),\n sourcePosts: posts,\n allKeys: posts.map((post) => post.itemKey),\n intelligenceGoal: posts[0].intelligenceGoal,\n minimumRelevanceScore: posts[0].minimumRelevanceScore,\n maxAlerts: posts[0].maxAlerts\n} }];"
},
"typeVersion": 2
},
{
"id": "71000000-0000-0000-4000-8000-000000000011",
"name": "3. Analyze Telegram Posts in One Batch",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
0,
80
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "id",
"value": "gpt-5.4-mini"
},
"options": {
"store": false,
"maxTokens": 5000,
"reasoning": {
"reasoningOptions": {
"effort": "low",
"summary": "none"
}
},
"textFormat": {
"textOptions": {
"name": "telegram_intelligence_batch",
"type": "json_schema",
"schema": "{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"results\"],\"properties\":{\"results\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"itemKey\",\"qualified\",\"relevanceScore\",\"urgency\",\"topic\",\"sentiment\",\"summary\",\"whyItMatters\"],\"properties\":{\"itemKey\":{\"type\":\"string\"},\"qualified\":{\"type\":\"boolean\"},\"relevanceScore\":{\"type\":\"integer\",\"minimum\":0,\"maximum\":100},\"urgency\":{\"type\":\"string\",\"enum\":[\"critical\",\"high\",\"normal\",\"low\"]},\"topic\":{\"type\":\"string\",\"minLength\":1,\"maxLength\":120},\"sentiment\":{\"type\":\"string\",\"enum\":[\"positive\",\"neutral\",\"negative\",\"mixed\"]},\"summary\":{\"type\":\"string\",\"minLength\":1,\"maxLength\":500},\"whyItMatters\":{\"type\":\"string\",\"minLength\":1,\"maxLength\":500}}}}}}",
"strict": true,
"verbosity": "low",
"description": "One validated intelligence result for every Telegram post."
}
},
"instructions": "Analyze every supplied Telegram post exactly once. Preserve itemKey values. Use only the supplied public content and metrics. Return the strict schema with concise, factual English. Qualified means the post materially supports the stated intelligence goal and meets the minimum relevance score."
},
"responses": {
"values": [
{
"content": "=Intelligence goal:\n{{ $json.intelligenceGoal }}\n\nMinimum relevance score: {{ $json.minimumRelevanceScore }}\n\nAnalyze every post and preserve itemKey:\n{{ JSON.stringify($json.posts) }}"
}
]
},
"builtInTools": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.3
},
{
"id": "71000000-0000-0000-4000-8000-000000000012",
"name": "Validate Intelligence Results",
"type": "n8n-nodes-base.code",
"position": [
240,
80
],
"parameters": {
"jsCode": "const source = $('Build One Intelligence Batch').first().json;\nconst raw = $input.first()?.json || {};\nconst seen = new Set();\nfunction findStructured(value) {\n if (value === null || value === undefined) return null;\n if (typeof value === 'string') {\n const text = value.trim().replace(/^\\x60\\x60\\x60(?:json)?\\s*/i, '').replace(/\\s*\\x60\\x60\\x60$/, '');\n if (!text.startsWith('{')) return null;\n try { return findStructured(JSON.parse(text)); } catch { return null; }\n }\n if (typeof value !== 'object' || seen.has(value)) return null;\n seen.add(value);\n if (Array.isArray(value.results)) return value;\n const preferred = ['output_text', 'outputText', 'text', 'content', 'output', 'message', 'response', 'data'];\n for (const key of preferred) {\n if (Object.prototype.hasOwnProperty.call(value, key)) {\n const found = findStructured(value[key]);\n if (found) return found;\n }\n }\n for (const nested of Array.isArray(value) ? value : Object.values(value)) {\n const found = findStructured(nested);\n if (found) return found;\n }\n return null;\n}\nconst parsed = findStructured(raw);\nif (!parsed) throw new Error('OpenAI returned an invalid Telegram intelligence batch.');\nconst expected = new Set(source.allKeys);\nconst actual = parsed.results.map((row) => String(row.itemKey));\nif (actual.length !== expected.size || new Set(actual).size !== actual.length || actual.some((key) => !expected.has(key))) throw new Error('OpenAI result keys do not exactly match the Telegram input batch.');\nconst byKey = new Map(source.sourcePosts.map((post) => [post.itemKey, post]));\nconst allowedUrgency = new Set(['critical', 'high', 'normal', 'low']);\nconst allowedSentiment = new Set(['positive', 'neutral', 'negative', 'mixed']);\nconst qualified = [];\nfor (const result of parsed.results) {\n if (typeof result.qualified !== 'boolean' || !Number.isInteger(result.relevanceScore) || result.relevanceScore < 0 || result.relevanceScore > 100 || !allowedUrgency.has(result.urgency) || !allowedSentiment.has(result.sentiment) || !String(result.topic || '').trim() || !String(result.summary || '').trim() || !String(result.whyItMatters || '').trim()) throw new Error('OpenAI returned a malformed Telegram intelligence result.');\n if (!result.qualified || result.relevanceScore < source.minimumRelevanceScore) continue;\n qualified.push({ ...byKey.get(result.itemKey), relevanceScore: result.relevanceScore, urgency: result.urgency, topic: String(result.topic).trim(), sentiment: result.sentiment, summary: String(result.summary).trim(), whyItMatters: String(result.whyItMatters).trim() });\n}\nqualified.sort((a, b) => b.relevanceScore - a.relevanceScore);\nreturn [{ json: { allKeys: source.allKeys, maxAlerts: source.maxAlerts, qualifiedPosts: qualified } }];"
},
"typeVersion": 2
},
{
"id": "71000000-0000-0000-4000-8000-000000000013",
"name": "Has Relevant Intelligence",
"type": "n8n-nodes-base.if",
"position": [
480,
80
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "telegram-qualified",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.qualifiedPosts.length }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2.2
},
{
"id": "71000000-0000-0000-4000-8000-000000000014",
"name": "Expand Intelligence Rows",
"type": "n8n-nodes-base.code",
"position": [
720,
-16
],
"parameters": {
"jsCode": "return $json.qualifiedPosts.map((post) => ({ json: post }));"
},
"typeVersion": 2
},
{
"id": "71000000-0000-0000-4000-8000-000000000015",
"name": "4. Save Intelligence to Google Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [
960,
-16
],
"parameters": {
"columns": {
"value": {
"Post": "={{ $json.postLink }}",
"Topic": "={{ $json.topic }}",
"Views": "={{ $json.views }}",
"Channel": "={{ $json.channel }}",
"Replies": "={{ $json.replies }}",
"Summary": "={{ $json.summary }}",
"Urgency": "={{ $json.urgency }}",
"Post key": "={{ $json.itemKey }}",
"Post date": "={{ $json.postDate }}",
"Reactions": "={{ $json.reactions }}",
"Sentiment": "={{ $json.sentiment }}",
"Analyzed at": "={{ $now.toMillis() / 86400000 + 25569 }}",
"Why it matters": "={{ $json.whyItMatters }}",
"Relevance score": "={{ $json.relevanceScore }}"
},
"schema": [
{
"id": "Post key",
"type": "string",
"display": true,
"required": false,
"displayName": "Post key",
"defaultMatch": true,
"canBeUsedToMatch": true
},
{
"id": "Analyzed at",
"type": "number",
"display": true,
"required": false,
"displayName": "Analyzed at",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Channel",
"type": "string",
"display": true,
"required": false,
"displayName": "Channel",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Post date",
"type": "number",
"display": true,
"required": false,
"displayName": "Post date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Post",
"type": "string",
"display": true,
"required": false,
"displayName": "Post",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Topic",
"type": "string",
"display": true,
"required": false,
"displayName": "Topic",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Urgency",
"type": "string",
"display": true,
"required": false,
"displayName": "Urgency",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Relevance score",
"type": "number",
"display": true,
"required": false,
"displayName": "Relevance score",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Sentiment",
"type": "string",
"display": true,
"required": false,
"displayName": "Sentiment",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Summary",
"type": "string",
"display": true,
"required": false,
"displayName": "Summary",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Why it matters",
"type": "string",
"display": true,
"required": false,
"displayName": "Why it matters",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Views",
"type": "number",
"display": true,
"required": false,
"displayName": "Views",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Reactions",
"type": "number",
"display": true,
"required": false,
"displayName": "Reactions",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Replies",
"type": "number",
"display": true,
"required": false,
"displayName": "Replies",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"Post key"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {
"useAppend": true
},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "id",
"value": "7001002",
"cachedResultName": "Telegram Intelligence"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "1ow1HyBqrkZimNDsCF9T_WORw7vRQ3qON6ZI3VaDc4TM",
"cachedResultName": "FetchCat n8n QA - Telegram Intelligence"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "71000000-0000-0000-4000-8000-000000000016",
"name": "Build Telegram Intelligence Digest",
"type": "n8n-nodes-base.code",
"position": [
1200,
-16
],
"parameters": {
"jsCode": "const result = $('Validate Intelligence Results').first().json;\nconst escapeHtml = (value) => String(value ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\nconst posts = result.qualifiedPosts.slice(0, result.maxAlerts);\nconst lines = posts.map((post, index) => (index + 1) + '. <a href=\"' + escapeHtml(post.postUrl) + '\"><b>' + escapeHtml(post.topic) + '</b></a>\\n' + escapeHtml(post.channel) + ' | ' + escapeHtml(post.urgency.toUpperCase()) + ' | ' + post.relevanceScore + '/100\\n' + escapeHtml(post.summary) + '\\nWhy it matters: ' + escapeHtml(post.whyItMatters));\nreturn [{ json: { telegramMessage: '<b>Telegram channel intelligence</b>\\n\\n' + lines.join('\\n\\n') + (result.qualifiedPosts.length > posts.length ? '\\n\\n+' + (result.qualifiedPosts.length - posts.length) + ' more relevant posts saved in Google Sheets.' : '') } }];"
},
"typeVersion": 2
},
{
"id": "71000000-0000-0000-4000-8000-000000000017",
"name": "5. Send Intelligence Digest to Telegram",
"type": "n8n-nodes-base.telegram",
"position": [
1440,
-16
],
"parameters": {
"text": "={{ $json.telegramMessage }}",
"chatId": "123456789",
"additionalFields": {
"parse_mode": "HTML",
"appendAttribution": false,
"disable_notification": false
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "71000000-0000-0000-4000-8000-000000000018",
"name": "Prepare Delivered Post Keys",
"type": "n8n-nodes-base.code",
"position": [
1680,
80
],
"parameters": {
"jsCode": "return $('Validate Intelligence Results').first().json.allKeys.map((itemKey) => ({ json: { itemKey } }));"
},
"typeVersion": 2
},
{
"id": "71000000-0000-0000-4000-8000-000000000019",
"name": "Commit Delivered Posts",
"type": "n8n-nodes-base.dataTable",
"position": [
1920,
80
],
"parameters": {
"columns": {
"value": {
"itemKey": "={{ $json.itemKey }}",
"deliveredAt": "={{ $now.toISO() }}",
"destination": "Google Sheets and Telegram",
"workflowSlug": "telegram-channel-intelligence-monitor"
},
"schema": [
{
"id": "workflowSlug",
"type": "string",
"display": true,
"required": false,
"displayName": "workflowSlug",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "itemKey",
"type": "string",
"display": true,
"required": false,
"displayName": "itemKey",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "destination",
"type": "string",
"display": true,
"required": false,
"displayName": "destination",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "deliveredAt",
"type": "dateTime",
"display": true,
"required": false,
"displayName": "deliveredAt",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"dataTableId": {
"__rl": true,
"mode": "name",
"value": "FetchCat Delivery Ledger"
}
},
"typeVersion": 1.1
},
{
"id": "71000000-0000-0000-4000-8000-000000000020",
"name": "Intelligence Monitor Complete",
"type": "n8n-nodes-base.code",
"position": [
2160,
80
],
"parameters": {
"jsCode": "const result = $('Validate Intelligence Results').first().json;\nreturn [{ json: { status: 'complete', postsEvaluated: result.allKeys.length, relevantPosts: result.qualifiedPosts.length, telegramAlertSent: result.qualifiedPosts.length > 0 } }];"
},
"executeOnce": true,
"typeVersion": 2
},
{
"id": "71000000-0000-0000-4000-8000-000000000030",
"name": "Workflow Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2848,
-464
],
"parameters": {
"width": 480,
"height": 1040,
"content": "## Monitor Telegram Channels for Market Intelligence\n\nTurn public Telegram channel posts into a focused intelligence feed with fetch_cat/telegram-channel-posts-scraper, OpenAI, Google Sheets, and Telegram. The workflow works on n8n Cloud or self-hosted n8n and makes one structured AI call per non-empty batch.\n\n### How it works\n\n1. Starts manually or once every hour and creates its delivery ledger if needed.\n2. Reads public channel handles, an intelligence goal, relevance threshold, and item limits from one editable Set node.\n3. Runs the Apify Actor once, normalizes the newest posts, and removes IDs already delivered.\n4. Classifies all unseen posts in one strict OpenAI batch.\n5. Saves relevant posts to Google Sheets and sends at most five in one Telegram digest.\n6. Records evaluated IDs only after required destinations succeed, so delivery failures remain retryable.\n\n### Setup\n\n- [ ] Enter public channel handles and describe the business questions that matter.\n- [ ] Connect Apify HTTP Header Auth in both request nodes.\n- [ ] Connect OpenAI and keep the pinned low-cost model.\n- [ ] Copy the spreadsheet template and select its Telegram Intelligence tab.\n- [ ] Create a Telegram bot with BotFather, connect it, and enter the destination chat ID.\n- [ ] Run manually twice to verify delivery and deduplication before publishing the hourly schedule.\n\nThe workflow monitors public content only. It never joins channels, contacts authors, or sends automated replies."
},
"typeVersion": 1
},
{
"id": "71000000-0000-0000-4000-8000-000000000031",
"name": "Start and Prepare State",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2288,
-160
],
"parameters": {
"color": 7,
"width": 560,
"height": 480,
"content": "## Start and prepare state\n\nRun manually or hourly and create the durable delivery ledger idempotently before collecting posts."
},
"typeVersion": 1
},
{
"id": "71000000-0000-0000-4000-8000-000000000032",
"name": "Configure Intelligence",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1760,
-160
],
"parameters": {
"color": 7,
"width": 480,
"height": 480,
"content": "## Configure intelligence\n\nEnter public channel handles, the intelligence goal, score threshold, post limits, and digest limit in one editable node."
},
"typeVersion": 1
},
{
"id": "71000000-0000-0000-4000-8000-000000000033",
"name": "Collect Public Posts",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1280,
-160
],
"parameters": {
"color": 7,
"width": 560,
"height": 480,
"content": "## Collect public posts\n\nRun the Telegram Channel Posts Scraper once and download the bounded public dataset after the Actor finishes."
},
"typeVersion": 1
},
{
"id": "71000000-0000-0000-4000-8000-000000000034",
"name": "Normalize and Deduplicate",
"type": "n8n-nodes-base.stickyNote",
"position": [
-720,
-160
],
"parameters": {
"color": 7,
"width": 656,
"height": 480,
"content": "## Normalize and deduplicate\n\nCreate stable post keys and readable metrics, remove previously delivered IDs, and assemble one bounded analysis batch."
},
"typeVersion": 1
},
{
"id": "71000000-0000-0000-4000-8000-000000000035",
"name": "Analyze and Validate",
"type": "n8n-nodes-base.stickyNote",
"position": [
-64,
-160
],
"parameters": {
"color": 7,
"width": 704,
"height": 480,
"content": "## Analyze and validate\n\nUse one structured OpenAI call, require exactly one result per input post, and keep only posts meeting the configured relevance threshold."
},
"typeVersion": 1
},
{
"id": "71000000-0000-0000-4000-8000-000000000036",
"name": "Deliver Relevant Intelligence",
"type": "n8n-nodes-base.stickyNote",
"position": [
640,
-160
],
"parameters": {
"color": 7,
"width": 960,
"height": 480,
"content": "## Deliver relevant intelligence\n\nUpsert qualified posts to Google Sheets and send one concise Telegram digest with direct links, scores, summaries, and business implications."
},
"typeVersion": 1
},
{
"id": "71000000-0000-0000-4000-8000-000000000037",
"name": "Commit Delivery State",
"type": "n8n-nodes-base.stickyNote",
"position": [
1600,
-160
],
"parameters": {
"color": 7,
"width": 720,
"height": 480,
"content": "## Commit delivery state\n\nRecord every evaluated key only after required delivery succeeds, then return a concise execution summary. Failed deliveries remain retryable."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"timezone": "Europe/Lisbon",
"binaryMode": "separate",
"callerPolicy": "workflowsFromSameOwner",
"executionOrder": "v1",
"saveManualExecutions": true
},
"versionId": "0ca1cfe6-e316-45e2-ac81-0c5c34467630",
"nodeGroups": [],
"connections": {
"Manual Trigger": {
"main": [
[
{
"node": "Ensure Delivery Ledger",
"type": "main",
"index": 0
}
]
]
},
"Commit Delivered Posts": {
"main": [
[
{
"node": "Intelligence Monitor Complete",
"type": "main",
"index": 0
}
]
]
},
"Ensure Delivery Ledger": {
"main": [
[
{
"node": "1. Configure Channel Intelligence",
"type": "main",
"index": 0
}
]
]
},
"Keep Undelivered Posts": {
"main": [
[
{
"node": "Build One Intelligence Batch",
"type": "main",
"index": 0
}
]
]
},
"Expand Intelligence Rows": {
"main": [
[
{
"node": "4. Save Intelligence to Google Sheets",
"type": "main",
"index": 0
}
]
]
},
"Has Relevant Intelligence": {
"main": [
[
{
"node": "Expand Intelligence Rows",
"type": "main",
"index": 0
}
],
[
{
"node": "Prepare Delivered Post Keys",
"type": "main",
"index": 0
}
]
]
},
"Hourly Intelligence Trigger": {
"main": [
[
{
"node": "Ensure Delivery Ledger",
"type": "main",
"index": 0
}
]
]
},
"Prepare Delivered Post Keys": {
"main": [
[
{
"node": "Commit Delivered Posts",
"type": "main",
"index": 0
}
]
]
},
"Build One Intelligence Batch": {
"main": [
[
{
"node": "3. Analyze Telegram Posts in One Batch",
"type": "main",
"index": 0
}
]
]
},
"Validate Intelligence Results": {
"main": [
[
{
"node": "Has Relevant Intelligence",
"type": "main",
"index": 0
}
]
]
},
"Download Telegram Post Results": {
"main": [
[
{
"node": "Normalize Public Telegram Posts",
"type": "main",
"index": 0
}
]
]
},
"Normalize Public Telegram Posts": {
"main": [
[
{
"node": "Keep Undelivered Posts",
"type": "main",
"index": 0
}
]
]
},
"1. Configure Channel Intelligence": {
"main": [
[
{
"node": "Validate Channels and Build Actor Input",
"type": "main",
"index": 0
}
]
]
},
"Build Telegram Intelligence Digest": {
"main": [
[
{
"node": "5. Send Intelligence Digest to Telegram",
"type": "main",
"index": 0
}
]
]
},
"2. Run Telegram Channel Posts Scraper": {
"main": [
[
{
"node": "Download Telegram Post Results",
"type": "main",
"index": 0
}
]
]
},
"4. Save Intelligence to Google Sheets": {
"main": [
[
{
"node": "Build Telegram Intelligence Digest",
"type": "main",
"index": 0
}
]
]
},
"3. Analyze Telegram Posts in One Batch": {
"main": [
[
{
"node": "Validate Intelligence Results",
"type": "main",
"index": 0
}
]
]
},
"5. Send Intelligence Digest to Telegram": {
"main": [
[
{
"node": "Prepare Delivered Post Keys",
"type": "main",
"index": 0
}
]
]
},
"Validate Channels and Build Actor Input": {
"main": [
[
{
"node": "2. Run Telegram Channel Posts Scraper",
"type": "main",
"index": 0
}
]
]
}
}
}
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.
googleSheetsOAuth2ApihttpHeaderAuthopenAiApitelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow runs hourly (or manually) to scrape posts from public Telegram channels via Apify, batch-scores them with OpenAI against an intelligence goal, saves qualified items to Google Sheets, and sends a short digest to a Telegram chat while preventing duplicate delivery.…
Source: https://n8n.io/workflows/17734/ — 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.
Telegram Lead Gen - Apify Google Maps to Sheets. Uses telegramTrigger, telegram, httpRequest, openAi. Event-driven trigger; 25 nodes.
This workflow is designed for entrepreneurs, sales teams, marketers, and agencies who want to automate lead discovery and build qualified business contact lists — without manual searching or copying d
This workflow is Part 2 of the HR Client Acquisition system and builds on the lead discovery pipeline from the previous workflow:
This workflow runs on a schedule to scrape job listings via Apify (Google Jobs and Career Site API), scores each role against your base resume using OpenAI, generates tailored cover letter and optiona
Product - SERP Analysis (Serper + Firecrawl). Uses formTrigger, httpRequest, googleSheets, openAi. Event-driven trigger; 40 nodes.