This workflow follows the Agent → Execute Workflow Trigger 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 →
{
"name": "LinkedIn Growth & Intelligence Agent",
"nodes": [
{
"parameters": {
"chatId": "={{ $('Prep - Telegram').item.json.chat_id }}",
"text": "=\ud83d\udc4b *Hey! Welcome to LinkedIn Growth & Intelligence Agent*\n\n\ud83c\udfaf *What I do* \nI track public LinkedIn company data in real time and turn it into actionable insights for: \n\u2022 \ud83d\udd0e Smart B2B prospecting (opportunity scoring & next best actions) \n\u2022 \ud83d\udcca Competitive intelligence (hiring spikes, leadership changes, company pivots) \n\u2022 \ud83d\udca1 Content recommendations (top engaging posts & ideas you can reuse) \n\n\u2728 *Commands* \n\u2022 /prospect \u2192 analyze a LinkedIn company URL and score opportunities \n\u2022 /watch \u2192 start monitoring competitors and get alerts on key changes \n\u2022 /content \u2192 get weekly trending content + new post ideas \n\u2022 /clear \u2192 reset your current selection \n\u2022 /help \u2192 show usage tips \n\n\u2139\ufe0f *Good to know* \nOnly public LinkedIn data is processed. Insights are delivered straight to Slack/Telegram or your CRM.\n",
"additionalFields": {
"appendAttribution": false
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
-1248,
640
],
"id": "e1543155-4cd0-4476-b53d-816b1d23bb35",
"name": "/start",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"chatId": "={{ $('Prep - Telegram').item.json.chat_id }}",
"text": "=\ud83e\udd14 *How to use LinkedIn Growth & Intelligence Agent*\n\n\ud83d\udd0e *Step 1 \u2014 Send me a LinkedIn company URL* \nJust type or paste one a public LinkedIn company profiles, like: \nhttps://www.linkedin.com/company/openai/ \nhttps://www.linkedin.com/company/nvidia/ \n\n\ud83d\udee0\ufe0f *Step 2 \u2014 Commands* \n\u2022 /prospect \u2192 analyze the company you sent and score opportunities \n\u2022 /content \u2192 get trending posts & ideas for your own LinkedIn content \n\u2022 /clear \u2192 reset your current company list \n\u2022 /help \u2192 show this help message again \n\n\ud83d\udcca *What you\u2019ll get* \nFor each company, I\u2019ll provide: \n- Size, industry & followers \n- Hiring activity & growth signals \n- Engagement trends on recent posts \n- A *business opportunity score* + recommended next actions \n\n\u2139\ufe0f *Pro tip* \nOnly public LinkedIn data is used. Perfect for *B2B prospecting, competitive intelligence, and content strategy*.\n",
"additionalFields": {
"appendAttribution": false
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
-1472,
1168
],
"id": "616e5d58-0acc-4ffd-b700-5dc93bde4ebc",
"name": "/help",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"chatId": "={{ $('Prep - Telegram').item.json.chat_id }}",
"text": "=\ud83e\uddf9 *Selection cleared* \n\nSend me new LinkedIn company or profile URL, then use: \n\n\u2022 /prospect \u2192 analyze & score \n\u2022 /content \u2192 get content ideas\n",
"additionalFields": {
"appendAttribution": false
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
-1248,
832
],
"id": "ba68206f-6480-4d91-a315-6e81d350fc40",
"name": "/clear",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "765ed8db-7152-4c40-87b8-e0b9cfd3e80f",
"name": "chat_id",
"value": "={{ $json.message.chat.id }}",
"type": "string"
},
{
"id": "6afe501f-5edc-4c26-b482-f62748f2b245",
"name": "text",
"value": "={{ $json.message.text }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-2592,
1072
],
"id": "6618de56-7422-4a27-95df-7568eab561f5",
"name": "Prep - Telegram"
},
{
"parameters": {
"operation": "upsert",
"schema": {
"__rl": true,
"mode": "list",
"value": "public"
},
"table": {
"__rl": true,
"value": "linkedin_selection",
"mode": "list",
"cachedResultName": "linkedin_selection"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"chat_id": "={{ $json.chat_id }}",
"urls": "={{ $json.urls }}"
},
"matchingColumns": [
"chat_id"
],
"schema": [
{
"id": "chat_id",
"displayName": "chat_id",
"required": true,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "urls",
"displayName": "urls",
"required": false,
"defaultMatch": false,
"display": true,
"type": "array",
"canBeUsedToMatch": false
},
{
"id": "updated_at",
"displayName": "updated_at",
"required": false,
"defaultMatch": false,
"display": true,
"type": "dateTime",
"canBeUsedToMatch": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
-1920,
1168
],
"id": "7f59afeb-7a8f-4446-9500-70f9d1347b4b",
"name": "Upsert LinkedIn URLs",
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "deleteTable",
"schema": {
"__rl": true,
"mode": "list",
"value": "public"
},
"table": {
"__rl": true,
"value": "linkedin_selection",
"mode": "list",
"cachedResultName": "linkedin_selection"
},
"deleteCommand": "delete",
"where": {
"values": [
{
"column": "chat_id",
"value": "={{ $('Prep - Telegram').item.json.chat_id }}"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
-1472,
832
],
"id": "3f1ef9ab-a8af-415e-99d1-c3dd88de751d",
"name": "Delete stored chat",
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"leftValue": "={{ $('Prep - Telegram').item.json.text }}",
"rightValue": "/start",
"operator": {
"type": "string",
"operation": "equals"
},
"id": "a26ffeca-a4f9-43ef-83c7-0c6c0adaa733"
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "start"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "9f7c3717-89b2-4da6-8f9b-6893921eaea2",
"leftValue": "={{ $('Prep - Telegram').item.json.text }}",
"rightValue": "/clear",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "clear"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "64c4d192-61dc-4886-b8e0-16628b3ecada",
"leftValue": "={{ $('Prep - Telegram').item.json.text }}",
"rightValue": "/help",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "help"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "5ae6aa3e-2b59-4fd5-8688-abc31697bd5c",
"leftValue": "={{ $('Prep - Telegram').item.json.text }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "default"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.2,
"position": [
-1696,
1040
],
"id": "1dd96b17-8160-4c87-9b50-031d3aeadcc7",
"name": "Check for global commands"
},
{
"parameters": {
"chatId": "={{ $('Prep - Telegram').item.json.chat_id }}",
"text": "=\ud83d\udc4b *Welcome back!* \n\nI found *{{ $json.urls.length }} LinkedIn URL(s)* already saved for you. \n\nYou can now: \n\u2022 `/prospect` \u2192 analyze and score them \n\u2022 `/watch` \u2192 start monitoring for changes \n\u2022 `/content` \u2192 get top posts & new ideas \n\nOr send new LinkedIn URLs to update your selection.",
"additionalFields": {
"appendAttribution": false
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
-1248,
448
],
"id": "6dc20f46-b87c-4e70-b051-d80e9d5356c4",
"name": "welcome-back",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "34043973-4b4f-4b67-9989-0dd5c3af105a",
"leftValue": "={{ $json.urls.urls }}",
"rightValue": "",
"operator": {
"type": "array",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-1472,
448
],
"id": "2a9b258e-375c-469c-a085-21f821053396",
"name": "User has set URLS"
},
{
"parameters": {
"chatId": "={{ $('Prep - Telegram').item.json.chat_id }}",
"text": "=Please send public LinkedIn company URL first, then /prospect.",
"additionalFields": {
"appendAttribution": false
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
-1248,
1456
],
"id": "5931c1f8-00a1-44bc-9225-bf3f7b444d6d",
"name": "No URLs set by user",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Code \u00e0 utiliser dans un n\u0153ud Code n8n\nconst inputData = $('Has URLs Stored').all();\n\nconst result = inputData.map(item => {\n const data = item.json;\n \n // Transformer le tableau d'URLs en tableau d'objets\n const urlObjects = data.urls.map(url => ({\n url: url\n }));\n \n return {\n json: {\n urls: urlObjects\n }\n };\n});\n\nreturn result;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1024,
1216
],
"id": "1c0f5348-d845-400a-9504-dc36bfbafa69",
"name": "Code"
},
{
"parameters": {
"chatId": "={{ $('Prep - Telegram').item.json.chat_id }}",
"text": "=\ud83d\udd0e Analyzing {{$json.urls.length}} LinkedIn URL(s)\u2026 I\u2019ll send results here.",
"additionalFields": {
"appendAttribution": false
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
-1248,
1216
],
"id": "2a9db65c-526a-4931-b94f-29df84a29c1a",
"name": "Send Analyzing",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "b935ae85-a3d7-4cfb-a545-bbc92bee3cb4",
"leftValue": "={{ $json.urls }}",
"rightValue": "",
"operator": {
"type": "array",
"operation": "empty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-2144,
1072
],
"id": "986828da-f58e-424a-bc7e-5ef0727baabd",
"name": "If"
},
{
"parameters": {
"operation": "select",
"schema": {
"__rl": true,
"mode": "list",
"value": "public"
},
"table": {
"__rl": true,
"value": "linkedin_selection",
"mode": "list",
"cachedResultName": "linkedin_selection"
},
"limit": 1,
"where": {
"values": [
{
"column": "chat_id",
"value": "={{ $json.chat_id }}"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
-1920,
976
],
"id": "0f41e7e3-4de2-4938-8ec6-5b424c0b679e",
"name": "Select rows from a table",
"alwaysOutputData": true,
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"workflowInputs": {
"values": [
{
"name": "snapshot_id"
}
]
}
},
"type": "n8n-nodes-base.executeWorkflowTrigger",
"typeVersion": 1.1,
"position": [
-2816,
2160
],
"id": "956eadf4-cb21-4b8b-a294-89eafeb60e7e",
"name": "BrightData Scraper Sub-Workflow"
},
{
"parameters": {
"resource": "webScrapper",
"operation": "triggerCollectionByUrl",
"dataset_id": {
"__rl": true,
"value": "gd_l1vikfnt1wgvvqz95w",
"mode": "list",
"cachedResultName": "LinkedIn company information"
},
"urls": "={{ $json.urls.toJsonString() }}",
"requestOptions": {}
},
"type": "@brightdata/n8n-nodes-brightdata.brightData",
"typeVersion": 1,
"position": [
-800,
1216
],
"id": "fc279645-5c1f-4651-b04c-0eab1e09c97a",
"name": "Initiate batch extraction for Companies",
"credentials": {
"brightdataApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"workflowId": {
"__rl": true,
"value": "yNGc5OuaemnrXMaQ",
"mode": "list",
"cachedResultName": "LinkedIn Growth & Intelligence Agent"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {},
"matchingColumns": [],
"schema": [
{
"id": "snapshot_id",
"displayName": "snapshot_id",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "string",
"removed": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": true
},
"mode": "each",
"options": {
"waitForSubWorkflow": true
}
},
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [
-576,
1216
],
"id": "a20d7eba-47e5-4c40-90b1-c4f39d20df2d",
"name": "Execute Bright Data Sub workflow Loop for Companies"
},
{
"parameters": {
"options": {
"reset": true
}
},
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
-2592,
2160
],
"id": "c02bb61b-82e4-4c23-bf1c-86f8231d8f17",
"name": "Loop Over Items"
},
{
"parameters": {
"resource": "webScrapper",
"operation": "monitorProgressSnapshot",
"snapshot_id": "={{ $json.snapshot_id }}",
"requestOptions": {}
},
"type": "@brightdata/n8n-nodes-brightdata.brightData",
"typeVersion": 1,
"position": [
-2368,
2096
],
"id": "a27175df-9ca6-43aa-9d28-d861818847b2",
"name": "Check the status of a batch extraction",
"credentials": {
"brightdataApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {},
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [
-2144,
2096
],
"id": "cc2cfcee-f81e-4f15-83b1-9bcd53ed3883",
"name": "Wait 5 seconds"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "aaf6fec4-cbe7-4fa3-94c7-d169dcb83ac1",
"leftValue": "={{ $('Check the status of a batch extraction').item.json.status }}",
"rightValue": "ready",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-1920,
2096
],
"id": "49277942-0704-4638-872d-683fb92639d5",
"name": "Check if Batch ready"
},
{
"parameters": {
"resource": "webScrapper",
"operation": "downloadSnapshot",
"snapshot_id": "={{ $('Loop Over Items').item.json.snapshot_id }}",
"requestOptions": {}
},
"type": "@brightdata/n8n-nodes-brightdata.brightData",
"typeVersion": 1,
"position": [
-1696,
2048
],
"id": "3e583d1f-1d7d-4685-b90b-2bfae047cdbc",
"name": "Download the snapshot content",
"credentials": {
"brightdataApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {},
"type": "n8n-nodes-base.noOp",
"name": "Check Snapshot Again for Success",
"typeVersion": 1,
"position": [
-1696,
2256
],
"id": "d445bf9d-efed-4d25-b053-411f10185416"
},
{
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const cleanInt = (s) =>\n s == null ? null : (Number(String(s).replace(/[^0-9]/g, \"\")) || 0);\n\nconst asStr = (v, max = 4000) =>\n (v == null ? \"\" : String(v)).slice(0, max);\n\nconst source = $json;\nconst p0 = source || {};\n\nconst followers = cleanInt(p0.followers);\nconst sizeRange = p0.company_size || p0.size_range || null;\n\n// Map des posts depuis \"updates\" Bright Data\nconst posts = Array.isArray(p0.updates)\n ? p0.updates.map(u => ({\n id: u.post_id ?? null,\n date: u.date ?? null,\n text: asStr(u.text, 1500),\n likes: cleanInt(u.likes_count),\n comments: cleanInt(u.comments_count),\n post_url: u.post_url ?? null\n }))\n : [];\n\nlet jobsOpenProxy = null;\nif (typeof p0.additional_information === \"string\") {\n const m =\n p0.additional_information.match(/(\\d+)\\s+open jobs/i) ||\n p0.additional_information.match(/\\((\\d+)\\s+open jobs\\)/i);\n jobsOpenProxy = m ? Number(m[1]) : null;\n}\n\nreturn {\n sourceUrl: p0.input?.url || p0.url,\n name: p0.name,\n linkedin_url: p0.url,\n industry: p0.industries || p0.specialties || null,\n size_range: sizeRange,\n employee_count_est: cleanInt(p0.employees_in_linkedin),\n followers,\n description: asStr(p0.description),\n last_posts: posts,\n jobs_open_proxy: jobsOpenProxy,\n executives: p0.employees || [],\n metrics: {},\n};\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
96,
896
],
"id": "912a7c77-1955-4799-8b7c-efd579a95f54",
"name": "Normalizer"
},
{
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const j = $json;\nconst followers = j.followers || 1;\nconst posts = Array.isArray(j.last_posts) ? j.last_posts : [];\n\nconst now = new Date();\nconst D30 = 1000 * 60 * 60 * 24 * 30;\n\nconst recentPosts = posts.filter(p => {\n if (!p?.date) return false;\n const t = Date.parse(p.date);\n return Number.isFinite(t) && (now - t) <= D30;\n});\n\n// fallback: si aucun post horodat\u00e9 dans 30j, on prend les 10 plus r\u00e9cents\nconst postsForER = recentPosts.length\n ? recentPosts\n : posts.slice(0, 10);\n\nconst avgER = postsForER.length\n ? postsForER.reduce((s, p) => {\n const l = Number(p.likes || 0);\n const c = Number(p.comments || 0);\n const sh = Number(p.shares || 0);\n return s + (l + c + sh) / followers;\n }, 0) / postsForER.length\n : 0;\n\n// Sans la liste des jobs dat\u00e9s 30j, on renvoie 0 par d\u00e9faut\n// (on conserve jobs_open_proxy pour d'autres features/affichage)\nconst hiring30 = 0;\n\nreturn {\n ...j,\n metrics: {\n ...j.metrics,\n avg_eng_rate_30d: avgER,\n jobs_last_30d: hiring30,\n },\n features: {\n // Fit taille: favorise 100\u20131000 employ\u00e9s si employee_count_est dispo\n size_fit: (() => {\n const x = j.employee_count_est || 0;\n if (!x) return 0.5;\n if (x >= 100 && x <= 1000) return 1;\n return Math.max(0.2, Math.exp(-Math.abs(x - 500) / 500));\n })(),\n growth_headcount: 0.5, // \u00e0 am\u00e9liorer avec historique\n hiring_volume: Math.min(1, (j.jobs_open_proxy || 0) / 20),\n icp_relevance: 0.6, // \u00e0 ajuster selon ton ICP/mots-cl\u00e9s\n engagement_activity: Math.min(1, (postsForER.length / 8) * 0.5 + avgER * 0.5),\n leadership_change: 0,\n risk_noise: (j.followers < 500 ? 0.6 : 0.2),\n }\n};\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
320,
896
],
"id": "04df0561-34de-4ea1-bb77-71caad7b994b",
"name": "Metrics"
},
{
"parameters": {
"promptType": "define",
"text": "=You are a B2B sales intelligence assistant. \nAnalyze the company profile below and return a **strict JSON object**, matching the schema exactly. \nUse the data provided to generate a factual summary, identify post-related insights, and recommend sales actions. \nAdapt dynamically to the data (e.g., if no job postings, reflect that accordingly).\n\n---\n\nCompany data:\n- Name: {{$json.name}}\n- Industry: {{$json.industry}}\n- Size: {{$json.employee_count_est}} employees ({{$json.size_range}})\n- Followers: {{$json.followers}}\n- Jobs posted in last 30 days: {{$json.metrics.jobs_last_30d}}\n- Average engagement rate (30 days): {{$json.metrics.avg_eng_rate_30d}}\n- Description: {{$json.description}}\n- Recent posts: {{ JSON.stringify($json.last_posts.slice(0,3)) }}\n\n---\n\nSchema (valid JSON only, no markdown, no comments):\n\n{\n \"summary\": [\n \ud83e\udde0 *Industry*: {{$json.industry}} \n \ud83d\udc65 *Employees*: {{$json.employee_count_est}} (size range: {{$json.size_range}}) \n \ud83d\udce3 *Followers*: {{$json.followers}} \n \ud83d\udcca *Engagement Rate (30d)*: {{$json.metrics.avg_eng_rate_30d}}\n \ud83d\udcbc *Jobs (30 d)*: {{$json.metrics.jobs_last_30d}}\n ],\n \"post_insights\": [\n {\n \"key_themes\": [...],\n \"summary\": \"...\",\n \"potential_opportunity\": \"...\"\n }\n ],\n \"next_best_actions\": [\n {\n \"action\": \"...\",\n \"description\": \"...\"\n },\n {\n \"action\": \"...\",\n \"description\": \"...\"\n },\n {\n \"action\": \"...\",\n \"description\": \"...\"\n }\n ]\n}\n\n---\n\nInstructions:\n- Replace all placeholders with actual values from the input JSON.\n- In the `\"summary\"`, write one sentence about job activity:\n - If `jobs_last_30d > 0`: mention it as active hiring.\n - If `jobs_last_30d = 0`: mention absence of job postings and suggest possible internal focus.\n- In the `\"summary\"`, the value for `\"avg_eng_rate_30d\"` must be **rounded to 2 decimal places**.\n- `\"post_insights\"`: summarize themes in recent posts (e.g., product launches, geographic expansion, hiring signals, use cases).\n- `\"next_best_actions\"`: must be concrete and sales-relevant (LinkedIn outreach, email, engagement strategy, etc.).\n- Do not include any explanations, markdown, or formatting. Return JSON only.\n",
"hasOutputParser": true,
"options": {
"systemMessage": "You are a B2B Sales Intelligence Assistant.\n\nYour mission is to analyze raw company profiles (provided as structured JSON input) and return **only a JSON object**, strictly following a predefined schema.\n\n---\n\n\ud83c\udfaf Core task:\n- Extract actionable insights useful for B2B sales or prospecting.\n- Adapt dynamically to the presence or absence of data (e.g., hiring activity, post frequency).\n- Always include: a factual summary, key insights from recent social posts, and 3 high-value sales actions.\n\n---\n\n\ud83e\udde0 Behavior Rules:\n- Never ask questions.\n- Never return markdown, explanations, HTML, or comments.\n- Output **JSON only**. No introductory or closing sentences.\n- If a data field is missing or null, adapt the language accordingly (e.g., \"no recent job postings\").\n- Keep tone professional, concise, and neutral.\n- Output should be ready for use in a CRM, sales dashboard, or automation tool.\n\n---\n\n\ud83d\udcca Output JSON Schema:\nYour output must follow this structure, every time:\n\n1. `\"summary\"`: factual, readable overview based on the input.\n2. `\"post_insights\"`: extract key themes and signals from the latest social posts (launches, growth, partnerships, topics).\n3. `\"next_best_actions\"`: 3 actionable recommendations for sales outreach, tailored to the company's context.\n\n---\n\n\ud83e\udde9 Input Structure:\nEach request will provide:\n- A reminder of your role\n- A structured list of company data fields (name, size, industry, etc.)\n- A JSON schema defining the expected output\n- Instructions for dynamic formatting (e.g., conditional language based on hiring activity)\n\n---\n\n\ud83d\udeab Strict Do-Nots:\n- Never output \u201cHere is the JSON\u201d or similar.\n- Never include free-form explanation.\n- Never include markdown formatting or non-JSON content.\n\n---\n\n\ud83c\udfaf Goal:\nBe ready to integrate into Make.com, Slack, Airtable, Zapier, or a GPT-based assistant. \nYour output must be 100% clean, reusable, and directly usable by a person or system.\n\n"
}
},
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 2.2,
"position": [
544,
800
],
"id": "3fa484ae-1743-4177-b8d0-20fe71165cc3",
"name": "AI Agent"
},
{
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Scoring (n8n Function node)\n\n// 1) Pull canonical input from the Metrics node (has features + metrics)\nconst metricsNode = $('Metrics').first();\nif (!metricsNode) {\n // Hard fail-safe: no Metrics node found\n return {\n json: {\n error: \"Metrics node not found for scoring.\",\n score: 0,\n priority: \"C\"\n }\n };\n}\n\nconst canonical = metricsNode.json || {};\nconst features = canonical.features || {};\nconst nz = v => (v == null || v === '' ? 0 : Number(v));\n\nconst W = {\n size_fit: 0.18,\n growth_headcount: 0.22,\n hiring_volume: 0.15,\n icp_relevance: 0.20,\n engagement_activity: 0.15,\n leadership_change: 0.05,\n risk_noise: -0.05\n};\n\n// 2) Compute score 's' from features\nlet s = 0;\ns += W.size_fit * nz(features.size_fit);\ns += W.growth_headcount * nz(features.growth_headcount);\ns += W.hiring_volume * nz(features.hiring_volume);\ns += W.icp_relevance * nz(features.icp_relevance);\ns += W.engagement_activity * nz(features.engagement_activity);\ns += W.leadership_change * nz(features.leadership_change);\ns += W.risk_noise * nz(features.risk_noise);\n\n// 3) Normalize to 0..100, set priority\nconst score = Math.max(0, Math.min(100, Math.round(100 * s)));\nconst priority = score >= 75 ? 'A' : score >= 50 ? 'B' : 'C';\n\n// 4) Get current branch item (likely holds the AI Agent output)\nconst current = $json || {};\nconst aiOutput = current.output || current; // supports either {output:{...}} or the object itself\n\n// 5) Build a clean 'input' payload from Metrics (avoid echoing 'output' if present)\nconst { output: _drop, ...inputPayload } = canonical;\n\n// 6) Return final unified object\nreturn {\n json: {\n input: inputPayload, // original normalized data + metrics + features\n output: aiOutput, // AI Agent result\n score,\n priority\n }\n};"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
896,
896
],
"id": "75b19056-8a4f-4aa7-bd74-4c39c7b132f6",
"name": "Scoring"
},
{
"parameters": {
"jsCode": "function filterCompanyUrls(chatData) {\n // Check if input is valid and convert to array if needed\n if (!chatData) {\n console.log(\"No input data provided\");\n return [];\n }\n \n // Convert single object to array\n let dataArray = Array.isArray(chatData) ? chatData : [chatData];\n console.log(\"Processing data:\", dataArray);\n \n // Helper function to check if a URL is a company URL (more flexible patterns)\n function isCompanyUrl(url) {\n return url.includes('/company/');\n }\n \n // Helper function to clean company URLs (remove everything after company name)\n function cleanCompanyUrl(url) {\n if (!url.includes('/company/')) return url;\n \n // Find the company part and extract just the base company URL\n const companyMatch = url.match(/https?:\\/\\/[^\\/]+\\/company\\/[^\\/\\?#]+/);\n return companyMatch ? companyMatch[0] : url;\n }\n \n // Helper function to extract URLs from text\n function extractUrls(text) {\n if (typeof text !== 'string') return [];\n const urlRegex = /https?:\\/\\/[^\\s]+/g;\n const urls = text.match(urlRegex) || [];\n console.log(\"Found URLs:\", urls);\n const companyUrls = urls.filter(isCompanyUrl).map(cleanCompanyUrl);\n console.log(\"Cleaned company URLs:\", companyUrls);\n return companyUrls;\n }\n \n // Group data by chat_id and collect company URLs\n const result = {};\n \n dataArray.forEach(item => {\n const chatId = item.chat_id;\n const companyUrls = extractUrls(item.text);\n \n if (!result[chatId]) {\n result[chatId] = {\n chat_id: chatId,\n urls: []\n };\n }\n \n // Add company URLs to the existing array\n result[chatId].urls.push(...companyUrls);\n });\n \n // Convert result object to array\n return Object.values(result);\n}\n\nreturn filterCompanyUrls($input.first().json);\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-2368,
1072
],
"id": "fd4749a2-9ffd-4d82-b8fe-2b13c5cf6037",
"name": "Extract URLs"
},
{
"parameters": {
"schemaType": "manual",
"inputSchema": "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"title\": \"Company Engagement Schema\",\n \"type\": \"object\",\n \"properties\": {\n \"summary\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"post_insights\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"key_themes\": {\n \"type\": \"array\",\n \"items\": { \"type\": \"string\" }\n },\n \"summary\": { \"type\": \"string\" },\n \"potential_opportunity\": { \"type\": \"string\" }\n },\n \"required\": [\"key_themes\", \"summary\", \"potential_opportunity\"]\n }\n },\n \"next_best_actions\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"action\": { \"type\": \"string\" },\n \"description\": { \"type\": \"string\" }\n },\n \"required\": [\"action\", \"description\"]\n }\n }\n },\n \"required\": [\"summary\", \"next_best_actions\"]\n}\n"
},
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"typeVersion": 1.3,
"position": [
688,
1024
],
"id": "71069b27-216f-4bc8-8b58-200b42576bf4",
"name": "Structured Output Parser"
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"leftValue": "={{ $('Prep - Telegram').item.json.text }}",
"rightValue": "/prospect",
"operator": {
"type": "string",
"operation": "equals"
},
"id": "8d84603b-6201-4553-b55b-e001c7a3fe9f"
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "prospect"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "0bf8c52b-6e4d-4901-91a2-c8ad9e563124",
"leftValue": "={{ $('Prep - Telegram').item.json.text }}",
"rightValue": "/content",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "content"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "1c5d74e3-1258-4afd-9e6c-563191165768",
"leftValue": "={{ $('Prep - Telegram').item.json.text }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "other"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.2,
"position": [
-352,
1200
],
"id": "b843904b-0ffe-4c1b-b898-532738411790",
"name": "Switch"
},
{
"parameters": {
"jsCode": "const cleanInt = s => s==null ? null : (Number(String(s).replace(/[^0-9]/g,'')) || 0);\nconst asStr = (v, max=1500) => (v==null?'':String(v)).slice(0,max);\n\nconst json = $input.first().json;\n\n// Followers & URL\nconst followers = cleanInt(json.followers) || 0;\nconst F = followers > 0 ? followers : 1;\n\nconst postsRaw = Array.isArray(json.updates) ? json.updates : [];\n\n// Normalizerr by post + ER fallback (likes+comments+shares)/max(1,followers)\nconst posts = postsRaw.map(u => {\n const likes = cleanInt(u.likes_count ?? u.likes) || 0;\n const comments = cleanInt(u.comments_count ?? u.comments) || 0;\n\n const date = u.date || null;\n const er = (likes + comments) / F;\n\n return {\n id: u.post_id ?? u.id ?? null,\n date,\n text: asStr(u.text ?? u.text_html?.replace(/<[^>]*>/g,' ') ?? ''),\n likes, comments,\n engagement_rate: er,\n post_url: u.post_url ?? u.url ?? null,\n title: u.title ?? null,\n };\n});\n\nreturn [{\n json: {\n company: json.name || json.title || 'Company',\n followers: followers || null,\n linkedin_url: json.url || json.input?.url || null,\n posts\n }\n}];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-128,
1456
],
"id": "3e0bc147-15d7-4430-8f6a-3b3c112554b2",
"name": "Normalize Posts"
},
{
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const now = Date.now(), D30 = 1000*60*60*24*30;\nconst data = $json;\n\nconst parseTs = (s) => {\n const t = Date.parse(s || '');\n return Number.isFinite(t) ? t : null;\n};\n\n// filter last 30 days ; fallback = keep last 10\nconst recent = (data.posts || []).filter(p => {\n const t = parseTs(p.date);\n return t && (now - t) <= D30;\n});\n\nconst candidates = recent.length ? recent : (data.posts || []).slice(0, 10);\n\nfunction keywords(s){\n return (String(s||'').toLowerCase()\n .replace(/[^a-z0-9\\s]/g,' ')\n .split(/\\s+/)\n .filter(w => w.length >= 4)\n .slice(0,8));\n}\n\nconst used = new Set();\nconst top_posts = candidates\n .slice()\n .sort((a,b) => (b.engagement_rate||0) - (a.engagement_rate||0))\n .filter(p => {\n const key = (keywords(p.text).slice(0,2).join('|')) || 'misc';\n if (used.has(key)) return false;\n used.add(key);\n return true;\n })\n .slice(0,3);\n\nreturn { json: {\n ...data,\n recent_window_days: 30,\n top_posts,\n has_posts: !!top_posts.length\n}};\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
96,
1456
],
"id": "2e3b533d-1baa-4fdd-bcd5-e073d8346ed1",
"name": "Rank Posts"
},
{
"parameters": {
"promptType": "define",
"text": "=Company: {{$json.company}}\nFollowers: {{$json.followers}}\nWindow (days): {{$json.recent_window_days}}\n\nTopPosts (JSON):\n{{ JSON.stringify($json.top_posts) }}\n\nTask:\n1. Extract 3\u20135 short insights about what performs in these posts (themes, formats, angles).\n2. Generate 3\u20135 LinkedIn post ideas inspired by those posts (no copy-paste).\n3. Each idea must include: title, hook, angle, 3 bullets, CTA.\n4. Return valid JSON only, matching this structure:\n\n\nAdd company, recent_window_days and top_posts on output\nIf you cannot extract insights from the posts, write: [\"No insights available.\"]\nIf you cannot generate post ideas, write 3 ideas based on company\u2019s context alone.\n\n",
"hasOutputParser": true,
"options": {
"systemMessage": "You are a B2B LinkedIn content strategist.\n\nYour role:\n- Read the company profile and its top-performing LinkedIn posts.\n- Extract 3\u20135 key content insights.\n- Generate 3\u20135 new LinkedIn post ideas based on what worked, without copying.\n\nEach idea includes:\n- title\n- hook (max 120 chars)\n- angle\n- 3 short bullets\n- CTA (call to action)\n\nTone: expert, concise, realistic for LinkedIn.\n\nIf input is limited, still generate 3 ideas using only company info.\n\n\u26a0\ufe0f Return JSON only (no markdown, no prose). \n"
}
},
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 2.2,
"position": [
544,
1200
],
"id": "dba6068a-fc8f-43fb-b000-f2471e1b8098",
"name": "AI Ideas Content"
},
{
"parameters": {
"chatId": "={{ $('Prep - Telegram').item.json.chat_id }}",
"text": "=\u2139\ufe0f No recent public posts found.\nTry widening the window or adding more sources. You can still use /prospect for scoring.\n",
"additionalFields": {
"appendAttribution": false
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
608,
1600
],
"id": "e104f8a7-35d8-4eb0-84a5-457cfd6371a1",
"name": "Fallback",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"schemaType": "manual",
"inputSchema": "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"title\": \"Content Generation Output\",\n \"type\": \"object\",\n \"required\": [\"input\", \"insights\", \"post_ideas\"],\n \"properties\": {\n \"input\": {\n \"type\": \"object\",\n \"required\": [\"company\", \"followers\", \"recent_window_days\", \"top_posts\"],\n \"properties\": {\n \"company\": { \"type\": \"string\" },\n \"followers\": { \"type\": \"number\" },\n \"recent_window_days\": { \"type\": \"number\" },\n \"top_posts\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\"id\", \"date\", \"text\", \"likes\", \"comments\", \"engagement_rate\", \"post_url\", \"title\"],\n \"properties\": {\n \"id\": { \"type\": \"string\" },\n \"date\": { \"type\": \"string\", \"format\": \"date-time\" },\n \"text\": { \"type\": \"string\" },\n \"likes\": { \"type\": \"number\" },\n \"comments\": { \"type\": \"number\" },\n \"engagement_rate\": { \"type\": \"number\" },\n \"post_url\": { \"type\": \"string\", \"format\": \"uri\" },\n \"title\": { \"type\": \"string\" }\n }\n }\n }\n }\n },\n \"insights\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"items\": { \"type\": \"string\" }\n },\n \"post_ideas\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"items\": {\n \"type\": \"object\",\n \"required\": [\"title\", \"hook\", \"angle\", \"bullets\", \"cta\"],\n \"properties\": {\n \"title\": { \"type\": \"string\" },\n \"hook\": { \"type\": \"string\", \"maxLength\": 120 },\n \"angle\": { \"type\": \"string\" },\n \"bullets\": {\n \"type\": \"array\",\n \"minItems\": 3,\n \"maxItems\": 3,\n \"items\": { \"type\": \"string\" }\n },\n \"cta\": { \"type\": \"string\" }\n }\n }\n }\n }\n}\n"
},
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"typeVersion": 1.3,
"position": [
688,
1424
],
"id": "2c41a141-dd96-4958-ac87-01ae8d08664a",
"name": "Structured Output Parser1"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "ecacd325-3734-4558-bc7f-5ba8c27a2b80",
"leftValue": "={{ $json.has_posts }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
320,
1456
],
"id": "65bdbe0b-4283-40a8-b0c2-055b789aaa8a",
"name": "HasPost"
},
{
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-mini"
},
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.2,
"position": [
560,
1024
],
"id": "372746e3-8b6e-4ed2-9ca3-26381a204568",
"name": "OpenAI Chat Prospect",
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-mini"
},
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.2,
"position": [
560,
1424
],
"id": "c7470e38-fafc-4ff2-bd45-0631150f0be5",
"name": "OpenAI Chat Content",
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"updates": [
"message"
],
"additionalFields": {}
},
"type": "n8n-nodes-base.telegramTrigger",
"typeVersion": 1.2,
"position": [
-2816,
1072
],
"id": "913c6534-76aa-42d4-b24c-c506c3b19da8",
"name": "Telegram Trigger",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"chatId": "={{ $('Prep - Telegram').item.json.chat_id }}",
"text": "=\ud83d\udca1 *Content Digest \u2013 {{ $json.output.input.company }}*\n\n\ud83d\uddd3\ufe0f *Top Posts (last {{$json.output.input.recent_window_days}} days)*:\n{{ $json.output.input.top_posts.map((item) => {\n return `\u2022 ${item.date.slice(0,10)} ${item.text.slice(0,60)} \u2014 ER ${Number(item.engagement_rate).toFixed(3)}`\n}).join('\\n')}}\n\n\ud83d\udccc *Insights*:\n{{ $json.output.insights.map((item) => (`\u2022 ${item}`)).join('\\n')}}\n\n\n\ud83e\udde0 *Post Ideas*:\n{{ $json.output.post_ideas.map((item, index) => {\n return `${index + 1} *${item.title}*\n _Hook_: ${item.hook}\n _Angle_: ${item.angle}\n _Structure_:\n${item.bullets.map((item) => (` \u2022 ${item}`)).join('\\n')}\n _CTA_: ${item.cta}\n`\n}).join('\\n')}}\n\n",
"additionalFields": {
"appendAttribution": false
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
896,
1296
],
"id": "251a0199-416a-4c9d-85c4-9108ae744b89",
"name": "Send Content message",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"chatId": "={{ $('Prep - Telegram').item.json.chat_id }}",
"text": "=\ud83d\udccc *Company Profile Summary \u2013 {{ $('Normalizer').item.json.name }}*\n\n\u26a0\ufe0f *Priority Score*: *{{$json.priority}}*\n---\n\n{{ $json.output.summary.join('\\n') }}\n---\n\n\ud83c\udfaf *Recommended Next Actions*\n\n1\ufe0f\u20e3 *{{ $json.output.next_best_actions[0].action }}*\n{{ $json.output.next_best_actions[0].description }}\n\n2\ufe0f\u20e3 *{{ $json.output.next_best_actions[1].action }}*\n{{ $json.output.next_best_actions[1].description }}\n\n3\ufe0f\u20e3 *{{ $json.output.next_best_actions[2].action }}*\n{{ $json.output.next_best_actions[2].description }}\n\n\n",
"additionalFields": {
"appendAttribution": false
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
1120,
896
],
"id": "2ee76108-e2d6-485c-bf5e-5122e537f958",
"name": "Send Prospect message",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "34043973-4b4f-4b67-9989-0dd5c3af105a",
"leftValue": "={{ $json.urls }}",
"rightValue": "",
"operator": {
"type": "array",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-1472,
1360
],
"id": "043366fb-5e2e-4a05-81a9-837b19c2f75a",
"name": "Has URLs Stored"
},
{
"parameters": {
"operation": "executeQuery",
"query": "DROP TABLE IF EXISTS linkedin_selection;\nCREATE TABLE IF NOT EXISTS linkedin_selection (\n chat_id TEXT PRIMARY KEY,\n urls text[],\n updated_at TIMESTAMPTZ DEFAULT now()\n);",
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
-2432,
1632
],
"id": "d0549907-1157-4a5c-ba75-acf38c710b5d",
"name": "Initialize database tables",
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"content": "## \ud83c\udfaf Purpose: Initialize database tables for the Growth Radar bot\n\n- Creates table `linkedin_selection` if it doesn\u2019t exist\n- Stores mapping: { chat_id, urls, last_updated }\n- Ensures session persistence across Telegram messages\n- Run once at workflow start (safe to re-run, idempotent)\n\n\u26a0\ufe0f Important: Only stores *public LinkedIn company URLs*",
"height": 256,
"width": 672,
"color": 3
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-2928,
1536
],
"id": "72139119-3bfb-470a-8a25-904e5972b7ea",
"name": "Sticky Note"
},
{
"parameters": {
"content": "## \ud83d\udd04 Purpose: Wait until Bright Data snapshot is ready\n\n- Polls snapshot status every 5s\n- If status != \"ready\" \u2192 loop continues\n- Once ready \u2192 downloads snapshot JSON\n- Ensures we always get complete & valid data\n\n\ud83d\udca1 Could be extended with exponential backoff",
"height": 528,
"width": 1472,
"color": 5
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-2944,
1952
],
"id": "39f64c89-8e7c-41b1-b2ee-730474006db3",
"name": "Sticky Note1"
},
{
"parameters": {
"content": "## \ud83e\udd16 Purpose: Transform Bright Data results into actionable insights\n\n- Uses Structured Output Parser with enforced JSON schema:\n {\n \"summary\": [string],\n \"post_insights\": [ { \"text\": string, \"engagement_rate\": number } ],\n \"next_best_actions\": [string]\n }\n\n- Ensures LLM always returns machine-parseable JSON\n- Adds robustness: downstream nodes (Scoring, Telegram Output) consume clean data\n\n\u26a0\ufe0f Fallback handling: if schema fails, default empty arrays are used",
"height": 2160,
"width": 4336
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-2976,
352
],
"id": "54d807ac-0832-4e0b-af75-2bea2af17f31",
"name": "Sticky Note2"
}
],
"connections": {
"Prep - Telegram": {
"main": [
[
{
"node": "Extract URLs",
"type": "main",
"index": 0
}
]
]
},
"Upsert LinkedIn URLs": {
"main": [
[
{
"node": "Check for global commands",
"type": "main",
"index": 0
}
]
]
},
"Delete stored chat": {
"main": [
[
{
"node": "/clear",
"type": "main",
"index": 0
}
]
]
},
"Check for global commands": {
"main": [
[
{
"node": "User has set URLS",
"type": "main",
"index": 0
}
],
[
{
"node": "Delete stored chat",
"type": "main",
"index": 0
}
],
[
{
"node": "/help",
"type": "main",
"index": 0
}
],
[
{
"node": "Has URLs Stored",
"type": "main",
"index": 0
}
]
]
},
"User has set URLS": {
"main": [
[
{
"node": "welcome-back",
"type": "main",
"index": 0
}
],
[
{
"node": "/start",
"type": "main",
"index": 0
}
]
]
},
"/help": {
"main": [
[]
]
},
"Code": {
"main": [
[
{
"node": "Initiate batch extraction for Companies",
"type": "main",
"index": 0
}
]
]
},
"Send Analyzing": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"If": {
"main": [
[
{
"node": "Select rows from a table",
"type": "main",
"index": 0
}
],
[
{
"node": "Upsert LinkedIn URLs",
"type": "main",
"index": 0
}
]
]
},
"Select rows from a table": {
"main": [
[
{
"node": "Check for global commands",
"type": "main",
"index": 0
}
]
]
},
"BrightData Scraper Sub-Workflow": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Initiate batch extraction for Companies": {
"main": [
[
{
"node": "Execute Bright Data Sub workflow Loop for Companies",
"type": "main",
"index": 0
}
]
]
},
"Execute Bright Data Sub workflow Loop for Companies": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[],
[
{
"node": "Check the status of a batch extraction",
"type": "main",
"index": 0
}
]
]
},
"Check the status of a batch extraction": {
"main": [
[
{
"node": "Wait 5 seconds",
"type": "main",
"index": 0
}
]
]
},
"Wait 5 seconds": {
"main": [
[
{
"node": "Check if Batch ready",
"type": "main",
"index": 0
}
]
]
},
"Check if Batch ready": {
"main": [
[
{
"node": "Download the snapshot content",
"type": "main",
"index": 0
}
],
[
{
"node": "Check Snapshot Again for Success",
"type": "main",
"index": 0
}
]
]
},
"Check Snapshot Again for Success": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Normalizer": {
"main": [
[
{
"node": "Metrics",
"type": "main",
"index": 0
}
]
]
},
"Metrics": {
"main": [
[
{
"node": "AI Agent",
"type": "main",
"index": 0
}
]
]
},
"AI Agent": {
"main": [
[
{
"node": "Scoring",
"type": "main",
"index": 0
}
]
]
},
"Scoring": {
"main": [
[
{
"node": "Send Prospect message",
"type": "main",
"index": 0
}
]
]
},
"Extract URLs": {
"main": [
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
},
"Structured Output Parser": {
"ai_outputParser": [
[
{
"node": "AI Agent",
"type": "a
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.
brightdataApiopenAiApipostgrestelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
LinkedIn Growth & Intelligence Agent. Uses telegram, postgres, executeWorkflowTrigger, @brightdata/n8n-nodes-brightdata. Event-driven trigger; 46 nodes.
Source: https://gist.github.com/synapz-fr/c80507e189a5213df021665ad6f32e7c — 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.
Template Carnaval - time instagram. Uses toolWorkflow, lmChatOpenAi, memoryBufferWindow, agent. Event-driven trigger; 56 nodes.
6_Multi-Agent_4vaEvzlaMrgovhNz. Uses postgres, httpRequest, lmChatOpenAi, outputParserStructured. Event-driven trigger; 54 nodes.
This workflow contains community nodes that are only compatible with the self-hosted version of n8n.
Analysis Anna. Uses httpRequest, memoryBufferWindow, telegram, outputParserStructured. Event-driven trigger; 38 nodes.
Analysis Anna. Uses httpRequest, memoryBufferWindow, telegram, outputParserStructured. Event-driven trigger; 38 nodes.