This workflow corresponds to n8n.io template #15031 — we link there as the canonical source.
This workflow follows the Google Drive → Google Drive 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 →
{
"id": "mEWNdUEQoGDdVJxX",
"name": "RAG Telegram Bot with Google Drive & Local LLM (Ollama)",
"tags": [],
"nodes": [
{
"id": "eb336315-3849-4708-b53d-403bbb396080",
"name": "\u2699\ufe0f Setup Instructions",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1520,
1088
],
"parameters": {
"color": "#8A9532",
"width": 640,
"height": 468,
"content": "## Setup Checklist\n\nStep 1 \u2014 Credentials\nAdd your credentials in n8n Settings \u2192 Credentials:\n\u2705 Telegram API \u2014 your bot token from @BotFather\n\u2705 PostgreSQL \u2014 your Supabase (or any Postgres) connection\n\u2705 Google Drive OAuth2 \u2014 connect your Google account\n\nStep 2 \u2014 Google Drive Trigger\nOpen the `Google Drive Trigger` node and select the folder you want to monitor for new documents.\n\nStep 3 \u2014 PIN Code\nIn the `Register New User` node, change `'1234'` in the SQL to your desired access PIN. All new users will need this PIN to access the bot.\n\nStep 4 \u2014 Ollama\nEnsure Ollama is running and accessible. If using Docker, it must be at `http://host.docker.internal:11434`.\nPull required models:\nollama pull nomic-embed-text\nollama pull qwen2.5:7b\n\nStep 5 \u2014 Activate\nActivate the workflow. Send `/start` to your Telegram bot!"
},
"typeVersion": 1
},
{
"id": "c25280fe-8551-457b-bc87-59519454bef9",
"name": "\ud83d\udccc Change Default PIN",
"type": "n8n-nodes-base.stickyNote",
"position": [
128,
-128
],
"parameters": {
"color": "#6E6E6E",
"width": 280,
"height": 100,
"content": "\u26a0\ufe0f **Change the PIN** from `1234` to your own secret code in the SQL query below."
},
"typeVersion": 1
},
{
"id": "79a090e8-bb02-4c60-a028-14ddc1a4b99a",
"name": "\ud83d\udccc Set Your Folder",
"type": "n8n-nodes-base.stickyNote",
"position": [
-576,
1216
],
"parameters": {
"color": 2,
"width": 280,
"height": 80,
"content": "\u26a0\ufe0f **Select your Google Drive folder** to watch in this trigger node."
},
"typeVersion": 1
},
{
"id": "14aa1ca3-4cf3-473b-8b90-d063e7a2d2b3",
"name": "Telegram Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
-640,
160
],
"parameters": {
"path": "rag-telegram-bot",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2
},
{
"id": "150b2bf2-3ead-4fbd-928d-761027d6e774",
"name": "Get User",
"type": "n8n-nodes-base.postgres",
"position": [
-416,
160
],
"parameters": {
"query": "SELECT chat_id, username, pin_code, is_verified, pin_attempts FROM telegram_users WHERE chat_id = '={{ $('Telegram Webhook').first().json.body.message.chat.id }}'",
"options": {},
"operation": "executeQuery"
},
"typeVersion": 2.5,
"alwaysOutputData": true
},
{
"id": "6263d211-5595-4bce-9ca0-24bbc5b24b2f",
"name": "User State Router",
"type": "n8n-nodes-base.code",
"position": [
-192,
160
],
"parameters": {
"jsCode": "const items = $input.all();\nconst tgData = $('Telegram Webhook').first().json.body;\nif (!tgData || !tgData.message) {\n return [{json: {state: 'ignore', chatId: '', text: '', username: ''}}];\n}\nconst chatId = String(tgData.message.chat.id);\nconst text = (tgData.message.text || '').trim();\nconst username = (tgData.message.from && tgData.message.from.username) ? tgData.message.from.username : '';\nif (items.length === 0 || !items[0].json.chat_id) {\n const r = {state: 'new', chatId: chatId, text: text, username: username};\n return [{json: r}];\n}\nconst user = items[0].json;\nif (user.is_verified) {\n const r = {state: 'verified', chatId: chatId, text: text, username: username};\n return [{json: r}];\n}\nconst r = {state: 'unverified', chatId: chatId, text: text, username: username, pin_code: String(user.pin_code || ''), pin_attempts: Number(user.pin_attempts || 0)};\nreturn [{json: r}];"
},
"typeVersion": 2
},
{
"id": "62807390-906b-46f5-9e05-eac9b01422aa",
"name": "Is New User?",
"type": "n8n-nodes-base.if",
"position": [
32,
160
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "or",
"conditions": [
{
"id": "1",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.state }}",
"rightValue": "new"
},
{
"id": "2",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.state }}",
"rightValue": "ignore"
}
]
}
},
"typeVersion": 2
},
{
"id": "da7ffc6f-b24b-433d-b585-319d373c7f48",
"name": "Register New User",
"type": "n8n-nodes-base.postgres",
"position": [
256,
0
],
"parameters": {
"query": "INSERT INTO telegram_users (chat_id, username, pin_code, is_verified, pin_attempts, created_at, updated_at) VALUES ('={{ $json.chatId }}', '={{ $json.username }}', 'YOUR_PIN_CODE', false, 0, NOW(), NOW()) ON CONFLICT (chat_id) DO UPDATE SET updated_at = NOW() RETURNING chat_id",
"options": {},
"operation": "executeQuery"
},
"typeVersion": 2.5
},
{
"id": "848711d5-f901-4b33-9c33-ad8169d3d32a",
"name": "Send PIN Request",
"type": "n8n-nodes-base.telegram",
"maxTries": 3,
"position": [
480,
-32
],
"parameters": {
"text": "Welcome! To access document search, please enter the PIN code:",
"chatId": "={{ $('User State Router').first().json.chatId }}",
"additionalFields": {}
},
"retryOnFail": true,
"typeVersion": 1.2,
"waitBetweenTries": 2000
},
{
"id": "92e2cb25-a441-4121-b464-ebb45332ef31",
"name": "Is Verified?",
"type": "n8n-nodes-base.if",
"position": [
256,
160
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "cond-verified",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.state }}",
"rightValue": "verified"
}
]
}
},
"typeVersion": 2
},
{
"id": "c70f2339-20ad-456f-afd9-c7b5e8ff8c8f",
"name": "Has Text?",
"type": "n8n-nodes-base.if",
"position": [
480,
160
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "cond-text",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.text }}"
}
]
}
},
"typeVersion": 2
},
{
"id": "3b7a1e3a-316f-4aea-8d32-70a3a4b753f6",
"name": "No Text Message",
"type": "n8n-nodes-base.telegram",
"maxTries": 3,
"position": [
704,
-32
],
"parameters": {
"text": "Please send a text message to search the documents.",
"chatId": "={{ $('User State Router').first().json.chatId }}",
"additionalFields": {}
},
"retryOnFail": true,
"typeVersion": 1.2,
"waitBetweenTries": 2000
},
{
"id": "d95fdcb4-0904-4f1c-af33-3c6cef4dc628",
"name": "Embed Query",
"type": "n8n-nodes-base.httpRequest",
"position": [
704,
160
],
"parameters": {
"url": "http://host.docker.internal:11434/api/embed",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify({model: 'nomic-embed-text', input: $json.text}) }}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.2
},
{
"id": "51d25cb0-a0a9-44c6-9103-9fafeab9e30f",
"name": "Format Query Embedding",
"type": "n8n-nodes-base.code",
"position": [
928,
160
],
"parameters": {
"jsCode": "const item = $input.first();\nconst embedding = item.json.embeddings[0];\nconst vectorStr = '[' + embedding.join(',') + ']';\nconst userState = $('User State Router').first().json;\nreturn [{json: {vectorStr: vectorStr, queryVec: embedding, chatId: userState.chatId, text: userState.text, username: userState.username}}];"
},
"typeVersion": 2
},
{
"id": "19a8f67a-7f69-4aca-9386-2cb1adab5d38",
"name": "Search Documents",
"type": "n8n-nodes-base.postgres",
"position": [
1136,
160
],
"parameters": {
"query": "SELECT chunk_id, chunk_text, file_name, embedding::text AS embedding_text FROM document_chunks",
"options": {},
"operation": "executeQuery"
},
"typeVersion": 2.5,
"alwaysOutputData": false
},
{
"id": "af541a86-b3a3-4316-b577-93ee0bcfc100",
"name": "Build Prompt",
"type": "n8n-nodes-base.code",
"position": [
1360,
160
],
"parameters": {
"jsCode": "const chunks = $input.all();\nconst userState = $('User State Router').first().json;\nconst queryVec = $('Format Query Embedding').first().json.queryVec;\nconst question = userState.text;\nconst chatId = userState.chatId;\n\nfunction cosineSim(a, b) {\n let dot = 0, na = 0, nb = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n na += a[i] * a[i];\n nb += b[i] * b[i];\n }\n return dot / (Math.sqrt(na) * Math.sqrt(nb));\n}\n\nconst scored = [];\nfor (const c of chunks) {\n const embText = c.json.embedding_text || '';\n const cleaned = embText.startsWith('=') ? embText.slice(1) : embText;\n try {\n const vec = JSON.parse(cleaned);\n const sim = cosineSim(queryVec, vec);\n scored.push({chunk_text: c.json.chunk_text || '', file_name: c.json.file_name || '', similarity: sim});\n } catch(e) {}\n}\nscored.sort((a, b) => b.similarity - a.similarity);\nconst top5 = scored.slice(0, 5);\n\nlet context = '';\nfor (const c of top5) {\n let t = c.chunk_text;\n if (t.startsWith('=')) t = t.slice(1);\n if (t.trim()) context += t + '\\n\\n';\n}\nif (!context.trim()) context = 'No relevant documents found.';\n\nconst messages = [\n {role: 'system', content: 'You are a helpful assistant. Answer questions based on the provided context only. If the answer is not in the context, say you do not have that information.'},\n {role: 'user', content: 'Context:\\n' + context + '\\n\\nQuestion: ' + question}\n];\nconst body = JSON.stringify({model: 'qwen2.5:7b', messages: messages, stream: false});\nreturn [{json: {messages: messages, body: body, chatId: chatId, question: question, matchCount: top5.length}}];"
},
"typeVersion": 2
},
{
"id": "f185f20c-519f-4847-8884-d5afd1624505",
"name": "Ask LLM",
"type": "n8n-nodes-base.httpRequest",
"position": [
1584,
160
],
"parameters": {
"url": "http://host.docker.internal:11434/api/chat",
"method": "POST",
"options": {},
"jsonBody": "={{ $json.body }}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.2
},
{
"id": "32589a75-ef55-4a5a-adcf-3f602d5018a7",
"name": "Send Answer",
"type": "n8n-nodes-base.telegram",
"maxTries": 3,
"position": [
1808,
160
],
"parameters": {
"text": "={{ $json.message.content }}",
"chatId": "={{ $('Build Prompt').first().json.chatId }}",
"additionalFields": {}
},
"retryOnFail": true,
"typeVersion": 1.2,
"waitBetweenTries": 2000
},
{
"id": "4afd3214-3beb-4b91-add3-607de1fa65ea",
"name": "Log Query",
"type": "n8n-nodes-base.postgres",
"position": [
2016,
160
],
"parameters": {
"query": "INSERT INTO query_log (chat_id, username, query, answer, match_count) VALUES ('{{ $('Build Prompt').first().json.chatId }}', '{{ $('User State Router').first().json.username }}', '{{ $('User State Router').first().json.text.replace(/'/g, \"''\") }}', '{{ $('Ask LLM').first().json.message.content.replace(/'/g, \"''\") }}', {{ $('Build Prompt').first().json.matchCount }})",
"options": {},
"operation": "executeQuery"
},
"typeVersion": 2.5
},
{
"id": "f3cd3724-a613-4319-bc18-9db07356fd8f",
"name": "PIN Correct?",
"type": "n8n-nodes-base.if",
"position": [
480,
368
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "cond-pin",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.text }}",
"rightValue": "={{ $json.pin_code }}"
}
]
}
},
"typeVersion": 2
},
{
"id": "f4bf6313-20c0-459a-bbdb-c8f0c465cc60",
"name": "Mark User Verified",
"type": "n8n-nodes-base.postgres",
"position": [
704,
368
],
"parameters": {
"query": "UPDATE telegram_users SET is_verified = true, verified_at = NOW(), updated_at = NOW() WHERE chat_id = '={{ $json.chatId }}' RETURNING chat_id",
"options": {},
"operation": "executeQuery"
},
"typeVersion": 2.5
},
{
"id": "056671c9-8994-41c2-bc59-7fb2882660d5",
"name": "Send Welcome",
"type": "n8n-nodes-base.telegram",
"maxTries": 3,
"position": [
928,
368
],
"parameters": {
"text": "You are now verified! Send me any question to search the documents.",
"chatId": "={{ $('User State Router').first().json.chatId }}",
"additionalFields": {}
},
"retryOnFail": true,
"typeVersion": 1.2,
"waitBetweenTries": 2000
},
{
"id": "54c70d97-b841-4f9e-9cbf-137ebe24fb93",
"name": "Wrong PIN Message",
"type": "n8n-nodes-base.telegram",
"maxTries": 3,
"position": [
704,
560
],
"parameters": {
"text": "Incorrect PIN. Please try again.",
"chatId": "={{ $('User State Router').first().json.chatId }}",
"additionalFields": {}
},
"retryOnFail": true,
"typeVersion": 1.2,
"waitBetweenTries": 2000
},
{
"id": "ce78c2f7-286b-4277-8486-4548e15eb464",
"name": "Google Drive Trigger",
"type": "n8n-nodes-base.googleDriveTrigger",
"position": [
-576,
1040
],
"parameters": {
"event": "fileCreated",
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"triggerOn": "specificFolder",
"folderToWatch": {
"__rl": true,
"mode": "list",
"value": ""
}
},
"typeVersion": 1
},
{
"id": "cc19368e-0b43-4894-8776-a383e2073b37",
"name": "Download File",
"type": "n8n-nodes-base.googleDrive",
"position": [
-384,
1040
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"options": {},
"operation": "download"
},
"typeVersion": 3
},
{
"id": "99c24f92-3ca5-4746-95c1-29c774ff11f2",
"name": "Extract Text",
"type": "n8n-nodes-base.code",
"position": [
-160,
1040
],
"parameters": {
"jsCode": "const fileData = $input.first();\nconst triggerData = $('Google Drive Trigger').first().json;\nconst fileName = triggerData.name || 'unknown';\nconst fileId = triggerData.id || '';\n\nlet text = '';\ntry {\n const binaryKeys = Object.keys(fileData.binary || {});\n if (binaryKeys.length > 0) {\n const buf = await $helpers.getBinaryDataBuffer(binaryKeys[0]);\n text = buf.toString('utf-8');\n }\n} catch(e) {\n text = '';\n}\n\nconst r = {text: text, fileName: fileName, fileId: fileId, driveFileId: fileId};\nreturn [{json: r}];"
},
"typeVersion": 2
},
{
"id": "edbaac02-ec81-4dd9-9c3f-0dac7831e708",
"name": "Split Into Chunks",
"type": "n8n-nodes-base.code",
"position": [
48,
1040
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const text = $json.text;\nconst fileName = $json.fileName;\nconst fileId = $json.fileId;\nconst driveFileId = $json.driveFileId;\nconst chunkSize = 500;\nconst overlap = 50;\n\nif (!text || text.length === 0) return [];\n\nconst chunks = [];\nlet i = 0;\nlet chunkIndex = 0;\n\nwhile (i < text.length) {\n const end = Math.min(i + chunkSize, text.length);\n const chunkText = text.slice(i, end).trim();\n if (chunkText.length > 10) {\n const c = {chunk_id: fileId + '_' + chunkIndex, file_id: fileId, file_name: fileName, drive_file_id: driveFileId, chunk_index: chunkIndex, chunk_text: chunkText};\n chunks.push({json: c});\n chunkIndex++;\n }\n i += chunkSize - overlap;\n}\n\nreturn chunks;"
},
"typeVersion": 2
},
{
"id": "6c6a50af-6d0b-4fa6-aefd-397170f547af",
"name": "Loop Chunks",
"type": "n8n-nodes-base.splitInBatches",
"position": [
272,
1040
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "682f33a6-1c5b-43eb-8d7f-d5a5eb5d30bd",
"name": "Embed Chunk",
"type": "n8n-nodes-base.httpRequest",
"position": [
480,
1024
],
"parameters": {
"url": "http://host.docker.internal:11434/api/embed",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify({model: 'nomic-embed-text', input: $json.chunk_text}) }}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.2
},
{
"id": "d53b1be9-a611-4907-ba25-678ca33e2646",
"name": "Format Chunk Embedding",
"type": "n8n-nodes-base.code",
"position": [
720,
1024
],
"parameters": {
"jsCode": "const item = $input.first();\nconst embedding = item.json.embeddings[0];\nconst vectorStr = '[' + embedding.join(',') + ']';\nconst chunk = $('Loop Chunks').first().json;\nreturn [{json: {chunk_id: chunk.chunk_id, file_id: chunk.file_id, file_name: chunk.file_name, chunk_index: chunk.chunk_index, chunk_text: chunk.chunk_text, drive_file_id: chunk.drive_file_id, vectorStr: vectorStr}}];"
},
"typeVersion": 2
},
{
"id": "ee7f7eae-4c0e-4b5b-bd5d-04d1dc5dd51d",
"name": "Store Chunk",
"type": "n8n-nodes-base.postgres",
"position": [
928,
1024
],
"parameters": {
"query": "INSERT INTO document_chunks (chunk_id, file_id, file_name, chunk_index, chunk_text, embedding) VALUES ('={{ $json.chunk_id }}', '={{ $json.file_id }}', '={{ $json.file_name }}', {{ $json.chunk_index }}, '={{ $json.chunk_text.replace(\"'\", \"''\") }}', '={{ $json.vectorStr }}'::vector) ON CONFLICT (chunk_id) DO UPDATE SET chunk_text = EXCLUDED.chunk_text, embedding = EXCLUDED.embedding",
"options": {},
"operation": "executeQuery"
},
"typeVersion": 2.5
},
{
"id": "c544da09-f14c-4e79-93ad-bd1bcdb9bbbe",
"name": "Log Ingestion",
"type": "n8n-nodes-base.postgres",
"position": [
544,
1296
],
"parameters": {
"query": "INSERT INTO ingestion_log (file_id, file_name, drive_file_id, status) VALUES ($1, $2, $3, 'completed') ON CONFLICT (file_id) DO UPDATE SET status = 'completed', ingested_at = NOW()",
"options": {},
"operation": "executeQuery"
},
"typeVersion": 2.5
},
{
"id": "a2114b9e-ad3f-45aa-ba94-101b15aba09e",
"name": "\ud83d\udccb Template Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1520,
-208
],
"parameters": {
"color": "#6D6B37",
"width": 640,
"height": 1268,
"content": "## RAG Telegram Bot with Local LLM Ollama\n\nWho is this for?\nTeams or individuals who want a private, PIN-protected Telegram chatbot that answers questions from their own documents powered entirely by a local LLM stack with no external AI API costs.\n\nWhat You Need\nTelegram Bot (via @BotFather)\nPostgreSQL with pgvector (e.g. Supabase free tier)\nOllama running locally with `nomic-embed-text` + `qwen2.5:7b`\nGoogle Drive account\n\nHow It Works\n\nDocument Ingestion (bottom flow)\n1. New file added to your Google Drive folder\n2. File downloaded & text extracted\n3. Text split into 500-char chunks (50-char overlap)\n4. Each chunk embedded via Ollama `nomic-embed-text`\n5. Embeddings stored in PostgreSQL with pgvector\n\nTelegram Bot (top flow)\n1. User sends a message to the bot\n2. New users are registered and asked for a PIN\n3. Unverified users must enter the correct PIN\n4. Verified users ask questions in plain text\n5. Question is embedded \u2192 top-5 document chunks retrieved via cosine similarity\n6. `qwen2.5:7b` generates a context-aware answer\n7. Answer sent back to the user via Telegram\n8. Query logged to database for audit\n\nRequired PostgreSQL Tables\n\nRun these before activating:\nsql\nCREATE EXTENSION IF NOT EXISTS vector;\n\nCREATE TABLE telegram_users (\n chat_id TEXT PRIMARY KEY,\n username TEXT, pin_code TEXT,\n is_verified BOOLEAN DEFAULT false,\n pin_attempts INT DEFAULT 0,\n verified_at TIMESTAMP,\n created_at TIMESTAMP DEFAULT NOW(),\n updated_at TIMESTAMP DEFAULT NOW()\n);\n\nCREATE TABLE document_chunks (\n chunk_id TEXT PRIMARY KEY,\n file_id TEXT, file_name TEXT,\n chunk_index INT,\n chunk_text TEXT,\n embedding vector(768)\n);\n\nCREATE TABLE query_log (\n id SERIAL PRIMARY KEY,\n chat_id TEXT, username TEXT,\n query TEXT, answer TEXT,\n match_count INT,\n created_at TIMESTAMP DEFAULT NOW()\n);\n\nCREATE TABLE ingestion_log (\n file_id TEXT PRIMARY KEY,\n file_name TEXT, drive_file_id TEXT,\n status TEXT,\n ingested_at TIMESTAMP DEFAULT NOW()\n);"
},
"typeVersion": 1
},
{
"id": "0202ce0f-f466-4513-988e-2644011a36c8",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-704,
-144
],
"parameters": {
"color": "#6E6E6E",
"width": 2912,
"height": 944,
"content": "## Telegram RAG Bot Flow"
},
"typeVersion": 1
},
{
"id": "387269d8-e5e3-4944-9c3b-4ecbc17e5d92",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-704,
928
],
"parameters": {
"color": "#6E6E6E",
"width": 1856,
"height": 608,
"content": "## Document Ingestion Flow"
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "1135a0b9-356d-41e6-8f6a-7413a37e09ac",
"connections": {
"Ask LLM": {
"main": [
[
{
"node": "Send Answer",
"type": "main",
"index": 0
}
]
]
},
"Get User": {
"main": [
[
{
"node": "User State Router",
"type": "main",
"index": 0
}
]
]
},
"Has Text?": {
"main": [
[
{
"node": "Embed Query",
"type": "main",
"index": 0
}
],
[
{
"node": "No Text Message",
"type": "main",
"index": 0
}
]
]
},
"Embed Chunk": {
"main": [
[
{
"node": "Format Chunk Embedding",
"type": "main",
"index": 0
}
]
]
},
"Embed Query": {
"main": [
[
{
"node": "Format Query Embedding",
"type": "main",
"index": 0
}
]
]
},
"Loop Chunks": {
"main": [
[
{
"node": "Embed Chunk",
"type": "main",
"index": 0
}
],
[
{
"node": "Log Ingestion",
"type": "main",
"index": 0
}
]
]
},
"Send Answer": {
"main": [
[
{
"node": "Log Query",
"type": "main",
"index": 0
}
]
]
},
"Store Chunk": {
"main": [
[
{
"node": "Loop Chunks",
"type": "main",
"index": 0
}
]
]
},
"Build Prompt": {
"main": [
[
{
"node": "Ask LLM",
"type": "main",
"index": 0
}
]
]
},
"Extract Text": {
"main": [
[
{
"node": "Split Into Chunks",
"type": "main",
"index": 0
}
]
]
},
"Is New User?": {
"main": [
[
{
"node": "Register New User",
"type": "main",
"index": 0
}
],
[
{
"node": "Is Verified?",
"type": "main",
"index": 0
}
]
]
},
"Is Verified?": {
"main": [
[
{
"node": "Has Text?",
"type": "main",
"index": 0
}
],
[
{
"node": "PIN Correct?",
"type": "main",
"index": 0
}
]
]
},
"PIN Correct?": {
"main": [
[
{
"node": "Mark User Verified",
"type": "main",
"index": 0
}
],
[
{
"node": "Wrong PIN Message",
"type": "main",
"index": 0
}
]
]
},
"Download File": {
"main": [
[
{
"node": "Extract Text",
"type": "main",
"index": 0
}
]
]
},
"Search Documents": {
"main": [
[
{
"node": "Build Prompt",
"type": "main",
"index": 0
}
]
]
},
"Telegram Webhook": {
"main": [
[
{
"node": "Get User",
"type": "main",
"index": 0
}
]
]
},
"Register New User": {
"main": [
[
{
"node": "Send PIN Request",
"type": "main",
"index": 0
}
]
]
},
"Split Into Chunks": {
"main": [
[
{
"node": "Loop Chunks",
"type": "main",
"index": 0
}
]
]
},
"User State Router": {
"main": [
[
{
"node": "Is New User?",
"type": "main",
"index": 0
}
]
]
},
"Mark User Verified": {
"main": [
[
{
"node": "Send Welcome",
"type": "main",
"index": 0
}
]
]
},
"Google Drive Trigger": {
"main": [
[
{
"node": "Download File",
"type": "main",
"index": 0
}
]
]
},
"Format Chunk Embedding": {
"main": [
[
{
"node": "Store Chunk",
"type": "main",
"index": 0
}
]
]
},
"Format Query Embedding": {
"main": [
[
{
"node": "Search Documents",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This template is for developers, teams, and automation enthusiasts who want a private, PIN-protected Telegram chatbot that answers questions from their own documents — without relying on external AI APIs. Ideal for internal knowledge bases, private document search, or anyone…
Source: https://n8n.io/workflows/15031/ — 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.
Transcribe audio messages from Telegram using Google Gemini for free.
Automate Instagram Reel Downloads, Storage, and Activity Logging Handles incoming webhook requests (ideal for Instagram/Facebook API triggers). Validates the webhook via challenge-response and custom
Listens for completed Fireflies transcripts, qualifies whether a proposal is needed using OpenAI, drafts structured proposal content, populates a Google Doc template, converts to PDF, and sends it to
This n8n template demonstrates a complete AI-driven content pipeline for social media. It automatically generates captions and hashtags for new product images, collects human approval via Telegram, an
Monitor Google Drive folder, parsing PDF, DOCX and image file into a destination folder, ready for further processing (e.g. RAG ingestion, translation, etc.) Keep processing log in Google Sheet and se