AutomationFlowsAI & RAG › Auto-reply to Instagram DMs with AI Chatbot

Auto-reply to Instagram DMs with AI Chatbot

Original n8n title: Auto-reply to Instagram Dms with an AI Chatbot and Google Gemini History

ByNguyễn Thiệu Toàn (Jay Nguyen) @nguyenthieutoan on n8n.io

This workflow turns your Instagram Business or Creator account into an AI-powered customer support chatbot using Google Gemini and n8n Data Table for persistent conversation history. Every incoming Direct Message is automatically received, processed, and replied to — with long…

Webhook trigger★★★★★ complexityAI-powered31 nodesGoogle Gemini ChatHTTP RequestData TableAgent
AI & RAG Trigger: Webhook Nodes: 31 Complexity: ★★★★★ AI nodes: yes Added:

This workflow corresponds to n8n.io template #14026 — we link there as the canonical source.

This workflow follows the Agent → Datatable recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "feededdf-bb71-4d9c-b869-0a3d1d88d45d",
      "name": "Instagram Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        1024,
        352
      ],
      "parameters": {
        "path": "nguyenthieutoan-instagram",
        "options": {},
        "responseMode": "responseNode",
        "multipleMethods": true
      },
      "typeVersion": 2.1
    },
    {
      "id": "b2069343-d56c-4111-9bac-7c72cf8d9330",
      "name": "Webhook Verification",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1248,
        176
      ],
      "parameters": {
        "options": {
          "responseCode": 200
        },
        "respondWith": "text",
        "responseBody": "={{ $json.query['hub.challenge'] }}"
      },
      "typeVersion": 1.5
    },
    {
      "id": "1d9d267e-5a09-4aad-a62b-14c6663eaeec",
      "name": "Set Context",
      "type": "n8n-nodes-base.set",
      "position": [
        1568,
        352
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "message-text-field",
              "name": "message",
              "type": "string",
              "value": "={{ $json.body.entry[0].messaging[0].message.text }}"
            },
            {
              "id": "sender-id-field",
              "name": "ig_id",
              "type": "string",
              "value": "={{ $json.body.entry[0].id }}"
            },
            {
              "id": "285e5c64-c494-4bc2-97af-759696a40308",
              "name": "is_from_bot",
              "type": "string",
              "value": "={{ $json.body.entry[0].messaging[0].message.metadata }}"
            },
            {
              "id": "access-token-field",
              "name": "ig_access_token",
              "type": "string",
              "value": "[YOUR_TOKEN]"
            },
            {
              "id": "timestamp-field",
              "name": "received_timestamp",
              "type": "number",
              "value": "={{ $json.body.entry[0].messaging[0].timestamp }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "afb4d221-8f1a-41ed-b1b9-11a596cb3e69",
      "name": "Google Gemini Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        3600,
        960
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "e4ffc56f-5c56-4fba-8651-f285abdd686f",
      "name": "Get 15 newest rows",
      "type": "n8n-nodes-base.code",
      "position": [
        3072,
        544
      ],
      "parameters": {
        "jsCode": "const inputItems = items;\n\n// N\u1ebfu danh s\u00e1ch r\u1ed7ng, tr\u1ea3 v\u1ec1 r\u1ed7ng\nif (inputItems.length === 0) {\n  return [];\n}\n\n// S\u1eafp x\u1ebfp theo th\u1eddi gian t\u1ea1o m\u1edbi nh\u1ea5t\nconst sortedItems = [...inputItems].sort((a, b) => new Date(b.json.createdAt) - new Date(a.json.createdAt));\n\n// L\u1ea5y 15 h\u00e0ng m\u1edbi nh\u1ea5t\nconst latestItems = sortedItems.slice(0, 15);\n\n// S\u1eafp x\u1ebfp l\u1ea1i theo th\u1eddi gian t\u0103ng d\u1ea7n \u0111\u1ec3 gi\u1eef \u0111\u00fang th\u1ee9 t\u1ef1 h\u1ed9i tho\u1ea1i\nconst finalSorted = [...latestItems].sort((a, b) => new Date(a.json.createdAt) - new Date(b.json.createdAt));\n\n// Tr\u1ea3 v\u1ec1 k\u1ebft qu\u1ea3\nreturn finalSorted;\n"
      },
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "67a2212b-fb57-4577-8a40-41b5bf83e5fb",
      "name": "Delay Between Messages",
      "type": "n8n-nodes-base.wait",
      "notes": "Delay 100ms gi\u1eefa c\u00e1c tin nh\u1eafn \u0111\u1ec3 \u0111\u1ea3m b\u1ea3o th\u1ee9 t\u1ef1",
      "position": [
        4464,
        736
      ],
      "parameters": {
        "amount": 1
      },
      "typeVersion": 1.1
    },
    {
      "id": "899467e7-aa45-4ef1-ac4a-65c2f57d4dc4",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "notes": "Loop tu\u1ea7n t\u1ef1 qua t\u1eebng tin nh\u1eafn",
      "position": [
        4256,
        720
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "003ccf45-e60b-45b9-8f0e-123b10d02d7f",
      "name": "Merge History and Find Min_ID",
      "type": "n8n-nodes-base.code",
      "position": [
        3280,
        544
      ],
      "parameters": {
        "jsCode": "const inputItems = items;\n\nif (inputItems.length === 0) {\n  return [];\n}\n\n// Sort messages by creation time (ascending)\nconst sortedItems = [...inputItems].sort(\n  (a, b) => new Date(a.json.createdAt) - new Date(b.json.createdAt)\n);\n\n// Format helper for YYYY-MM-DD HH:mm:ss in Vietnam timezone\nfunction formatVNDateTime(date) {\n  return date.toLocaleString('en-CA', { timeZone: 'Asia/Ho_Chi_Minh' });\n}\n\n// Helper to get date string only (YYYY-MM-DD)\nfunction getVNDate(date) {\n  return new Date(date).toLocaleDateString('en-CA', { timeZone: 'Asia/Ho_Chi_Minh' });\n}\n\nlet sessions = [];\nlet currentSession = [];\nlet lastDate = null;\n\nsortedItems.forEach(item => {\n  const createdAt = new Date(item.json.createdAt);\n  const currentDate = getVNDate(createdAt);\n\n  if (lastDate && currentDate !== lastDate) {\n    // Push previous session\n    sessions.push(currentSession);\n    currentSession = [];\n  }\n\n  currentSession.push(item);\n  lastDate = currentDate;\n});\n\n// Push the final session\nif (currentSession.length > 0) {\n  sessions.push(currentSession);\n}\n\n// Get today's date in Vietnam timezone\nconst todayVN = getVNDate(new Date());\n\n// Split sessions into old and current\nlet oldSessionHistory = '';\nlet nowSessionHistory = '';\n\nsessions.forEach(session => {\n  const sessionDate = getVNDate(session[0].json.createdAt);\n  let sessionBlock = `--- \ud83d\udd52 Session started at ${formatVNDateTime(new Date(session[0].json.createdAt))} ---\\n`;\n\n  session.forEach(item => {\n    const userText = item.json.user_text;\n    const pageText = item.json.page_text;   // <-- l\u1ea5y ph\u1ea3n h\u1ed3i t\u1eeb Page\n    const botRep = item.json.bot_rep;       // <-- ph\u1ea3n h\u1ed3i t\u1eeb AI n\u1ebfu c\u00f3\n    const createdAtFormatted = formatVNDateTime(new Date(item.json.createdAt));\n    const updatedAtFormatted = formatVNDateTime(new Date(item.json.updatedAt));\n\n    if (userText) {\n      sessionBlock += `Human [${createdAtFormatted}]: ${userText}\\n`;\n    }\n    if (pageText) {\n      sessionBlock += `Page [${updatedAtFormatted}]: ${pageText}\\n`;\n    }\n    if (botRep) {\n      sessionBlock += `AI [${updatedAtFormatted}]: ${botRep}\\n`;\n    }\n  });\n\n  sessionBlock = sessionBlock.trim() + '\\n';\n\n  if (sessionDate === todayVN) {\n    nowSessionHistory += sessionBlock;\n  } else {\n    oldSessionHistory += sessionBlock;\n  }\n});\n\n// Calculate min_id (smallest id in the list)\nconst minId = Math.min(...sortedItems.map(item => item.json.id));\n\n// Final output\nreturn [\n  {\n    json: {\n      old_session_history: oldSessionHistory.trim(),\n      now_session_history: nowSessionHistory.trim(),\n      min_id: minId\n    }\n  }\n];\n"
      },
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "4cd4d408-7e4a-4c5d-9861-a6aeb96c13de",
      "name": "User is Sender",
      "type": "n8n-nodes-base.set",
      "position": [
        2064,
        352
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "16816be3-d7e3-4c45-9277-38be6cb24335",
              "name": "user_id",
              "type": "string",
              "value": "={{ $('Instagram Webhook').item.json.body.entry[0].messaging[0].sender.id }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "f0fada31-adc3-42cc-8fa1-22a488a636c0",
      "name": "Is from page?",
      "type": "n8n-nodes-base.if",
      "position": [
        1792,
        352
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "22aba732-f39d-473f-9336-6e9f0fdfd9ff",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $('Instagram Webhook').item.json.body.entry[0].messaging[0].sender.id }}",
              "rightValue": "={{ $('Instagram Webhook').item.json.body.entry[0].id }}"
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.3,
      "alwaysOutputData": false
    },
    {
      "id": "532a45d0-0c3b-4dbd-88d8-038053b0bbbe",
      "name": "Seen",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        2400,
        320
      ],
      "parameters": {
        "url": "=https://graph.instagram.com/v24.0/{{ $('Set Context').item.json.ig_id }}/messages",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"recipient\": {\n    \"id\": \"{{ $('User is Sender').item.json.user_id }}\"\n  },\n  \"sender_action\": \"mark_seen\"\n} ",
        "sendBody": true,
        "sendQuery": true,
        "specifyBody": "json",
        "queryParameters": {
          "parameters": [
            {
              "name": "access_token",
              "value": "={{ $('Set Context').item.json.ig_access_token }}"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "ddc0ffa3-27d9-4713-8398-df3402c8508a",
      "name": "Insert To Unprocessed",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        2400,
        544
      ],
      "parameters": {
        "columns": {
          "value": {
            "page_id": "={{ $('Set Context').item.json.ig_id }}",
            "user_id": "={{ $('User is Sender').item.json.user_id }}",
            "processed": false,
            "user_text": "={{ $('Set Context').item.json.message }}"
          },
          "schema": [
            {
              "id": "user_id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "user_id",
              "defaultMatch": false
            },
            {
              "id": "user_text",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "user_text",
              "defaultMatch": false
            },
            {
              "id": "page_id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "page_id",
              "defaultMatch": false
            },
            {
              "id": "page_text",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "page_text",
              "defaultMatch": false
            },
            {
              "id": "processed",
              "type": "boolean",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "processed",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "JuNlpJZIJFqQaIFn",
          "cachedResultUrl": "/projects/mYtIzVb8cWfhFOhJ/datatables/JuNlpJZIJFqQaIFn",
          "cachedResultName": "insert_message"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "fafc6774-42f4-4b81-b624-8bce03ab9196",
      "name": "Get history message",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        2848,
        544
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "keyName": "user_id",
              "keyValue": "={{ $('User is Sender').item.json.user_id }}"
            },
            {
              "keyName": "processed",
              "condition": "isTrue"
            },
            {
              "keyName": "page_id",
              "keyValue": "={{ $json.page_id }}"
            }
          ]
        },
        "matchType": "allConditions",
        "operation": "get",
        "returnAll": true,
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "JuNlpJZIJFqQaIFn",
          "cachedResultUrl": "/projects/mYtIzVb8cWfhFOhJ/datatables/JuNlpJZIJFqQaIFn",
          "cachedResultName": "insert_message"
        }
      },
      "typeVersion": 1.1,
      "alwaysOutputData": true
    },
    {
      "id": "1ededf20-1141-45e3-905d-94dbdc83788e",
      "name": "Send Typing",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        3616,
        400
      ],
      "parameters": {
        "url": "=https://graph.instagram.com/v24.0/{{ $('Set Context').item.json.ig_id }}/messages",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"recipient\": {\n    \"id\": \"{{ $('User is Sender').first().json.user_id }}\"\n  },\n  \"sender_action\": \"typing_on\"\n}\n",
        "sendBody": true,
        "sendQuery": true,
        "specifyBody": "json",
        "queryParameters": {
          "parameters": [
            {
              "name": "access_token",
              "value": "={{ $('Set Context').first().json.ig_access_token }}"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "7d350753-3bc9-4257-9cad-163b88a7c033",
      "name": "Process Merged Message",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        3616,
        720
      ],
      "parameters": {
        "text": "={{ $('Get MaxID and Merged Mess').first().json.merged_message }}",
        "options": {
          "systemMessage": "=## Persona\nYou are Jenix, the personal AI assistant for Nguy\u1ec5n Thi\u1ec7u To\u00e0n (Jay Nguyen) and his GenStaff Company. Your primary role is to act as a specialized consultant, advising potential clients on the AI and Automation services he provides.\n\n## Core Directives\n1. *Consult on Services:* Your main goal is to understand the customer's needs and advise them on how Nguy\u1ec5n Thi\u1ec7u To\u00e0n's services can help. The core services include:  \n   a. *Automation Solutions for Businesses:* Providing custom automation workflows using platforms like n8n and other AI-powered tools.  \n   b. *AI & Automation Training:* Offering training programs for individuals and companies on leveraging AI and automation.  \n2. *Facilitate Scheduling:* If a potential client expresses strong interest or has complex questions that require direct expertise, proactively offer to schedule a consultation with Nguy\u1ec5n Thi\u1ec7u To\u00e0n.\n\n## Conversational Flow\n1. *Opening Message:* In the very first message of a new conversation (when chat history is empty), you MUST introduce yourself. The introduction should adapt to the input language. For example:  \n   - If the user writes in Vietnamese: \"Ch\u00e0o anh/ch\u1ecb, em l\u00e0 Jenix, tr\u1ee3 l\u00fd AI c\u1ee7a anh Nguy\u1ec5n Thi\u1ec7u To\u00e0n. Em c\u00f3 th\u1ec3 gi\u00fap anh/ch\u1ecb gi\u1ea3i \u0111\u00e1p c\u00e1c th\u1eafc m\u1eafc v\u1ec1 d\u1ecbch v\u1ee5 v\u00e0 gi\u1ea3i ph\u00e1p T\u1ef1 \u0111\u1ed9ng h\u00f3a & AI \u1ea1.\"  \n   - If the user writes in English: \"Hello, I\u2019m Jenix, the AI assistant of Mr. Nguy\u1ec5n Thi\u1ec7u To\u00e0n. I can help answer your questions about AI & Automation services.\"  \n2. *Follow-up Messages:* In all subsequent messages within the same conversation, skip the introduction and go straight to the point, directly answering the user's query.\n\n## Constraints & Rules\n1. *Extreme Brevity:* Your responses (after the opening message) must be concise and directly address the user's core question. Eliminate filler words.  \n2. *Language Protocol:* Always respond in the same language as the user\u2019s input.  \n   - In Vietnamese: Address the customer as \"anh\" or \"ch\u1ecb\", and refer to yourself as \"em\".  \n   - In English: Use polite, professional addressing such as \"you\" and refer to yourself as \"I\".  \n\n## Tone of Voice\n- *Clever & Witty:* Employ a smart and slightly humorous tone.  \n- *Friendly & Approachable:* Maintain a warm, welcoming, and helpful demeanor.  \n- *Efficient:* Convey competence and respect for the customer's time.  \n\n## Output Format\n- Your entire output must be formatted in *Facebook Markdown*.  \n\n## More information and context\n### Date and time now: {{ $now }}\n### Social Channel\n#### Nguy\u1ec5n Thi\u1ec7u To\u00e0n (Jay Nguyen)\nWebsite: https://nguyenthieutoan.com  \nFacebook: https://www.facebook.com/nguyenthieutoan  \nLinkedIn: https://www.linkedin.com/in/nguyenthieutoan  \nX (Twitter): https://www.x.com/nguyenthieutoan  \nYouTube: https://www.youtube.com/@NguyenThieuToan  \nEmail: me@nguyenthieutoan.com  \n\n#### GenStaff\nWebsite: https://genstaff.net  \nFacebook: https://www.facebook.com/genstaff.net  \nEmail: contact@genstaff.net  \n\n## Chat History (DO NOT DELETE)\n### Old session chat history: {{ $json.old_session_history }}\n### Now session chat history: {{ $json.now_session_history }}\n"
        },
        "promptType": "define"
      },
      "retryOnFail": true,
      "typeVersion": 3.1,
      "waitBetweenTries": 100
    },
    {
      "id": "bb140074-72ac-49a0-9b48-48d54b0a99e4",
      "name": "Send Text",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4672,
        736
      ],
      "parameters": {
        "url": "=https://graph.instagram.com/v24.0/{{ $('Set Context').first().json.ig_id }}/messages",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"recipient\": {\n    \"id\": \"{{ $('User is Sender').first().json.user_id }}\"\n  },\n  \"messaging_type\": \"RESPONSE\",\n  \"message\": {\n    \"text\": {{JSON.stringify($('Format Output').item.json.text)}},\n    \"metadata\": \"bot_rep\"\n  }\n}",
        "sendBody": true,
        "sendQuery": true,
        "specifyBody": "json",
        "queryParameters": {
          "parameters": [
            {
              "name": "access_token",
              "value": "={{ $('Set Context').first().json.ig_access_token}}"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "64f1ffd0-d83e-443c-a43d-bd966e97da78",
      "name": "Update Page Rep",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        4528,
        464
      ],
      "parameters": {
        "columns": {
          "value": {
            "page_text": "={{ $('Process Merged Message').item.json.output }}"
          },
          "schema": [
            {
              "id": "user_id",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "user_id",
              "defaultMatch": false
            },
            {
              "id": "user_text",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "user_text",
              "defaultMatch": false
            },
            {
              "id": "page_id",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "page_id",
              "defaultMatch": false
            },
            {
              "id": "page_text",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "page_text",
              "defaultMatch": false
            },
            {
              "id": "processed",
              "type": "boolean",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "processed",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "filters": {
          "conditions": [
            {
              "keyName": "user_id",
              "keyValue": "={{ $('User is Sender').first().json.user_id }}"
            },
            {
              "keyValue": "={{ $('Get MaxID and Merged Mess').first().json.id }}"
            },
            {
              "keyName": "page_id",
              "keyValue": "={{ $('Set Context').first().json.ig_id }}"
            }
          ]
        },
        "options": {},
        "matchType": "allConditions",
        "operation": "update",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "JuNlpJZIJFqQaIFn",
          "cachedResultUrl": "/projects/mYtIzVb8cWfhFOhJ/datatables/JuNlpJZIJFqQaIFn",
          "cachedResultName": "insert_message"
        }
      },
      "typeVersion": 1.1,
      "alwaysOutputData": true
    },
    {
      "id": "0aa989ef-ac8f-42ee-8961-ea6c5f844868",
      "name": "Update FALSE to TRUE",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        4368,
        464
      ],
      "parameters": {
        "columns": {
          "value": {
            "processed": true
          },
          "schema": [
            {
              "id": "user_id",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "user_id",
              "defaultMatch": false
            },
            {
              "id": "user_text",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "user_text",
              "defaultMatch": false
            },
            {
              "id": "page_id",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "page_id",
              "defaultMatch": false
            },
            {
              "id": "page_text",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "page_text",
              "defaultMatch": false
            },
            {
              "id": "processed",
              "type": "boolean",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "processed",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "filters": {
          "conditions": [
            {
              "keyName": "user_id",
              "keyValue": "={{ $('User is Sender').first().json.user_id }}"
            },
            {
              "keyName": "processed",
              "condition": "isFalse"
            },
            {
              "keyName": "page_id",
              "keyValue": "={{ $('Set Context').first().json.ig_id }}"
            }
          ]
        },
        "options": {},
        "matchType": "allConditions",
        "operation": "update",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "JuNlpJZIJFqQaIFn",
          "cachedResultUrl": "/projects/mYtIzVb8cWfhFOhJ/datatables/JuNlpJZIJFqQaIFn",
          "cachedResultName": "insert_message"
        }
      },
      "typeVersion": 1.1,
      "alwaysOutputData": true
    },
    {
      "id": "56786305-3ae3-4a0b-9eff-bcab59711ec7",
      "name": "Clean History",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        4672,
        464
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "keyName": "user_id",
              "keyValue": "={{ $('User is Sender').first().json.user_id }}"
            },
            {
              "keyName": "processed",
              "condition": "isTrue"
            },
            {
              "keyName": "page_id",
              "keyValue": "={{ $('Set Context').first().json.ig_id }}"
            },
            {
              "keyValue": "={{ $('Merge History and Find Min_ID').first().json.min_id }}",
              "condition": "lt"
            }
          ]
        },
        "options": {
          "dryRun": false
        },
        "matchType": "allConditions",
        "operation": "deleteRows",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "JuNlpJZIJFqQaIFn",
          "cachedResultUrl": "/projects/mYtIzVb8cWfhFOhJ/datatables/JuNlpJZIJFqQaIFn",
          "cachedResultName": "insert_message"
        }
      },
      "typeVersion": 1.1,
      "alwaysOutputData": true
    },
    {
      "id": "34af050c-7b86-4e29-b2c8-d6007aa6a371",
      "name": "Get MaxID and Merged Mess",
      "type": "n8n-nodes-base.code",
      "position": [
        2624,
        544
      ],
      "parameters": {
        "jsCode": "// L\u1ea5y danh s\u00e1ch input\nconst inputItems = items;\n\n// N\u1ebfu danh s\u00e1ch r\u1ed7ng, tr\u1ea3 v\u1ec1 r\u1ed7ng\nif (inputItems.length === 0) {\n  return [];\n}\n\n// S\u1eafp x\u1ebfp theo id t\u0103ng d\u1ea7n\nconst sortedItems = [...inputItems].sort((a, b) => a.json.id - b.json.id);\n\n// T\u1ea1o merged_message t\u1eeb c\u00e1c user_text kh\u00f4ng null\nconst mergedMessage = sortedItems\n  .map(item => item.json.user_text)\n  .filter(text => text !== null && text !== undefined)\n  .join(' ');\n\n// T\u00ecm item c\u00f3 id l\u1edbn nh\u1ea5t\nlet maxItem = sortedItems[sortedItems.length - 1];\n\n// Th\u00eam c\u00e1c tr\u01b0\u1eddng v\u00e0o item l\u1edbn nh\u1ea5t\nmaxItem.json.merged_message = mergedMessage;\n// Tr\u1ea3 v\u1ec1 item c\u00f3 id l\u1edbn nh\u1ea5t k\u00e8m merged_message v\u00e0 history\nreturn [maxItem];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "a4e720f0-4e6f-4865-9834-c480ef010e09",
      "name": "Is Message?",
      "type": "n8n-nodes-base.if",
      "position": [
        1312,
        368
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "has-message-check",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.body.entry?.[0]?.messaging?.[0]?.message?.text }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "681198f5-d345-43c6-a5c2-b4c588dee113",
      "name": "Main Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        368,
        -48
      ],
      "parameters": {
        "width": 580,
        "height": 1220,
        "content": "## Instagram DM Chatbot with AI & Conversation History\n\nThis workflow turns your **Instagram Business/Creator account** into a fully automated AI chatbot using **Google Gemini** and **n8n Data Table** for persistent conversation history. Every incoming Direct Message is stored, processed, and replied to automatically \u2014 with long replies split and delivered sequentially.\n\n> \u26a0\ufe0f **Simplified version** \u2014 ideal for learning and low-traffic deployments. For production, integrate the Smart Batching and Human Takeover workflows linked in the Upgrade Note.\n\n### How it works\n1. **Instagram Webhook** receives incoming DMs and responds to Meta's GET verification challenge.\n2. **Is Message?** filters text-only events; **Is from page?** blocks echo from the page itself.\n3. **Set Context** extracts `user_id`, `ig_id`, and `access_token` \u2014 single config point.\n4. New message is saved to **Data Table** as `unprocessed`. Unprocessed rows are merged into one prompt.\n5. Last 15 **processed rows** are loaded and formatted as old/current-session history for context.\n6. **Gemini AI Agent** generates a reply using the merged message + full session history.\n7. Reply is **split** into \u22642000-character chunks and sent sequentially with a 1-second delay.\n8. Data Table is **updated**: all rows marked `processed`, AI reply saved, old rows cleaned up.\n\n### Setup\n* [ ] Create a **Meta App** \u2192 add **Instagram** product.\n* [ ] Go to Instagram > **API Setup with credentials** \u2192 log in to your IG Business/Creator account \u2192 copy the **long-lived Access Token**.\n* [ ] Paste the token into **Set Context** (`ig_access_token` field).\n* [ ] In Meta App > Instagram > **Webhooks**: paste your n8n production webhook URL + a Verify Token \u2192 click Verify.\n* [ ] Connect **Google Gemini** (`googlePalmApi`) credential in the AI Agent and LLM nodes.\n* [ ] Create the **n8n Data Table** `insert_message` with columns: `user_id`, `page_id`, `user_text`, `page_text`, `processed` (Boolean).\n* [ ] Activate the workflow in **production mode BEFORE** verifying the webhook in Meta.\n\n### Customization tips\n* **Change AI persona:** Edit the system prompt inside `Process Merged Message` \u2014 nothing else needs changing.\n* **Switch model:** Swap `Google Gemini Chat Model` for any supported LLM sub-node.\n* **Scale up:** Combine with the Smart Batching or Human Takeover workflows for production readiness.\n\n### LICENCE\nThis template is shared free of charge. Copyright belongs to Nguyen Thieu Toan (Jay Nguyen). Any copying or modification must credit the author."
      },
      "typeVersion": 1
    },
    {
      "id": "0eb9dbcd-ce1b-48e0-9f26-1dc69c08cb20",
      "name": "Author Message",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1600,
        800
      ],
      "parameters": {
        "color": 4,
        "width": 628,
        "height": 380,
        "content": "## Author Message\n\nHi! I am **Nguyen Thieu Toan (Jay Nguyen)** \u2014 a Verified n8n Creator. Thank you for using this template!\n\nThis workflow is shared with you for free. If it brings value to your work, optimizes your operations, or saves you time, you can buy me a coffee here: **[My Donate Website](https://nguyenthieutoan.com/payment/)** *(PayPal, Momo, Bank Transfer)*\n\n* Website: [nguyenthieutoan.com](https://nguyenthieutoan.com)\n* Email: me@nguyenthieutoan.com\n* Company: GenStaff ([genstaff.net](https://genstaff.net))\n* Socials (Facebook / X / LinkedIn): @nguyenthieutoan\n\n*Discover more of my automation solutions:* **[Click here](https://n8n.io/creators/nguyenthieutoan/)**"
      },
      "typeVersion": 1
    },
    {
      "id": "294cc9be-6f9a-4614-81a1-675048405711",
      "name": "Upgrade Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        976,
        800
      ],
      "parameters": {
        "color": 3,
        "width": 596,
        "height": 376,
        "content": "## IMPORTANT: Upgrade for Production\n\nThis is a **simplified version** \u2014 suitable for learning, testing, or low-traffic use.\n\nFor production, combine with these **100% compatible** workflows (originally built for Facebook Messenger, but fully cross-platform \u2014 works with Instagram too):\n\n* **[Smart message batching](https://n8n.io/workflows/9192):** Waits for the user to finish typing before responding. Prevents duplicate or out-of-order replies.\n* **[Smart human takeover](https://n8n.io/workflows/11920):** Automatically pauses the bot when an admin replies. Resumes when the admin is done.\n\nIntegrate **one or both** for a production-ready Instagram chatbot."
      },
      "typeVersion": 1
    },
    {
      "id": "444fd240-d108-4d62-8c57-f84cba69c6dc",
      "name": "Section 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        976,
        -48
      ],
      "parameters": {
        "color": 7,
        "width": 1256,
        "height": 820,
        "content": "## Section 1: Webhook & Validation\nReceives Instagram DM events via webhook \u2192 returns GET verification challenge to Meta \u2192 **Is Message?** filters text-only events \u2192 **Is from page?** blocks echo messages sent by the page itself \u2192 extracts config into **Set Context**."
      },
      "typeVersion": 1
    },
    {
      "id": "ad1a29a3-0bd1-4c23-ba6b-94b37f36f643",
      "name": "Section 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2272,
        -48
      ],
      "parameters": {
        "color": 7,
        "width": 1144,
        "height": 820,
        "content": "## Section 2: Store Message & Load History\nInserts new user message into **Data Table** as `unprocessed` \u2192 marks as **Seen** \u2192 merges all unprocessed rows into one prompt (simple batching) \u2192 retrieves last 15 **processed** rows \u2192 formats them into old-session / current-session history blocks for the AI."
      },
      "typeVersion": 1
    },
    {
      "id": "3f24989a-d18a-468a-8df3-187fbc7e3ad9",
      "name": "Section 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3472,
        -48
      ],
      "parameters": {
        "color": 7,
        "width": 632,
        "height": 1120,
        "content": "## Section 3: AI Processing\nSends `typing_on` indicator to the user \u2192 **Gemini AI Agent** generates a reply using the merged message + full session history \u2192 **Format Output** normalizes markdown, strips unsupported syntax, and splits the reply into \u22642000-character chunks."
      },
      "typeVersion": 1
    },
    {
      "id": "abd97549-b79e-41e8-810f-517804724baf",
      "name": "Section 4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4176,
        -48
      ],
      "parameters": {
        "color": 7,
        "width": 668,
        "height": 1124,
        "content": "## Section 4: Deliver & Update\nLoops through each message chunk \u2192 waits 1 second between sends \u2192 delivers via **Instagram Graph API** \u2192 marks all unprocessed rows `processed = true` \u2192 saves AI reply into `page_text` \u2192 deletes old history rows beyond the 15-message window."
      },
      "typeVersion": 1
    },
    {
      "id": "9890977c-ae36-457e-a375-a6af54a26feb",
      "name": "Warning Activate",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        976,
        592
      ],
      "parameters": {
        "color": 3,
        "width": 440,
        "height": 148,
        "content": "## Activate before verifying!\n\nThe webhook verification in Meta App **only works with the production URL**. Activate this workflow in production mode first, then go to Meta App > Instagram > Webhooks to verify."
      },
      "typeVersion": 1
    },
    {
      "id": "94733b4a-4ba7-4959-83a8-cdcf2c08b168",
      "name": "Warning Token",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1504,
        160
      ],
      "parameters": {
        "color": 3,
        "width": 236,
        "height": 180,
        "content": "## Edit this node!\n\nReplace `ig_access_token` with your **long-lived Instagram Access Token** from:\nMeta App > Instagram > API Setup with credentials."
      },
      "typeVersion": 1
    },
    {
      "id": "8558e2b3-4cc5-42ae-842b-44467d6a599e",
      "name": "Format Output",
      "type": "n8n-nodes-base.code",
      "position": [
        3968,
        720
      ],
      "parameters": {
        "jsCode": "const MAX_LEN = 2000;\n\n/* === Get input === */\nlet text = ($input.first().json.output || \"\").trim();\nif (!text) return [];\n\n/* --- Fix escape JSON: \u0111\u1ed5i \\\\n, \\\\\\n th\u00e0nh \\n th\u1eadt --- */\ntext = text.replace(/\\\\+/g, '\\\\');   // g\u1ed9p nhi\u1ec1u \\ th\u00e0nh 1\ntext = text.replace(/\\\\n/g, '\\n');   // \u0111\u1ed5i th\u00e0nh newline th\u1eadt\n\n/* ---------- 1) Markdown \u2192 Facebook Markdown ---------- */\nfunction mdToFacebook(md){\n  let s = md;\n\n  // code block \u2192 gi\u1eef nguy\u00ean\n  s = s.replace(/```([\\s\\S]*?)```/g, (m,p1)=>`\\`\\`\\n${p1.trim()}\\n\\`\\`\\``);\n\n  // inline code\n  s = s.replace(/`([^`]+)`/g, (m,p1)=>`\\`${p1}\\``);\n\n  // bold (**text**) \u2192 *text*\n  s = s.replace(/\\*\\*([^*]+)\\*\\*/g, '*$1*');\n\n  // italic (_text_) \u2192 gi\u1eef nguy\u00ean\n\n  // underline & strike \u2192 b\u1ecf\n  s = s.replace(/__([^_]+)__/g, '$1');\n  s = s.replace(/~~([^~]+)~~/g, '$1');\n\n  // headers \u2192 bold\n  s = s.replace(/^(#{1,6})\\s+(.+)$/gm, (m, hashes, content)=>`*${content.trim()}*`);\n\n  // list markers \u2192 bullet list Messenger\n  s = s.replace(/^[\\-\\*\\+]\\s+(.+)$/gm, '* $1');\n\n  // links \u2192 gi\u1eef text\n  s = s.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, (m,txt,url)=>txt);\n\n  return s.trim();\n}\n\n/* ---------- 2) HTML \u2192 Facebook Markdown ---------- */\nfunction htmlToFbMarkdown(input){\n  let s = input;\n\n  s = s.replace(/<br\\s*\\/?>/gi, '\\n');\n\n  // headers \u2192 bold\n  s = s.replace(/<h[1-6][^>]*>([\\s\\S]*?)<\\/h[1-6]>/gi, (m, inner)=>`*${inner.trim()}*`);\n\n  // bold / italic\n  s = s.replace(/<\\/?strong[^>]*>/gi, '*');\n  s = s.replace(/<\\/?b[^>]*>/gi, '*');\n  s = s.replace(/<\\/?em[^>]*>/gi, '_');\n  s = s.replace(/<\\/?i[^>]*>/gi, '_');\n\n  // paragraphs/divs \u2192 newline\n  s = s.replace(/<\\/?(p|div)[^>]*>/gi, '\\n');\n\n  // lists \u2192 bullet\n  s = s.replace(/<li[^>]*>/gi, '* ').replace(/<\\/li>/gi, '\\n');\n  s = s.replace(/<\\/?(ul|ol)[^>]*>/gi, '');\n\n  // remove all other tags\n  s = s.replace(/<\\/?[^>]+>/g, '');\n\n  return s.trim();\n}\n\n/* ---------- 3) Normalize content ---------- */\nfunction normalizeToFacebookMarkdown(input){\n  const looksLikeMd = /(^|\\s)[*_`~]|^#{1,6}\\s|```/.test(input);\n  let s = looksLikeMd ? mdToFacebook(input) : htmlToFbMarkdown(input);\n\n  // collapse nhi\u1ec1u newline th\u00e0nh 1\n  s = s.replace(/\\n{3,}/g, '\\n\\n');\n\n  // xo\u00e1 k\u00fd t\u1ef1 \"\\\" th\u1eeba c\u00f2n s\u00f3t\n  s = s.replace(/\\\\+/g, '');\n\n  return s.trim();\n}\n\n/* ---------- 4) Split by newlines (smart split) ---------- */\nfunction splitMarkdownSmart(text, maxLen) {\n  const lines = text.split('\\n');\n  const chunks = [];\n  let buffer = '';\n\n  for (const line of lines) {\n    if ((buffer + line + '\\n').length > maxLen) {\n      if (buffer.trim()) chunks.push(buffer.trim());\n      buffer = line + '\\n';\n    } else {\n      buffer += line + '\\n';\n    }\n  }\n\n  if (buffer.trim()) chunks.push(buffer.trim());\n\n  return chunks.map(c => c.replace(/\\n+$/,''));\n}\n\n/* ===== Run ===== */\nconst normalized = normalizeToFacebookMarkdown(text);\nconst chunks = splitMarkdownSmart(normalized, MAX_LEN);\n\nreturn chunks.map(c => ({ json: { text: c } }));\n"
      },
      "typeVersion": 2
    }
  ],
  "connections": {
    "Seen": {
      "main": [
        []
      ]
    },
    "Send Text": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Message?": {
      "main": [
        [
          {
            "node": "Set Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Context": {
      "main": [
        [
          {
            "node": "Is from page?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Output": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is from page?": {
      "main": [
        [],
        [
          {
            "node": "User is Sender",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "User is Sender": {
      "main": [
        [
          {
            "node": "Insert To Unprocessed",
            "type": "main",
            "index": 0
          },
          {
            "node": "Seen",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [
          {
            "node": "Update FALSE to TRUE",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Delay Between Messages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Page Rep": {
      "main": [
        [
          {
            "node": "Clean History",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Instagram Webhook": {
      "main": [
        [
          {
            "node": "Webhook Verification",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Webhook Verification",
            "type": "main",
            "index": 0
          },
          {
            "node": "Is Message?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get 15 newest rows": {
      "main": [
        [
          {
            "node": "Merge History and Find Min_ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get history message": {
      "main": [
        [
          {
            "node": "Get 15 newest rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update FALSE to TRUE": {
      "main": [
        [
          {
            "node": "Update Page Rep",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert To Unprocessed": {
      "main": [
        [
          {
            "node": "Get MaxID and Merged Mess",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delay Between Messages": {
      "main": [
        [
          {
            "node": "Send Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Merged Message": {
      "main": [
        [
          {
            "node": "Format Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Process Merged Message",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Get MaxID and Merged Mess": {
      "main": [
        [
          {
            "node": "Get history message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge History and Find Min_ID": {
      "main": [
        [
          {
            "node": "Send Typing",
            "type": "main",
            "index": 0
          },
          {
            "node": "Process Merged Message",
            "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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow turns your Instagram Business or Creator account into an AI-powered customer support chatbot using Google Gemini and n8n Data Table for persistent conversation history. Every incoming Direct Message is automatically received, processed, and replied to — with long…

Source: https://n8n.io/workflows/14026/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

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

AI & RAG

by Nguyen Thieu Toan (Jay Nguyen)

Google Gemini Chat, Data Table, HTTP Request +1
AI & RAG

Main-2.0. Uses agent, lmChatGoogleGemini, outputParserStructured, httpRequestTool. Webhook trigger; 37 nodes.

Agent, Google Gemini Chat, Output Parser Structured +3
AI & RAG

by Nguyen Thieu Toan (Jay Nguyen)

Google Gemini Chat, HTTP Request, Data Table +1
AI & RAG

⏺ 🚀 How it works

Agent, Anthropic Chat, Output Parser Structured +6
AI & RAG

CLINICAINTEGRAL_secretary. Uses postgres, mcpClientTool, googleDriveTool, toolWorkflow. Webhook trigger; 89 nodes.

Postgres, Mcp Client Tool, Google Drive Tool +14