AutomationFlowsAI & RAG › Route RAG Customer Support Across Whatsapp and Outlook with Pinecone and…

Route RAG Customer Support Across Whatsapp and Outlook with Pinecone and…

Original n8n title: Route RAG Customer Support Across Whatsapp and Outlook with Pinecone and Openrouter

BySalman Mehboob @salmanmehboob on n8n.io

A production-ready 3-workflow system that handles customer support across WhatsApp and Email using RAG-powered AI. Automatically routes queries, detects escalation intent, logs handoffs to Google Sheets, alerts your team via Slack, and lets agents resolve cases with a single…

Event trigger★★★★★ complexityAI-powered52 nodesForm TriggerPinecone Vector StoreDocument Default Data LoaderGoogle Gemini EmbeddingsText Splitter Token SplitterWhatsApp TriggerAgentMemory Buffer Window
AI & RAG Trigger: Event Nodes: 52 Complexity: ★★★★★ AI nodes: yes Added:

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

This workflow follows the Agent → Documentdefaultdataloader 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
{
  "id": "9i4AkgD0CB6Yovrg",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Multi-Channel RAG AI Customer Support",
  "tags": [],
  "nodes": [
    {
      "id": "61ce5205-c616-4f90-990d-c66c5375effd",
      "name": "Sticky Note cbdd800e",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1568,
        -656
      ],
      "parameters": {
        "color": 3,
        "width": 448,
        "height": 656,
        "content": "# Workflow #1\n\n## Knowledge Base Ingestion\n\n**Supported formats:** PDF, DOCX, TXT\n**Embeddings:** Google Gemini (swap to OpenAI Embeddings node if preferred)\n**Vector store:** Pinecone\n\n---\n\n**Setup checklist:**\n1. Set Pinecone credential + select your index in the 'Pinecone - Insert Documents' node\n2. Set Google Gemini credential on the Embeddings node (or replace with OpenAI Embeddings)\n3. Activate workflow\n4. Open the form URL and start uploading documents\n\n**Chunk settings (Text Splitter):**\n- Chunk size: 800 tokens, Overlap: 150 tokens\n- Adjust based on your document length and query style\n\n**Note:** clearNamespace is OFF \u2014 uploads add to the index without wiping existing docs.\nTo fully re-ingest from scratch, temporarily enable clearNamespace in the Pinecone node."
      },
      "typeVersion": 1
    },
    {
      "id": "23c90878-5e20-4634-9abc-a22eb615bd75",
      "name": "Sticky Note 9ee08a90",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1632,
        352
      ],
      "parameters": {
        "color": 2,
        "width": 624,
        "height": 672,
        "content": "# Workflow #2\n\n## Multi-Channel AI Customer Support\n\n**Channels:** WhatsApp Business Cloud API + Microsoft Outlook\n**AI:** Configurable LLM (OpenRouter / OpenAI / etc.)\n**Knowledge Base:** Pinecone vector store (RAG)\n**Handoff State:** Google Sheets (persistent across restarts)\n\n---\n\n**Setup checklist:**\n1. Google Sheets: create a sheet named 'Handoffs' with headers:\n   contact_id | channel | contact_name | status | handoff_since | resolved_at\n   Then set the Sheet ID in all 4 GS nodes\n2. WhatsApp: set credential + replace YOUR_WHATSAPP_PHONE_NUMBER_ID in the 2 Send WhatsApp nodes\n3. Outlook: set OAuth2 credential on the 2 Send Outlook nodes + Outlook Trigger\n4. Slack: set credential + update channel name in all 3 Slack nodes\n5. AI model: set credential on the LLM node (currently OpenRouter)\n6. Pinecone: set credential + index name in the Knowledge Base tool node\n7. Embeddings: set credential on the Embeddings node\n8. Activate workflow\n\n**To resolve a handoff:**\nPOST to: [your-n8n-url]/webhook/resolve-handoff\nBody: { \"contact_id\": \"customer@email.com\" }\nTip: Wire a Slack Slash Command to call this webhook automatically."
      },
      "typeVersion": 1
    },
    {
      "id": "f178bf92-9b08-4bb7-8a6a-ad2641d7006a",
      "name": "Sticky Note 26ce77f0",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1584,
        1824
      ],
      "parameters": {
        "color": 4,
        "width": 448,
        "height": 960,
        "content": "# Workflow #3\n\n## Resolve Handoff \u2014 Slack Slash Command\n\nUsage in any Slack channel:\n/resolve customer@email.com  or  /resolve 15551234567\n\nWhat happens:\n1. Slack POSTs command to this webhook\n2. n8n acknowledges within 3-second Slack timeout\n3. Finds contact in Google Sheets Handoffs tab\n4. Marks status resolved + logs agent name + timestamp\n5. Posts confirmation to #support-handoff\n6. AI in main workflow now responds to this contact again\n\nSETUP \u2014 SLACK APP (api.slack.com/apps):\n1. Your App > Slash Commands > Create New Command\n2. Command: /resolve\n3. Request URL: copy Production webhook URL from trigger node\n4. Description: Resolve a customer support handoff\n5. Usage hint: [contact_id]\n6. Save > Reinstall App to Workspace\n\nSETUP \u2014 n8n:\n1. Set Google Sheets credential + replace YOUR_GOOGLE_SHEET_ID in both GS nodes\n2. Set Slack credential on both Slack nodes\n3. Update channel name if different from support-handoff\n4. Activate > copy Production webhook URL > paste into Slack app\n\nGOOGLE SHEET columns needed:\ncontact_id | channel | contact_name | status | handoff_since | resolved_by | resolved_at"
      },
      "typeVersion": 1
    },
    {
      "id": "fc7430da-d925-4bb2-9db5-2f35ff72a3bc",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2672,
        -672
      ],
      "parameters": {
        "width": 848,
        "height": 3344,
        "content": "## Multi-Channel RAG AI Customer Support\n\n### How it works\n\n1. Documents are ingested via a form trigger and stored in Pinecone vector store for knowledge retrieval\n2. Incoming WhatsApp and Email messages are normalized and merged into a unified format\n3. The workflow checks if the contact is already in a handoff state using Google Sheets\n4. If not in handoff, the AI Support Agent generates responses using conversation memory and the knowledge base\n5. If the AI requests a human handoff, the contact is saved and the support team is notified via Slack\n6. Responses are routed back through the appropriate channel (WhatsApp or Email) based on the original contact method\n7. Support agents can resolve handoffs using a Slack /resolve command which updates the tracking sheet\n\n### Setup steps\n\n- - [ ] Configure WhatsApp Business API credentials for trigger and send nodes\n- - [ ] Set up Microsoft Outlook credentials for email integration\n- - [ ] Configure Slack bot token for notification and slash command handling\n- - [ ] Set up Pinecone vector store credentials and index configuration\n- - [ ] Configure Google Gemini API for embeddings model\n- - [ ] Set up Google Sheets credentials for handoff tracking sheet\n- - [ ] Configure OpenRouter API key for chat model access\n- - [ ] Create the handoff tracking sheet with columns: contact_id, in_handoff, resolved, resolved_by, resolved_at\n- - [ ] Deploy Slack slash command webhook URL in your Slack app configuration\n- - [ ] Configure knowledge base with your Pinecone index for document retrieval\n\n### Customization\n\nCustomize AI prompts in the AI Support Agent. Adjust handoff conditions in Google Sheets. Add more channels in the Route by Channel switches."
      },
      "typeVersion": 1
    },
    {
      "id": "0f0d9b55-b426-4f8c-92b5-94c759f3a7f3",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1104,
        -672
      ],
      "parameters": {
        "color": 7,
        "width": 768,
        "height": 752,
        "content": "## Document Ingestion Pipeline\n\nHandles form-based document uploads and stores them in Pinecone vector store for the knowledge base. Includes supporting tool nodes for document loading and token splitting."
      },
      "typeVersion": 1
    },
    {
      "id": "ed1f9074-cde5-4ec9-886b-0f111c7a8c09",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -976,
        352
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 304,
        "content": "## WhatsApp Message Ingestion\n\nProcesses incoming WhatsApp messages and normalizes them for unified handling."
      },
      "typeVersion": 1
    },
    {
      "id": "f5ad6dac-c0b7-4310-bdcb-e5a6797de641",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -976,
        688
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 304,
        "content": "## Email Message Ingestion\n\nProcesses incoming Email messages and normalizes them for unified handling."
      },
      "typeVersion": 1
    },
    {
      "id": "d913a578-0bbf-46ed-8175-5da637c954cc",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -416,
        336
      ],
      "parameters": {
        "color": 7,
        "width": 1120,
        "height": 464,
        "content": "## Handoff Check and Routing\n\nMerges normalized messages from WhatsApp and Email, checks Google Sheets for existing handoff status, and prepares data for AI agent routing."
      },
      "typeVersion": 1
    },
    {
      "id": "2678e868-df3e-42bf-98f6-f42d67a0f9d5",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        256,
        832
      ],
      "parameters": {
        "color": 7,
        "width": 944,
        "height": 736,
        "content": "## AI Support Agent & Memory\n\nAI-powered support agent that uses conversation memory, knowledge base retrieval, and structured output parsing to generate responses. Includes OpenRouter chat model integration."
      },
      "typeVersion": 1
    },
    {
      "id": "e00e4d37-17d7-462b-b775-92a56d23d5f7",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1232,
        368
      ],
      "parameters": {
        "color": 7,
        "width": 1072,
        "height": 1152,
        "content": "## Handoff Response & Routing\n\nHandles AI response routing - either marks contact for handoff (saving to sheet, notifying Slack) or routes answer back via the appropriate channel (WhatsApp or Email)."
      },
      "typeVersion": 1
    },
    {
      "id": "39059086-652a-43d9-9f0c-20cac109fae0",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1120,
        1824
      ],
      "parameters": {
        "color": 7,
        "width": 1760,
        "height": 560,
        "content": "## Slack Resolve Command Handler\n\nSlack slash command handler for resolving handoffs. Extracts contact ID, validates input, updates Google Sheets, and notifies support team."
      },
      "typeVersion": 1
    },
    {
      "id": "2f3ad301-472f-40fb-a5f0-1f23812b8bc9",
      "name": "Document Upload Form",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        -1056,
        -512
      ],
      "parameters": {
        "options": {
          "buttonLabel": "Upload & Ingest",
          "respondWithOptions": {
            "values": {
              "formSubmittedText": "Documents uploaded and ingested into the knowledge base successfully!"
            }
          }
        },
        "formTitle": "Knowledge Base Document Upload",
        "formFields": {
          "values": [
            {
              "fieldName": "documents",
              "fieldType": "file",
              "fieldLabel": "Select Documents",
              "requiredField": true,
              "acceptFileTypes": ".pdf,.docx,.txt"
            },
            {
              "fieldName": "uploaded_by",
              "fieldLabel": "Uploaded by (for audit log)",
              "placeholder": "e.g. John",
              "requiredField": true
            }
          ]
        },
        "responseMode": "lastNode",
        "formDescription": "Upload FAQ, policy, or product documents to update the AI support knowledge base. Accepted formats: PDF, DOCX, TXT."
      },
      "typeVersion": 2.5
    },
    {
      "id": "87021ae9-6778-46be-b011-ae447ea28c3b",
      "name": "Insert into Vector Store",
      "type": "@n8n/n8n-nodes-langchain.vectorStorePinecone",
      "position": [
        -832,
        -512
      ],
      "parameters": {
        "mode": "insert",
        "options": {
          "clearNamespace": false
        },
        "pineconeIndex": {
          "__rl": true,
          "mode": "list",
          "value": "test-index",
          "cachedResultName": "test-index"
        },
        "embeddingBatchSize": 100
      },
      "credentials": {
        "pineconeApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "ba08ab48-bb50-4ec4-be39-01aea8e81e70",
      "name": "Document Loader",
      "type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader",
      "position": [
        -688,
        -288
      ],
      "parameters": {
        "options": {
          "metadata": {
            "metadataValues": [
              {
                "name": "uploaded_by",
                "value": "={{ $json.uploaded_by }}"
              },
              {
                "name": "ingested_at",
                "value": "={{ $now.format('dd-MM-yyyy hh:mm:ss a') }}"
              },
              {
                "name": "file_name",
                "value": "={{ $json.documents[0].filename }}"
              }
            ]
          },
          "splitPages": true
        },
        "dataType": "binary",
        "textSplittingMode": "custom"
      },
      "typeVersion": 1.1
    },
    {
      "id": "f02009dc-1c6a-47ad-88d6-06e765a4f884",
      "name": "Ingestion Success",
      "type": "n8n-nodes-base.set",
      "position": [
        -480,
        -512
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "c1",
              "name": "status",
              "type": "string",
              "value": "success"
            },
            {
              "id": "c2",
              "name": "message",
              "type": "string",
              "value": "Documents successfully ingested into the knowledge base."
            },
            {
              "id": "c3",
              "name": "ingested_at",
              "type": "string",
              "value": "={{ $now.toISO() }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "569df40c-b92f-49aa-b225-eb7d08950156",
      "name": "Embeddings Model",
      "type": "@n8n/n8n-nodes-langchain.embeddingsGoogleGemini",
      "position": [
        -960,
        -336
      ],
      "parameters": {
        "modelName": "models/gemini-embedding-2"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "08ac5465-033a-4344-ab2f-2080be45af66",
      "name": "Token Splitter",
      "type": "@n8n/n8n-nodes-langchain.textSplitterTokenSplitter",
      "position": [
        -592,
        -80
      ],
      "parameters": {
        "chunkSize": 500,
        "chunkOverlap": 100
      },
      "typeVersion": 1
    },
    {
      "id": "ff157d22-d71e-40af-af6e-81319d9900f0",
      "name": "WhatsApp Trigger",
      "type": "n8n-nodes-base.whatsAppTrigger",
      "position": [
        -928,
        480
      ],
      "parameters": {
        "options": {
          "messageStatusUpdates": []
        },
        "updates": [
          "messages"
        ]
      },
      "credentials": {
        "whatsAppTriggerApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "f74560ba-8af6-4e02-ad08-840c12250bce",
      "name": "Normalize WhatsApp",
      "type": "n8n-nodes-base.set",
      "position": [
        -704,
        480
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a1",
              "name": "channel",
              "type": "string",
              "value": "whatsapp"
            },
            {
              "id": "a2",
              "name": "contact_id",
              "type": "string",
              "value": "={{ $json.contacts?.[0]?.wa_id ?? $json.messages?.[0]?.from ?? \"unknown\" }}"
            },
            {
              "id": "a3",
              "name": "contact_name",
              "type": "string",
              "value": "={{ $json.contacts?.[0]?.profile?.name ?? \"Customer\" }}"
            },
            {
              "id": "a4",
              "name": "message_text",
              "type": "string",
              "value": "={{ $json.messages?.[0]?.text?.body ?? \"\" }}"
            },
            {
              "id": "a5",
              "name": "email_id",
              "type": "string",
              "value": ""
            },
            {
              "id": "a6",
              "name": "email_from",
              "type": "string",
              "value": ""
            },
            {
              "id": "a7",
              "name": "email_subject",
              "type": "string",
              "value": ""
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "45d4ba65-33b3-475a-9763-97dec7ec8e8c",
      "name": "AI Support Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        464,
        960
      ],
      "parameters": {
        "text": "=Channel: {{ $json.channel }}\nCustomer name: {{ $json.contact_name }}\nMessage: {{ $json.message_text }}",
        "options": {
          "maxIterations": 6,
          "systemMessage": "You are a helpful customer support assistant.\nUse the \"Knowledge Base\" tool to answer questions about products, services, fees, policies, and processes.\n\nSTRICT RULES:\n1. Only answer using information from the Knowledge Base tool. Do not guess or invent answers.\n2. Never claim to access individual account data, balances, orders, or personal records. If asked, explain you cannot access personal account information and offer to connect the customer with a human agent.\n3. If the customer explicitly asks to speak with a human agent (any phrasing), set handoff_requested=true and write a warm brief reply saying you are connecting them with the support team.\n4. If the Knowledge Base has no answer, say so honestly and offer a human handoff.\n5. Keep replies concise, friendly, and professional."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3.1
    },
    {
      "id": "7f46c59d-706c-4736-840d-27687622bc70",
      "name": "Conversation Memory",
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "position": [
        432,
        1248
      ],
      "parameters": {
        "sessionKey": "=session_key:{{ $('Is Contact In Handoff?').item.json.contact_id }}",
        "sessionIdType": "customKey",
        "contextWindowLength": 8
      },
      "typeVersion": 1.4
    },
    {
      "id": "5f9e2de9-9606-4400-8806-c7f610067778",
      "name": "Knowledge Base",
      "type": "@n8n/n8n-nodes-langchain.vectorStorePinecone",
      "position": [
        560,
        1248
      ],
      "parameters": {
        "mode": "retrieve-as-tool",
        "options": {},
        "pineconeIndex": {
          "__rl": true,
          "mode": "list",
          "value": "twin-index",
          "cachedResultName": "twin-index"
        },
        "toolDescription": "Search the company knowledge base to answer customer questions about products, services, fees, policies, and processes. Does NOT contain individual customer account data.",
        "includeDocumentMetadata": false
      },
      "credentials": {
        "pineconeApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "0580725e-b2b1-402d-84fb-bf3c4137428e",
      "name": "Structured Reply Schema",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        848,
        1248
      ],
      "parameters": {
        "jsonSchemaExample": "{\"reply\":  \"Our standard transfer fee is 1.5% per transaction.\", \"handoff_requested\":  false}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "b381bedd-97c9-4c2f-a6cb-6f396489cecd",
      "name": "Is Handoff Requested?",
      "type": "n8n-nodes-base.if",
      "position": [
        1280,
        896
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "hh1",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.handoff_requested }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "849a2c10-0930-4b78-a44a-18ff9d0c4af3",
      "name": "Notify Support Channel",
      "type": "n8n-nodes-base.slack",
      "position": [
        1712,
        800
      ],
      "parameters": {
        "text": "=:rotating_light: *New Human Handoff Request*\n*Channel:* {{ $json.channel }}\n*Contact:* {{ $json.contact_name }} ({{ $json.contact_id }})\n*Last message:* {{ $('Prepare Reply Output').item.json.message_text }}\n\n_AI is now silent for this contact. Reply to them directly on {{ $json.channel }}._\n_To re-enable AI, POST to the resolve webhook with body:_ `{\"contact_id\":  \"{{ $json.contact_id }}\"}`",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "support-handoff",
          "cachedResultName": "#support-handoff"
        },
        "otherOptions": {
          "unfurl_links": false,
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.5
    },
    {
      "id": "69306084-577b-4f85-834d-6b632a336f1c",
      "name": "Route by Channel (Handoff)",
      "type": "n8n-nodes-base.switch",
      "position": [
        1952,
        768
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "784867aa-f45b-4d85-9d3f-f97ae449c52e",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('Prepare Reply Output').item.json.channel }}",
                    "rightValue": "whatsapp"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "e6ad8580-818e-43a0-833f-d022de122b34",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $('Prepare Reply Output').item.json.channel }}",
                    "rightValue": "email"
                  }
                ]
              }
            }
          ]
        },
        "options": {
          "fallbackOutput": "none"
        }
      },
      "typeVersion": 3.2
    },
    {
      "id": "f4d290bc-996d-4223-ae45-c770a6bf6322",
      "name": "Send WhatsApp Reply",
      "type": "n8n-nodes-base.whatsApp",
      "position": [
        2160,
        496
      ],
      "parameters": {
        "textBody": "={{ $('Prepare Reply Output').item.json.reply }}",
        "operation": "send",
        "phoneNumberId": "1183690101495838",
        "additionalFields": {},
        "recipientPhoneNumber": "={{ $('Prepare Reply Output').item.json.contact_id }}"
      },
      "credentials": {
        "whatsAppApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "58d2c960-33c8-4dee-b21a-07ae310ca469",
      "name": "Send Email Reply",
      "type": "n8n-nodes-base.microsoftOutlook",
      "position": [
        2160,
        944
      ],
      "parameters": {
        "subject": "=Re: {{ $('Prepare Reply Output').item.json.email_subject }}",
        "bodyContent": "={{ $('Prepare Reply Output').item.json.reply }}",
        "toRecipients": "={{ $('Prepare Reply Output').item.json.email_from }}",
        "additionalFields": {
          "bodyContentType": "Text",
          "saveToSentItems": true
        }
      },
      "typeVersion": 2
    },
    {
      "id": "f3d0b6b2-c853-48fb-8b29-eac0a18342cc",
      "name": "Route by Channel (Answer)",
      "type": "n8n-nodes-base.switch",
      "position": [
        1504,
        1216
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.channel }}",
                    "rightValue": "whatsapp"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.channel }}",
                    "rightValue": "email"
                  }
                ]
              }
            }
          ]
        },
        "options": {
          "fallbackOutput": "none"
        }
      },
      "typeVersion": 3.2
    },
    {
      "id": "8a290350-7f7e-4f6d-93b8-c07174255a7a",
      "name": "Send WhatsApp Answer",
      "type": "n8n-nodes-base.whatsApp",
      "position": [
        1728,
        1120
      ],
      "parameters": {
        "textBody": "={{ $json.reply }}",
        "operation": "send",
        "phoneNumberId": "1183690101495838",
        "additionalFields": {},
        "recipientPhoneNumber": "={{ $json.contact_id }}"
      },
      "credentials": {
        "whatsAppApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "ce442153-e37c-477a-a15c-17a682ea57db",
      "name": "Send Email Answer",
      "type": "n8n-nodes-base.microsoftOutlook",
      "disabled": true,
      "position": [
        1728,
        1344
      ],
      "parameters": {
        "subject": "=Re: {{ $('Prepare Reply Output').item.json.email_subject }}",
        "bodyContent": "={{ $('Prepare Reply Output').item.json.reply }}",
        "toRecipients": "={{ $('Prepare Reply Output').item.json.email_from }}",
        "additionalFields": {
          "bodyContentType": "Text",
          "saveToSentItems": true
        }
      },
      "typeVersion": 2
    },
    {
      "id": "98c7a765-d43d-40a1-9a6c-f1d60b27e516",
      "name": "Outlook Trigger",
      "type": "n8n-nodes-base.microsoftOutlookTrigger",
      "disabled": true,
      "position": [
        -928,
        816
      ],
      "parameters": {
        "filters": {
          "readStatus": "unread"
        },
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "946b08c9-a350-4cf4-b398-cb5a5bccb888",
      "name": "Normalize Email",
      "type": "n8n-nodes-base.set",
      "disabled": true,
      "position": [
        -704,
        816
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "b1",
              "name": "channel",
              "type": "string",
              "value": "email"
            },
            {
              "id": "b2",
              "name": "contact_id",
              "type": "string",
              "value": "={{ $json.from?.emailAddress?.address ?? \"unknown\" }}"
            },
            {
              "id": "b3",
              "name": "contact_name",
              "type": "string",
              "value": "={{ $json.from?.emailAddress?.name ?? \"Customer\" }}"
            },
            {
              "id": "b4",
              "name": "message_text",
              "type": "string",
              "value": "={{ $json.bodyPreview ?? \"\" }}"
            },
            {
              "id": "b5",
              "name": "email_id",
              "type": "string",
              "value": "={{ $json.id ?? \"\" }}"
            },
            {
              "id": "b6",
              "name": "email_from",
              "type": "string",
              "value": "={{ $json.from?.emailAddress?.address ?? \"\" }}"
            },
            {
              "id": "b7",
              "name": "email_subject",
              "type": "string",
              "value": "={{ $json.subject ?? \"\" }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "e363b9ef-8edc-4833-9569-3a05784ee8c3",
      "name": "Embeddings Google Gemini",
      "type": "@n8n/n8n-nodes-langchain.embeddingsGoogleGemini",
      "position": [
        640,
        1392
      ],
      "parameters": {
        "modelName": "models/gemini-embedding-2"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "65542e2f-461a-4a39-9159-d0123aeee240",
      "name": "OpenRouter Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        304,
        1248
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "4b42db6d-1f3c-477c-a7bd-a8c27b91a8b7",
      "name": "Is Contact In Handoff?",
      "type": "n8n-nodes-base.if",
      "position": [
        256,
        624
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "ah1",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.in_handoff }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "1649850a-dc53-46ff-93fc-338a58fb8876",
      "name": "Forward to Support Channel",
      "type": "n8n-nodes-base.slack",
      "position": [
        560,
        464
      ],
      "parameters": {
        "text": "=:speech_balloon: *Customer follow-up (agent handling)*\n*Channel:* {{ $json.channel }}\n*Contact:* {{ $json.contact_name }} ({{ $json.contact_id }})\n*Message:* {{ $json.message_text }}\n\n_Reply to this customer directly on {{ $json.channel }}. AI remains silent._",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "support-handoff"
        },
        "otherOptions": {
          "unfurl_links": false,
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.5
    },
    {
      "id": "88c56117-6c73-4f95-ae2c-49798fac0caf",
      "name": "Prepare Agent Input",
      "type": "n8n-nodes-base.set",
      "position": [
        80,
        624
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "hf1",
              "name": "in_handoff",
              "type": "boolean",
              "value": "={{ $json.status === 'active' }}"
            },
            {
              "id": "6ad7c48b-e915-498b-bc32-66b9bf1e6766",
              "name": "channel",
              "type": "string",
              "value": "={{ $('Merge').item.json.channel }}"
            },
            {
              "id": "e5651d05-d280-4484-8ab9-4502f18be7a8",
              "name": "contact_id",
              "type": "string",
              "value": "={{ $('Merge').item.json.contact_id }}"
            },
            {
              "id": "272566d5-9111-46ee-bdeb-5d6e4dadbae7",
              "name": "contact_name",
              "type": "string",
              "value": "={{ $('Merge').item.json.contact_name }}"
            },
            {
              "id": "19fa7f7d-13d9-46d7-a2fe-6a323b9ba985",
              "name": "message_text",
              "type": "string",
              "value": "={{ $('Merge').item.json.message_text }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "1c31a7da-0280-40db-a48e-32715545641c",
      "name": "Prepare Reply Output",
      "type": "n8n-nodes-base.set",
      "position": [
        1056,
        960
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "m1",
              "name": "channel",
              "type": "string",
              "value": "={{ $('Prepare Agent Input').item.json.channel }}"
            },
            {
              "id": "m2",
              "name": "contact_id",
              "type": "string",
              "value": "={{ $('Prepare Agent Input').item.json.contact_id }}"
            },
            {
              "id": "m3",
              "name": "contact_name",
              "type": "string",
              "value": "={{ $('Prepare Agent Input').item.json.contact_name }}"
            },
            {
              "id": "m4",
              "name": "message_text",
              "type": "string",
              "value": "={{ $('Prepare Agent Input').item.json.message_text }}"
            },
            {
              "id": "m5",
              "name": "email_from",
              "type": "string",
              "value": "={{ $('Prepare Agent Input').item.json.email_from || \"\" }}"
            },
            {
              "id": "m6",
              "name": "email_subject",
              "type": "string",
              "value": "={{ $('Prepare Agent Input').item.json.email_subject || \"\" }}"
            },
            {
              "id": "m7",
              "name": "reply",
              "type": "string",
              "value": "={{ $json.output.reply }}"
            },
            {
              "id": "m8",
              "name": "handoff_requested",
              "type": "boolean",
              "value": "={{ $json.output.handoff_requested }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "a3d6adf9-f057-4971-935f-40cb9472e81b",
      "name": "Merge",
      "type": "n8n-nodes-base.set",
      "position": [
        -368,
        608
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "b1f57517-0049-4ead-9f34-37d6918c7fc5",
              "name": "channel",
              "type": "string",
              "value": "={{ $json.channel }}"
            },
            {
              "id": "9c8f55a5-2f47-4be5-9e89-d80d06a686b3",
              "name": "contact_id",
              "type": "string",
              "value": "={{ $json.contact_id }}"
            },
            {
              "id": "b7bb7e2b-65e9-41e5-8759-cb4c09839698",
              "name": "contact_name",
              "type": "string",
              "value": "={{ $json.contact_name }}"
            },
            {
              "id": "398e7ba6-fc2f-438e-b82d-1b12b72ad351",
              "name": "message_text",
              "type": "string",
              "value": "={{ $json.message_text }}"
            },
            {
              "id": "6ee79e82-2d07-4cc3-acf4-b6d8a71be032",
              "name": "email_id",
              "type": "string",
              "value": "={{ $json.email_id }}"
            },
            {
              "id": "2532d33c-0a3f-44f5-af70-9756b00ab0d4",
              "name": "email_from",
              "type": "string",
              "value": "={{ $json.email_from }}"
            },
            {
              "id": "01bc58bf-67fc-4788-acbf-5f1a3ca8eb8f",
              "name": "email_subject",
              "type": "string",
              "value": "={{ $json.email_subject }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "126d8777-a6f6-4aab-a8f7-259e9d34cccf",
      "name": "Check Handoff",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -144,
        608
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $json.contact_id }}",
              "lookupColumn": "contact_id"
            },
            {
              "lookupValue": "active",
              "lookupColumn": "status"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Handoffs"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "1uFE3grtjXXesgaac8NAr1Ql443sloNUwRn279f0VqcQ"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7,
      "alwaysOutputData": true
    },
    {
      "id": "b9e416ee-76af-4f58-aa61-fc5a7ae70d65",
      "name": "Save Handoff",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1504,
        800
      ],
      "parameters": {
        "columns": {
          "value": {
            "status": "active",
            "channel": "={{ $json.channel }}",
            "contact_id": "={{ $json.contact_id }}",
            "contact_name": "={{ $json.contact_name }}",
            "handoff_since": "={{ $now.toISO() }}"
          },
          "schema": [
            {
              "id": "contact_id",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "contact_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "channel",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "channel",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "contact_name",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "contact_name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "handoff_since",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "handoff_since",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "resolved_at",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "resolved_at",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "contact_id"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Handoffs"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "1uFE3grtjXXesgaac8NAr1Ql443sloNUwRn279f0VqcQ"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "f54e6f03-6723-4d99-ad9f-c7a88e056970",
      "name": "Slack Slash Command (/resolve)",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -1072,
        2128
      ],
      "parameters": {
        "path": "slack-resolve-handoff",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "1912b821-2234-4b77-99df-a353abe94f97",
      "name": "Extract Contact ID",
      "type": "n8n-nodes-base.set",
      "position": [
        -848,
        2128
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "e1",
              "name": "contact_id",
              "type": "string",
              "value": "={{ $json.body.text.trim() }}"
            },
            {
              "id": "e2",
              "name": "resolved_by",
              "type": "string",
              "value": "={{ $json.body.user_name }}"
            },
            {
              "id": "e3",
              "name": "channel_id",
              "type": "string",
              "value": "={{ $json.body.channel_id }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "709c5ede-d0f3-40ef-916a-c3e8365cb82c",
      "name": "Contact ID Provided?",
      "type": "n8n-nodes-base.if",
      "position": [
        -624,
        2128
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "v1",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              },
              "leftValue": "={{ $json.contact_id }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "8dd8e191-d285-46a0-9d7a-59642928e526",
      "name": "Respond to Slack (OK)",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -400,
        2032
      ],
      "parameters": {
        "options": {
          "responseCode": 200
        },
        "respondWith": "text",
        "responseBody": "=:hourglass: Resolving handoff for *{{ $json.contact_id }}*... Stand by."
      },
      "typeVersion": 1.5
    },
    {
      "id": "768520b3-374b-49fd-9461-33d004d64217",
      "name": "Find Handoff Row",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -176,
        2032
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $json.contact_id }}",
              "lookupColumn": "contact_id"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1uFE3grtjXXesgaac8NAr1Ql443sloNUwRn279f0VqcQ/edit#gid=0",
          "cachedResultName": "Handoffs"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "1uFE3grtjXXesgaac8NAr1Ql443sloNUwRn279f0VqcQ"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7,
      "alwaysOutputData": true
    },
    {
      "id": "3b6c6139-8f2f-41bf-968d-78186e2dba31",
      "name": "Active Handoff Found?",
      "type": "n8n-nodes-base.if",
      "position": [
        48,
        2032
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "r1",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.status }}",
              "rightValue": "active"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "35867290-ebba-408e-afbe-81a99a1b2610",
      "name": "Mark Resolved in Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        272,
        1936
      ],
      "parameters": {
        "columns": {
          "value": {
            "status": "resolved",
            "contact_id": "={{ $json.contact_id }}",
            "resolved_at": "={{ $now.toFormat(\"dd-MM-yyyy hh:mm:ss a\") }}",
            "resolved_by": "={{ $(\"Extract Contact ID\").item.json.resolved_by }}"
          },
          "schema": [
            {
              "id": "contact_id",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "contact_id",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "resolved_by",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "resolved_by",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "resolved_at",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "resolved_at",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "contact_id"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Handoffs"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "86841007-c874-48c7-9446-453b16fd83a8",
      "name": "Notify Not Found",
      "type": "n8n-nodes-base.slack",
      "position": [
        272,
        2128
      ],
      "parameters": {
        "text": "=:warning: No active handoff found for *{{ $(\"Extract Contact ID\").item.json.contact_id }}*\n_Either already resolved or the contact ID is incorrect._",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "support-handoff"
        },
        "otherOptions": {
          "unfurl_links": false,
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.5
    },
    {
      "id": "d780d478-c9ea-4dc6-b5a0-e6d78734add2",
      "name": "Respond to Slack (Error)",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -400,
        2224
      ],
      "parameters": {
        "options": {
          "responseCode": 200
        },
        "respondWith": "text",
        "responseBody": ": x: Usage: /resolve <contact_id>  Example: /resolve user@example.com"
      },
      "typeVersion": 1.5
    },
    {
      "id": "1fdf26ae-ebcb-4dd4-b0c4-1ef18824c56e",
      "name": "Notify Support Channel1",
      "type": "n8n-nodes-base.slack",
      "position": [
        496,
        1936
      ],
      "parameters": {
        "text": "=:white_check_mark: Handoff resolved by *{{ $(\"Extract Contact ID\").item.json.resolved_by }}*\n*Contact:* {{ $(\"Find Handoff Row\").item.json.contact_name }} ({{ $(\"Find Handoff Row\").item.json.contact_id }})\n*Channel:* {{ $(\"Find Handoff Row\").item.json.channel }}\n*Resolved at:* {{ $now.toFormat(\"dd-MM-yyyy hh:mm:ss a\") }}\n\n_AI support is now re-enabled for this contact._",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "support-handoff",
          "cachedResultName": "#support-handoff"
        },
        "otherOptions": {
          "unfurl_links": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.5
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "8e3e9545-c097-4655-958d-7c01a1ed82cc",
  "nodeGroups": [],
  "connections": {
    "Merge": {
      "main": [
        [
          {
            "node": "Check Handoff",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Handoff": {
      "main": [
        [
          {
            "node": "Notify Support Channel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Handoff": {
      "main": [
        [
          {
            "node": "Prepare Agent Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Knowledge Base": {
      "ai_tool": [
        [
          {
            "node": "AI Support Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Token Splitter": {
      "ai_textSplitter": [
        [
          {
            "node": "Document Loader",
            "type": "ai_textSplitter",
            "index": 0
          }
        ]
      ]
    },
    "Document Loader": {
      "ai_document": [
        [
          {
            "node": "Insert into Vector Store",
            "type": "ai_document",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Email": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Outlook Trigger": {
      "main": [
        [
          {
            "node": "Normalize Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Support Agent": {
      "main": [
        [
          {
            "node": "Prepare Reply Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Embeddings Model": {
      "ai_embedding": [
        [
          {
            "node": "Insert into Vector Store",
            "type": "ai_embedding",
            "index": 0
          }
        ]
      ]
    },
    "Find Handoff Row": {
      "main": [
        [
          {
            "node": "Active Handoff Found?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "WhatsApp Trigger": {
      "main": [
        [
          {
            "node": "Normalize WhatsApp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Contact ID": {
      "main": [
        [
          {
            "node": "Contact ID Provided?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize WhatsApp": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Conversation Memory": {
      "ai_memory": [
        [
          {
            "node": "AI Support Agent",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Agent Input": {
      "main": [
        [
          {
            "node": "Is Contact In Handoff?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Contact ID Provided?": {
      "main": [
        [
          {
            "node": "Respond to Slack (OK)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond to Slack (Error)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Document Upload Form": {
      "main": [
        [
          {
            "node": "Insert into Vector Store",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Reply Output": {
      "main": [
        [
          {
            "node": "Is Handoff Requested?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Active Handoff Found?": {
      "main": [
        [
          {
            "node": "Mark Resolved in Sheet",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Notify Not Found",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Handoff Requested?": {
      "main": [
        [
          {
            "node": "Save Handoff",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Route by Channel (Answer)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Support Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Respond to Slack (OK)": {
      "main": [
        [
          {
            "node": "Find Handoff Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Contact In Handoff?": {
      "main": [
        [
          {
            "node": "Forward to Support Channel",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "AI Support Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Resolved in Sheet": {
      "main": [
        [
          {
            "node": "Notify Support Channel1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notify Support Channel": {
      "main": [
        [
          {
            "node": "Route by Channel (Handoff)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Reply Schema": {
      "ai_outputParser": [
        [
          {
            "node": "AI Support Agent",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Embeddings Google Gemini": {
      "ai_embedding": [
        [
          {
            "node": "Knowledge Base",
            "type": "ai_embedding",
            "index": 0
          }
        ]
      ]
    },
    "Insert into Vector Store": {
      "main": [
        [
          {
            "node": "Ingestion Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Channel (Answer)": {
      "main": [
        [
          {
            "node": "Send WhatsApp Answer",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Email Answer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Channel (Handoff)": {
      "main": [
        [
          {
            "node": "Send WhatsApp Reply",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Email Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack Slash Command (/resolve)": {
      "main": [
        [
          {
            "node": "Extract Contact ID",
            "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

A production-ready 3-workflow system that handles customer support across WhatsApp and Email using RAG-powered AI. Automatically routes queries, detects escalation intent, logs handoffs to Google Sheets, alerts your team via Slack, and lets agents resolve cases with a single…

Source: https://n8n.io/workflows/16545/ — 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

Your AI workforce is ready. Are you?

Google Sheets Tool, Mcp Trigger, Google Drive +29
AI & RAG

This advanced n8n workflow automates the full lead enrichment, qualification, and personalized outreach process tailored specifically for the B2B real estate sector. Integrating top platforms like Api

N8N Nodes Fillout, OpenAI Chat, Pinecone Vector Store +11
AI & RAG

The "WhatsApp Productivity Assistant with Memory and AI Imaging" is a comprehensive n8n workflow that transforms your WhatsApp into a powerful, multi-talented AI assistant. It's designed to handle a w

WhatsApp Trigger, Agent, HTTP Request +20
AI & RAG

This n8n template automatically classifies incoming emails (Sales, Support, Internal, Finance, Promotions) and routes them to a dedicated OpenAI LLM Agent for processing. Depending on the category, th

OpenAI, Gmail, Text Classifier +16
AI & RAG

This simple philosophy changes the way we think about automated sales agents. Context changes everything. In this 4-part workflow, we start by creating a knowledge base that will act as context across

Pinecone Vector Store, Document Default Data Loader, Text Splitter Recursive Character Text Splitter +12