{
  "id": "amip0qiSwBAX6Zvs",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Ethical AI Customer Support Router with Human-in-the-Loop",
  "tags": [],
  "nodes": [
    {
      "id": "979f659c-0305-4b00-875f-ae248a7f1b54",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        96,
        -160
      ],
      "parameters": {
        "width": 980,
        "height": 984,
        "content": "## Ethical AI Customer Support Router with Human-in-the-Loop\n\nThis workflow classifies incoming support messages from email or Slack, drafts AI-generated replies for routine queries, and automatically pauses to notify a human agent for sensitive topics such as complaints, emotional distress, billing disputes, legal concerns, or escalation requests.\n\n### Who's it for\n\u2022 Customer support teams handling 50+ messages per day\n\u2022 SaaS companies needing compliant, empathetic AI responses\n\u2022 E-commerce brands managing returns, refunds, and complaints\n\u2022 Any team that wants AI speed with human oversight on sensitive issues\n\n### How it works / What it does\n1. Captures incoming messages from Email (webhook) or Slack\n2. Normalizes message context (sender, channel, body, urgency signals)\n3. AI classifies the message into a category and sensitivity tier\n4. Routine queries \u2192 AI drafts reply \u2192 sent automatically\n5. Sensitive queries \u2192 AI drafts reply \u2192 paused for human review\n6. Human approves, edits, or rejects the draft via webhook resume\n7. Final reply sent to customer via email or Slack\n8. All interactions logged to Google Sheet with category, tier, and resolution\n\n### How to set up\n1. Import this workflow\n2. Configure credentials (Gmail/IMAP, Slack, OpenAI/Anthropic, Google Sheets)\n3. Update your support tone, escalation keywords, and team Slack channel\n4. Activate workflow\n\n### Requirements\n\u2022 Gmail or IMAP webhook / Slack Events API\n\u2022 OpenAI / Anthropic / Grok API\n\u2022 Slack Bot Token (for human notifications)\n\u2022 Google Sheets for audit logging\n\u2022 HTTP endpoint for human approval webhook\n\n### How to customize the workflow\n\u2022 Edit sensitivity tiers in the Python classification node\n\u2022 Change escalation keywords list\n\u2022 Update Slack notification channel and message template\n\u2022 Modify AI tone and persona in the AI draft node\n\u2022 Adjust wait timeout for human review window"
      },
      "typeVersion": 1
    },
    {
      "id": "38bdbc05-cabc-4822-bc36-dc31212f60de",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1184,
        -48
      ],
      "parameters": {
        "color": 4,
        "width": 780,
        "height": 520,
        "content": "## 1. Trigger & Message Intake"
      },
      "typeVersion": 1
    },
    {
      "id": "6a9ad606-cf79-46af-a3f3-b9eef12818a1",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2048,
        -32
      ],
      "parameters": {
        "color": 3,
        "width": 1268,
        "height": 532,
        "content": "## 2. AI Classification & Draft Generation"
      },
      "typeVersion": 1
    },
    {
      "id": "a1c950e0-c7db-4dfb-94cb-4ebd7902807f",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1152,
        560
      ],
      "parameters": {
        "color": 3,
        "width": 552,
        "height": 300,
        "content": "## 3. Human-in-the-Loop Review"
      },
      "typeVersion": 1
    },
    {
      "id": "b2d96e33-4a7f-41b8-a732-268668f563d7",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1760,
        528
      ],
      "parameters": {
        "color": 2,
        "width": 860,
        "height": 356,
        "content": "## 4. Send Reply & Audit Log"
      },
      "typeVersion": 1
    },
    {
      "id": "83e8538d-12e1-4e5e-a726-81671d8737c4",
      "name": "Webhook - New Support Message",
      "type": "n8n-nodes-base.webhook",
      "position": [
        1312,
        128
      ],
      "parameters": {
        "path": "support-message-inbound",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 1.1
    },
    {
      "id": "37986229-181b-4a93-82b3-de31e69f0723",
      "name": "Poll Support Inbox",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        1296,
        304
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "*/10 * * * *"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "b638936b-78eb-4f68-9fee-9a611baca3b0",
      "name": "Normalize Message Context",
      "type": "n8n-nodes-base.set",
      "position": [
        1584,
        144
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "name": "sender",
              "type": "string",
              "value": "={{ $json.from || $json.body?.from || $json.event?.user || '' }}"
            },
            {
              "name": "channel",
              "type": "string",
              "value": "={{ $json.channel || $json.body?.channel || 'email' }}"
            },
            {
              "name": "subject",
              "type": "string",
              "value": "={{ $json.subject || $json.body?.subject || $json.event?.text?.substring(0, 60) || 'Support Request' }}"
            },
            {
              "name": "body",
              "type": "string",
              "value": "={{ $json.text || $json.body?.text || $json.event?.text || $json.body || '' }}"
            },
            {
              "name": "messageId",
              "type": "string",
              "value": "={{ $json.messageId || $json.event?.ts || Date.now().toString() }}"
            },
            {
              "name": "threadId",
              "type": "string",
              "value": "={{ $json.threadId || $json.event?.thread_ts || '' }}"
            },
            {
              "name": "customerName",
              "type": "string",
              "value": "={{ $json.name || $json.body?.name || 'Customer' }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "66f2889d-4460-4d7a-a800-192fdf29f93b",
      "name": "Python - Classify Sensitivity Tier",
      "type": "n8n-nodes-base.code",
      "position": [
        1792,
        144
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "python"
      },
      "typeVersion": 2
    },
    {
      "id": "805271d3-c352-431d-a367-53ec9cc83a4c",
      "name": "AI - Classify and Draft Reply",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2112,
        112
      ],
      "parameters": {
        "text": "=You are an expert, empathetic customer support AI for a SaaS company. Your job is to:\n1. Classify the support message into one category.\n2. Assign a sensitivity tier.\n3. Draft a professional, warm, and helpful reply.\n\nMessage Details:\nFrom: {{ $json.customerName }} <{{ $json.sender }}>\nChannel: {{ $json.channel }}\nSubject: {{ $json.subject }}\nMessage Body:\n{{ $json.body }}\n\nPre-classified Sensitivity Tier (from keyword scan): {{ $json.sensitivityTier }}\nDetected Signals: {{ $json.detectedSignals }}\n\nClassification Categories:\n- billing_issue\n- technical_bug\n- feature_request\n- general_inquiry\n- complaint\n- emotional_distress\n- legal_or_compliance\n- escalation_request\n- account_access\n- refund_request\n\nSensitivity Tiers:\n- ROUTINE: Safe to auto-send\n- SENSITIVE: Requires human review before sending\n\nRespond ONLY in this exact JSON format:\n{\n  \"category\": \"...\",\n  \"sensitivityTier\": \"ROUTINE\" or \"SENSITIVE\",\n  \"sentimentScore\": -1.0 to 1.0,\n  \"urgencyLevel\": \"low\" or \"medium\" or \"high\",\n  \"draftReply\": \"...\",\n  \"internalNote\": \"...\"\n}",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 1.6
    },
    {
      "id": "8461b7fe-e4fa-4d36-bc91-4e66a8f60319",
      "name": "JS - Parse AI Classification",
      "type": "n8n-nodes-base.code",
      "position": [
        2368,
        336
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const item = $input.item.json;\nlet aiOutput = {};\ntry {\n  const raw = item.output || item.response || item.text || '{}';\n  const clean = raw.replace(/```json|```/g, '').trim();\n  aiOutput = JSON.parse(clean);\n} catch (e) {\n  aiOutput = {\n    category: 'general_inquiry',\n    sensitivityTier: 'SENSITIVE',\n    sentimentScore: 0,\n    urgencyLevel: 'medium',\n    draftReply: 'Thank you for reaching out. A member of our team will be in touch shortly.',\n    internalNote: 'AI parsing failed \u2014 manual review required.'\n  };\n}\nreturn {\n  json: {\n    ...item,\n    category: aiOutput.category || 'general_inquiry',\n    sensitivityTier: aiOutput.sensitivityTier || 'SENSITIVE',\n    sentimentScore: aiOutput.sentimentScore || 0,\n    urgencyLevel: aiOutput.urgencyLevel || 'medium',\n    draftReply: aiOutput.draftReply || '',\n    internalNote: aiOutput.internalNote || '',\n    processedAt: new Date().toISOString(),\n    processedDate: new Date().toISOString().split('T')[0]\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "e72e3356-4280-48fb-9a72-87ca9514521e",
      "name": "Route - Sensitive or Routine?",
      "type": "n8n-nodes-base.filter",
      "position": [
        2592,
        336
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "conditions": [
            {
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.sensitivityTier }}",
              "rightValue": "SENSITIVE"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "0401c70d-f1c9-4695-bea7-ac1c9d6c51ba",
      "name": "Notify Human via Slack",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2816,
        336
      ],
      "parameters": {
        "url": "https://slack.com/api/chat.postMessage",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"channel\": \"#support-escalations\",\n  \"text\": \":rotating_light: *Human Review Required* :rotating_light:\",\n  \"blocks\": [\n    {\n      \"type\": \"section\",\n      \"text\": {\n        \"type\": \"mrkdwn\",\n        \"text\": \":rotating_light: *Human Review Required*\\n*From:* {{ $json.customerName }} ({{ $json.sender }})\\n*Category:* `{{ $json.category }}`  |  *Urgency:* `{{ $json.urgencyLevel }}`  |  *Sentiment:* `{{ $json.sentimentScore }}`\\n*Subject:* {{ $json.subject }}\"\n      }\n    },\n    {\n      \"type\": \"section\",\n      \"text\": {\n        \"type\": \"mrkdwn\",\n        \"text\": \"*Original Message:*\\n{{ ($json.body || '').substring(0, 300) }}\"\n      }\n    },\n    {\n      \"type\": \"section\",\n      \"text\": {\n        \"type\": \"mrkdwn\",\n        \"text\": \"*AI Draft Reply:*\\n{{ ($json.draftReply || '').substring(0, 400) }}\"\n      }\n    },\n    {\n      \"type\": \"section\",\n      \"text\": {\n        \"type\": \"mrkdwn\",\n        \"text\": \"*Internal Note:* {{ $json.internalNote }}\"\n      }\n    },\n    {\n      \"type\": \"actions\",\n      \"elements\": [\n        {\n          \"type\": \"button\",\n          \"text\": { \"type\": \"plain_text\", \"text\": \":white_check_mark: Approve & Send\" },\n          \"style\": \"primary\",\n          \"url\": \"https://your-n8n-instance.com/webhook/support-human-approve?messageId={{ $json.messageId }}&action=approve\"\n        },\n        {\n          \"type\": \"button\",\n          \"text\": { \"type\": \"plain_text\", \"text\": \":x: Reject Draft\" },\n          \"style\": \"danger\",\n          \"url\": \"https://your-n8n-instance.com/webhook/support-human-approve?messageId={{ $json.messageId }}&action=reject\"\n        }\n      ]\n    }\n  ]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_TOKEN_HERE"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "2a293d14-3039-477f-82aa-afb4d1ed5078",
      "name": "Webhook - Human Approval Response",
      "type": "n8n-nodes-base.webhook",
      "position": [
        1344,
        656
      ],
      "parameters": {
        "path": "support-human-approve",
        "options": {},
        "responseMode": "responseNode"
      },
      "typeVersion": 1.1
    },
    {
      "id": "e65ce847-f9fa-4b1b-815f-96f8dec5eade",
      "name": "Filter - Approved by Human?",
      "type": "n8n-nodes-base.filter",
      "position": [
        1568,
        656
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "conditions": [
            {
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.query?.action || $json.action }}",
              "rightValue": "approve"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "dc549605-d971-4ff1-b42f-7d43a9670d52",
      "name": "Wait - Human Review Window",
      "type": "n8n-nodes-base.wait",
      "position": [
        3040,
        336
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "3b5fc712-148d-450c-9491-10b48aa0dcb8",
      "name": "Send Reply to Customer",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1792,
        656
      ],
      "parameters": {
        "url": "https://api.sendgrid.com/v3/mail/send",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"personalizations\": [{\"to\": [{\"email\": \"{{ $json.sender }}\", \"name\": \"{{ $json.customerName }}\"}]}],\n  \"from\": {\"email\": \"support@yourcompany.com\", \"name\": \"Your Company Support\"},\n  \"subject\": \"Re: {{ $json.subject }}\",\n  \"content\": [{\"type\": \"text/plain\", \"value\": \"{{ $json.draftReply }}\"}]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_TOKEN_HERE"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "ad03c847-6ca9-475b-9908-5c83e63fb4d7",
      "name": "Send Slack Reply (if Slack channel)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2016,
        656
      ],
      "parameters": {
        "url": "https://slack.com/api/chat.postMessage",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"channel\": \"{{ $json.threadId || '#support-escalations' }}\",\n  \"text\": \"{{ $json.draftReply }}\"\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.2
    },
    {
      "id": "b37fab44-0047-414b-ba94-a70cf6d34058",
      "name": "Log to Support Audit Sheet",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2448,
        656
      ],
      "parameters": {
        "url": "https://sheets.googleapis.com/v4/spreadsheets/YOUR_SHEET_ID/values/Support_Log!A1:append?valueInputOption=USER_ENTERED",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"values\": [[\"{{ $json.processedDate }}\", \"{{ $json.messageId }}\", \"{{ $json.sender }}\", \"{{ $json.customerName }}\", \"{{ $json.channel }}\", \"{{ $json.subject }}\", \"{{ $json.category }}\", \"{{ $json.sensitivityTier }}\", \"{{ $json.urgencyLevel }}\", \"{{ $json.sentimentScore }}\", \"{{ $json.internalNote }}\", \"Replied\", \"{{ $json.processedAt }}\"]]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_TOKEN_HERE"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "c3df3076-0919-45ba-bf0c-8750ced7aeab",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        2128,
        352
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "ccedf4ee-90f8-47e6-b0cb-12ccb9260ce3",
      "name": "Wait For Result",
      "type": "n8n-nodes-base.wait",
      "position": [
        2224,
        656
      ],
      "parameters": {},
      "typeVersion": 1.1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "355b759a-c25f-4bd6-bc30-05ed88fb7ecf",
  "connections": {
    "Wait For Result": {
      "main": [
        [
          {
            "node": "Log to Support Audit Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI - Classify and Draft Reply",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Poll Support Inbox": {
      "main": [
        [
          {
            "node": "Normalize Message Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notify Human via Slack": {
      "main": [
        [
          {
            "node": "Wait - Human Review Window",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Reply to Customer": {
      "main": [
        [
          {
            "node": "Send Slack Reply (if Slack channel)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Message Context": {
      "main": [
        [
          {
            "node": "Python - Classify Sensitivity Tier",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter - Approved by Human?": {
      "main": [
        [
          {
            "node": "Send Reply to Customer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "JS - Parse AI Classification": {
      "main": [
        [
          {
            "node": "Route - Sensitive or Routine?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI - Classify and Draft Reply": {
      "main": [
        [
          {
            "node": "JS - Parse AI Classification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route - Sensitive or Routine?": {
      "main": [
        [
          {
            "node": "Notify Human via Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook - New Support Message": {
      "main": [
        [
          {
            "node": "Normalize Message Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook - Human Approval Response": {
      "main": [
        [
          {
            "node": "Filter - Approved by Human?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Python - Classify Sensitivity Tier": {
      "main": [
        [
          {
            "node": "AI - Classify and Draft Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Slack Reply (if Slack channel)": {
      "main": [
        [
          {
            "node": "Wait For Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}