This workflow corresponds to n8n.io template #12891 — we link there as the canonical source.
This workflow follows the Emailsend → Google Sheets 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 →
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "97c3d1fa-9857-44e2-ae12-170c3fce8b62",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
4688,
1216
],
"parameters": {
"width": 440,
"height": 808,
"content": "## Automate intelligent customer support responses with AI and Slack\n\n### How it works\n1. **Receive request** via webhook with customer question\n2. **Analyze sentiment** and detect urgency using JavaScript\n3. **Send urgent alerts** to Slack for critical cases\n4. **Search knowledge base** and fetch conversation history from PostgreSQL\n5. **Generate AI response** with context-aware prompts\n6. **Route intelligently**: Auto-respond via email OR escalate to Slack\n7. **Log everything** to Google Sheets and PostgreSQL for analytics\n\n### Setup steps\n1. **Slack webhooks**: Replace `YOUR_URGENT_WEBHOOK` and `YOUR_ESCALATION_WEBHOOK` with your webhook URLs\n2. **Google Sheets**: Replace `YOUR_SPREADSHEET_ID` with your spreadsheet ID and authenticate\n3. **Email**: Configure SMTP/Gmail credentials in the email node\n4. **PostgreSQL** (optional): Create `support_conversations` table or disable DB nodes\n5. **Production**: Replace mock AI nodes with OpenAI/Anthropic API nodes\n\n### Key features\n- Multi-language support (Japanese & English)\n- Sentiment analysis with urgency detection\n- Smart escalation routing\n- Real-time Slack notifications\n- Comprehensive analytics logging"
},
"typeVersion": 1
},
{
"id": "d0bd5bbb-8b47-4c11-a3ad-d80d181d7781",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
5318,
1368
],
"parameters": {
"color": 7,
"width": 820,
"height": 280,
"content": "## Intake & Analysis\n\nReceives customer requests, extracts data, and analyzes sentiment/urgency to determine handling priority."
},
"typeVersion": 1
},
{
"id": "00089b7e-e250-49e6-b687-e8caa381c7ce",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
6160,
1296
],
"parameters": {
"color": 7,
"width": 500,
"height": 344,
"content": "## Urgency Routing\n\nSends immediate Slack alerts for urgent cases (negative sentiment or urgent keywords), then merges paths to continue workflow."
},
"typeVersion": 1
},
{
"id": "649f76b7-3f55-4d4c-bd92-71ad0a2cbe3a",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
6786,
1368
],
"parameters": {
"color": 7,
"width": 1020,
"height": 280,
"content": "## Context Building & AI Response\n\nFetches conversation history, searches knowledge base, builds AI context, and generates intelligent responses with confidence scoring."
},
"typeVersion": 1
},
{
"id": "4fff7600-8288-423c-b3ff-c31bb7f75182",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
7904,
1280
],
"parameters": {
"color": 7,
"width": 740,
"height": 424,
"content": "## Smart Routing & Delivery\n\nAnalyzes response quality and decides: auto-respond via email OR escalate to support team via Slack based on confidence score and priority."
},
"typeVersion": 1
},
{
"id": "81fd500f-b6a3-4181-a423-27b2ffab280d",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
8656,
1360
],
"parameters": {
"color": 7,
"width": 760,
"height": 280,
"content": "## Logging & Response\n\nLogs all interactions to Google Sheets and PostgreSQL for analytics, then sends structured API response with metadata."
},
"typeVersion": 1
},
{
"id": "8ade0c3c-2940-4057-8e66-b4c68bb357c0",
"name": "Webhook Trigger",
"type": "n8n-nodes-base.webhook",
"notes": "POST /customer-support\n{\n \"email\": \"user@example.com\",\n \"name\": \"John Doe\",\n \"question\": \"How do I reset my password?\",\n \"conversationId\": \"optional\",\n \"language\": \"en\",\n \"priority\": \"normal\"\n}",
"position": [
5344,
1488
],
"parameters": {
"path": "customer-support",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2
},
{
"id": "519c4d0c-3d9b-49b1-aeff-80533846c928",
"name": "Extract & Enrich Data",
"type": "n8n-nodes-base.set",
"notes": "Extracts user data and generates unique IDs",
"position": [
5568,
1488
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "question_id",
"name": "questionId",
"type": "string",
"value": "={{ $now.toISO() }}-{{ $json.body.email.split('@')[0] }}-{{ Math.random().toString(36).substr(2, 9) }}"
},
{
"id": "user_question",
"name": "userQuestion",
"type": "string",
"value": "={{ $json.body.question }}"
},
{
"id": "user_email",
"name": "userEmail",
"type": "string",
"value": "={{ $json.body.email }}"
},
{
"id": "user_name",
"name": "userName",
"type": "string",
"value": "={{ $json.body.name || 'Customer' }}"
},
{
"id": "timestamp",
"name": "timestamp",
"type": "string",
"value": "={{ $now.toISO() }}"
},
{
"id": "conversation_id",
"name": "conversationId",
"type": "string",
"value": "={{ $json.body.conversationId || 'new-' + $now.toMillis() }}"
},
{
"id": "user_language",
"name": "userLanguage",
"type": "string",
"value": "={{ $json.body.language || 'en' }}"
},
{
"id": "priority",
"name": "priority",
"type": "string",
"value": "={{ $json.body.priority || 'normal' }}"
}
]
}
},
"typeVersion": 3.3
},
{
"id": "ff46a5c0-4f3a-4a82-8166-9c0d07bcd879",
"name": "Sentiment & Context Analysis",
"type": "n8n-nodes-base.code",
"notes": "Analyzes sentiment, urgency, and language\nRecommend using OpenAI/Claude API in production",
"position": [
5792,
1488
],
"parameters": {
"jsCode": "// Sentiment analysis and keyword extraction\nconst question = $input.item.json.userQuestion;\n\n// Simple sentiment analysis (recommend using OpenAI/Claude API in production)\nconst negativeKeywords = ['angry', 'terrible', 'worst', 'refund', 'complaint', 'issue', 'problem', 'frustrated', 'disappointed'];\nconst urgentKeywords = ['urgent', 'immediately', 'asap', 'emergency', 'critical', 'now'];\nconst positiveKeywords = ['thank', 'great', 'excellent', 'satisfied', 'happy', 'appreciate'];\n\nlet sentiment = 'neutral';\nlet urgency = 'normal';\nlet emotionScore = 0;\n\n// Sentiment scoring\nnegativeKeywords.forEach(keyword => {\n if (question.toLowerCase().includes(keyword)) {\n emotionScore -= 1;\n sentiment = 'negative';\n }\n});\n\npositiveKeywords.forEach(keyword => {\n if (question.toLowerCase().includes(keyword)) {\n emotionScore += 1;\n sentiment = 'positive';\n }\n});\n\nurgentKeywords.forEach(keyword => {\n if (question.toLowerCase().includes(keyword)) {\n urgency = 'high';\n }\n});\n\n// Very negative cases\nif (emotionScore <= -2) {\n sentiment = 'very_negative';\n urgency = 'high';\n}\n\n// Keyword extraction (simplified)\nconst keywords = question\n .replace(/[!?.,]/g, ' ')\n .split(' ')\n .filter(word => word.length > 2)\n .slice(0, 5);\n\n// Language detection (simplified - supports Japanese/English)\nconst hasKanji = /[\\u4e00-\\u9faf]/.test(question);\nconst hasHiragana = /[\\u3040-\\u309f]/.test(question);\nconst detectedLanguage = (hasKanji || hasHiragana) ? 'ja' : 'en';\n\nreturn {\n json: {\n questionId: $input.item.json.questionId,\n userQuestion: question,\n userEmail: $input.item.json.userEmail,\n userName: $input.item.json.userName,\n conversationId: $input.item.json.conversationId,\n timestamp: $input.item.json.timestamp,\n sentiment: sentiment,\n emotionScore: emotionScore,\n urgency: urgency,\n keywords: keywords.join(', '),\n detectedLanguage: detectedLanguage,\n requiresImmediateAttention: sentiment === 'very_negative' || urgency === 'high'\n }\n};"
},
"typeVersion": 2
},
{
"id": "d4f0a17e-a39a-4aa0-ba3a-b966e4daf621",
"name": "Urgency Check",
"type": "n8n-nodes-base.if",
"notes": "Routes urgent cases to Slack alert",
"position": [
6016,
1488
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "urgent_condition",
"operator": {
"type": "boolean",
"operation": "true"
},
"leftValue": "={{ $json.requiresImmediateAttention }}",
"rightValue": true
}
]
}
},
"typeVersion": 2
},
{
"id": "be626954-1b67-4949-853b-7895a97435d4",
"name": "Send Urgent Alert",
"type": "n8n-nodes-base.httpRequest",
"notes": "Sends urgent alert to Slack\nReplace YOUR_URGENT_WEBHOOK with actual webhook URL",
"position": [
6240,
1416
],
"parameters": {
"url": "https://hooks.slack.com/services/YOUR_URGENT_WEBHOOK",
"method": "POST",
"options": {},
"jsonBody": "={\n \"text\": \"\ud83d\udea8 *Urgent Support Request*\",\n \"blocks\": [\n {\n \"type\": \"header\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \"\u26a0\ufe0f Urgent Support Request Received\"\n }\n },\n {\n \"type\": \"section\",\n \"fields\": [\n {\n \"type\": \"mrkdwn\",\n \"text\": \"*Sentiment:*\\n{{ $json.sentiment === 'very_negative' ? '\ud83d\ude21 Very Negative' : '\u26a1 Urgent' }}\"\n },\n {\n \"type\": \"mrkdwn\",\n \"text\": \"*Priority:*\\n\ud83d\udd34 High\"\n },\n {\n \"type\": \"mrkdwn\",\n \"text\": \"*User:*\\n{{ $json.userName }} ({{ $json.userEmail }})\"\n },\n {\n \"type\": \"mrkdwn\",\n \"text\": \"*Time:*\\n{{ $json.timestamp }}\"\n }\n ]\n },\n {\n \"type\": \"section\",\n \"text\": {\n \"type\": \"mrkdwn\",\n \"text\": \"*Question:*\\n```{{ $json.userQuestion }}```\"\n }\n },\n {\n \"type\": \"section\",\n \"text\": {\n \"type\": \"mrkdwn\",\n \"text\": \"*Keywords:* {{ $json.keywords }}\"\n }\n },\n {\n \"type\": \"actions\",\n \"elements\": [\n {\n \"type\": \"button\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \"Respond Now\"\n },\n \"style\": \"danger\",\n \"url\": \"https://your-dashboard.com/urgent/{{ $json.questionId }}\"\n }\n ]\n }\n ]\n}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.2
},
{
"id": "748e4c16-6c42-4a24-8159-e9d0ca4c08c5",
"name": "Merge After Urgency",
"type": "n8n-nodes-base.merge",
"notes": "Merges urgent and normal paths",
"position": [
6464,
1488
],
"parameters": {},
"typeVersion": 3
},
{
"id": "b70fd79f-6445-4978-9482-4712533f0c1e",
"name": "Fetch Conversation History",
"type": "n8n-nodes-base.postgres",
"notes": "Fetches past conversation history from PostgreSQL\nDisable this node if table doesn't exist",
"position": [
6688,
1488
],
"parameters": {
"query": "=SELECT conversation_history, created_at \nFROM support_conversations \nWHERE conversation_id = '{{ $json.conversationId }}' \nOR user_email = '{{ $json.userEmail }}'\nORDER BY created_at DESC \nLIMIT 5",
"options": {},
"operation": "executeQuery"
},
"typeVersion": 2.4
},
{
"id": "b556035b-feeb-4cd4-8ff6-30d816abb61f",
"name": "Search Knowledge Base",
"type": "n8n-nodes-base.code",
"notes": "Searches knowledge base (currently mock)\nReplace with Pinecone/Supabase node in production",
"position": [
6912,
1488
],
"parameters": {
"jsCode": "// Knowledge base search simulation\n// In production: use Pinecone/Supabase/Qdrant for vector search\nconst question = $input.item.json.userQuestion;\nconst language = $input.item.json.detectedLanguage;\n\n// Mock search results (replace with actual vector search)\nconst mockSearchResults = language === 'ja' ? [\n {\n text: \"\u30d1\u30b9\u30ef\u30fc\u30c9\u30ea\u30bb\u30c3\u30c8\u306f\u3001\u30ed\u30b0\u30a4\u30f3\u753b\u9762\u306e\u300c\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5fd8\u308c\u305f\u65b9\u300d\u30ea\u30f3\u30af\u304b\u3089\u884c\u3048\u307e\u3059\u3002\u767b\u9332\u6e08\u307f\u306e\u30e1\u30fc\u30eb\u30a2\u30c9\u30ec\u30b9\u306b\u30ea\u30bb\u30c3\u30c8\u7528\u306e\u30ea\u30f3\u30af\u304c\u9001\u4fe1\u3055\u308c\u307e\u3059\u3002\",\n score: 0.85,\n metadata: { category: \"Account Management\", source: \"FAQ-001\" }\n },\n {\n text: \"\u30e1\u30fc\u30eb\u30a2\u30c9\u30ec\u30b9\u306e\u5909\u66f4\u306f\u3001\u30a2\u30ab\u30a6\u30f3\u30c8\u8a2d\u5b9a\u30da\u30fc\u30b8\u304b\u3089\u53ef\u80fd\u3067\u3059\u3002\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u306e\u305f\u3081\u3001\u73fe\u5728\u306e\u30d1\u30b9\u30ef\u30fc\u30c9\u306e\u5165\u529b\u304c\u5fc5\u8981\u306b\u306a\u308a\u307e\u3059\u3002\",\n score: 0.72,\n metadata: { category: \"Account Management\", source: \"FAQ-003\" }\n }\n] : [\n {\n text: \"To reset your password, click the 'Forgot Password' link on the login page. A reset link will be sent to your registered email address.\",\n score: 0.85,\n metadata: { category: \"Account Management\", source: \"FAQ-001\" }\n },\n {\n text: \"You can change your email address from the Account Settings page. For security reasons, you'll need to enter your current password.\",\n score: 0.72,\n metadata: { category: \"Account Management\", source: \"FAQ-003\" }\n }\n];\n\nreturn {\n json: {\n ...($input.item.json),\n searchResults: mockSearchResults\n }\n};"
},
"typeVersion": 2
},
{
"id": "b5132f09-072b-46e8-98a9-e28ff3eeebaa",
"name": "Build AI Context",
"type": "n8n-nodes-base.code",
"notes": "Builds comprehensive AI context with prompt engineering",
"position": [
7136,
1488
],
"parameters": {
"jsCode": "// Builds AI context from search results and conversation history\nconst searchResults = $input.item.json.searchResults || [];\nconst conversationHistory = $('Fetch Conversation History').all();\nconst currentData = $input.item.json;\n\n// Format conversation history\nlet historyContext = '';\nif (conversationHistory.length > 0) {\n historyContext = '\\n\u3010Past Conversation History\u3011\\n';\n conversationHistory.forEach((conv, idx) => {\n if (conv.json.conversation_history) {\n historyContext += `${idx + 1}. ${conv.json.conversation_history}\\n`;\n }\n });\n}\n\n// Format search results\nlet knowledgeContext = '';\nif (Array.isArray(searchResults) && searchResults.length > 0) {\n knowledgeContext = '\\n\u3010Knowledge Base Results\u3011\\n';\n searchResults.forEach((result, idx) => {\n knowledgeContext += `${idx + 1}. ${result.text}\\n`;\n knowledgeContext += ` Relevance: ${(result.score * 100).toFixed(1)}%\\n`;\n knowledgeContext += ` Source: ${result.metadata?.source || 'Unknown'}\\n\\n`;\n });\n} else {\n knowledgeContext = '\\n\u3010Knowledge Base Results\u3011\\nNo relevant information found.\\n';\n}\n\n// Tone guidance based on sentiment\nlet toneGuidance = '';\nswitch(currentData.sentiment) {\n case 'very_negative':\n toneGuidance = 'Use a very polite and apologetic tone. Acknowledge the customer\\'s frustration and provide quick solutions.';\n break;\n case 'negative':\n toneGuidance = 'Be empathetic and understanding. Show genuine concern and work towards resolution.';\n break;\n case 'positive':\n toneGuidance = 'Express appreciation and maintain a friendly tone. Keep the positive momentum.';\n break;\n default:\n toneGuidance = 'Use a professional and helpful tone. Provide clear and concise information.';\n}\n\n// Build AI prompt\nconst aiPrompt = `\nYou are a professional customer support representative. Answer the customer's question based on the following information.\n\n\u3010Customer Information\u3011\nName: ${currentData.userName}\nEmail: ${currentData.userEmail}\nLanguage: ${currentData.detectedLanguage === 'ja' ? 'Japanese' : 'English'}\nSentiment: ${currentData.sentiment}\nUrgency: ${currentData.urgency}\n\n\u3010Question\u3011\n${currentData.userQuestion}\n\n${knowledgeContext}\n${historyContext}\n\n\u3010Response Guidelines\u3011\n${toneGuidance}\n\n\u3010Instructions\u3011\n1. Use knowledge base information to provide accurate answers\n2. Consider past conversation history if available\n3. Include the following metadata at the end:\n CONFIDENCE_SCORE: [0-100]\n REQUIRES_FOLLOWUP: [true/false]\n SUGGESTED_CATEGORY: [category name]\n`;\n\nreturn {\n json: {\n ...currentData,\n knowledgeContext: knowledgeContext,\n historyContext: historyContext,\n toneGuidance: toneGuidance,\n aiPrompt: aiPrompt,\n hasHistory: conversationHistory.length > 0,\n searchResultCount: searchResults.length\n }\n};"
},
"typeVersion": 2
},
{
"id": "c8f42c73-bd2a-4f19-99bf-bb9d19961549",
"name": "AI Response Generator",
"type": "n8n-nodes-base.code",
"notes": "Generates AI response (currently mock)\nReplace with OpenAI/Anthropic node in production",
"position": [
7360,
1488
],
"parameters": {
"jsCode": "// AI response generation simulation\n// In production: replace with OpenAI/Claude API\nconst context = $input.item.json;\nconst language = context.detectedLanguage;\n\n// Generate mock response\nconst greeting = language === 'ja' ? \n `${context.userName}\u69d8\\n\\n\u304a\u554f\u3044\u5408\u308f\u305b\u3044\u305f\u3060\u304d\u3042\u308a\u304c\u3068\u3046\u3054\u3056\u3044\u307e\u3059\u3002` :\n `Dear ${context.userName},\\n\\nThank you for your inquiry.`;\n\nconst mainContent = language === 'ja' ?\n `\\n\\n\u3054\u8cea\u554f\u306e\u4ef6\u306b\u3064\u3044\u3066\u3001\u4ee5\u4e0b\u306e\u901a\u308a\u3054\u6848\u5185\u3055\u305b\u3066\u3044\u305f\u3060\u304d\u307e\u3059\u3002\\n\\n${context.knowledgeContext}\\n\\n\u4e0a\u8a18\u306e\u60c5\u5831\u304c\u304a\u5f79\u306b\u7acb\u3066\u3070\u5e78\u3044\u3067\u3059\u3002` :\n `\\n\\nRegarding your question, here is the information you need:\\n\\n${context.knowledgeContext}\\n\\nI hope this information is helpful.`;\n\nconst closing = language === 'ja' ?\n `\\n\\n\u3054\u4e0d\u660e\u306a\u70b9\u304c\u3054\u3056\u3044\u307e\u3057\u305f\u3089\u3001\u304a\u6c17\u8efd\u306b\u304a\u554f\u3044\u5408\u308f\u305b\u304f\u3060\u3055\u3044\u3002\\n\\n\u4eca\u5f8c\u3068\u3082\u3088\u308d\u3057\u304f\u304a\u9858\u3044\u3044\u305f\u3057\u307e\u3059\u3002` :\n `\\n\\nIf you have any further questions, please don't hesitate to ask.\\n\\nBest regards,\\nSupport Team`;\n\n// Calculate confidence score based on search quality\nconst avgScore = context.searchResults && context.searchResults.length > 0 ?\n context.searchResults.reduce((sum, r) => sum + r.score, 0) / context.searchResults.length :\n 0.3;\n\nconst confidenceScore = Math.round(avgScore * 100);\n\n// Determine if followup is needed\nconst requiresFollowup = \n confidenceScore < 70 || \n context.sentiment === 'very_negative' ||\n context.searchResultCount === 0;\n\n// Categorize question\nconst question = context.userQuestion.toLowerCase();\nlet category = 'General';\nif (question.includes('password') || question.includes('\u30d1\u30b9\u30ef\u30fc\u30c9')) category = 'Account Management';\nelse if (question.includes('payment') || question.includes('\u652f\u6255') || question.includes('billing')) category = 'Billing';\nelse if (question.includes('bug') || question.includes('error') || question.includes('\u30d0\u30b0') || question.includes('\u30a8\u30e9\u30fc')) category = 'Technical Support';\nelse if (question.includes('refund') || question.includes('\u8fd4\u91d1') || question.includes('cancel')) category = 'Refunds';\n\nconst mockAIResponse = `${greeting}${mainContent}${closing}\n\nCONFIDENCE_SCORE: ${confidenceScore}\nREQUIRES_FOLLOWUP: ${requiresFollowup}\nSUGGESTED_CATEGORY: ${category}`;\n\nreturn {\n json: {\n ...context,\n response: mockAIResponse\n }\n};"
},
"typeVersion": 2
},
{
"id": "9a826727-8466-4099-b8dd-dd7e4130bd84",
"name": "Process & Analyze Response",
"type": "n8n-nodes-base.code",
"notes": "Analyzes AI response and determines escalation need",
"position": [
7584,
1488
],
"parameters": {
"jsCode": "// Analyzes AI response and determines routing\nconst aiResponse = $input.item.json.response;\nconst contextData = $input.item.json;\n\n// Extract metadata\nconst confidenceMatch = aiResponse.match(/CONFIDENCE_SCORE:\\s*(\\d+)/);\nconst followupMatch = aiResponse.match(/REQUIRES_FOLLOWUP:\\s*(true|false)/);\nconst categoryMatch = aiResponse.match(/SUGGESTED_CATEGORY:\\s*(.+)/);\n\nconst confidenceScore = confidenceMatch ? parseInt(confidenceMatch[1]) : 0;\nconst requiresFollowup = followupMatch ? followupMatch[1] === 'true' : false;\nconst suggestedCategory = categoryMatch ? categoryMatch[1].trim() : 'Uncategorized';\n\n// Clean response (remove metadata)\nconst cleanResponse = aiResponse\n .replace(/CONFIDENCE_SCORE:\\s*\\d+/g, '')\n .replace(/REQUIRES_FOLLOWUP:\\s*(true|false)/g, '')\n .replace(/SUGGESTED_CATEGORY:\\s*.+/g, '')\n .trim();\n\n// Escalation logic\nconst needsEscalation = \n confidenceScore < 70 || // Low confidence\n requiresFollowup || // Needs followup\n contextData.sentiment === 'very_negative' || // Very negative\n contextData.urgency === 'high' || // High urgency\n cleanResponse.includes('escalate') || // Explicit escalation\n contextData.searchResultCount === 0; // No knowledge base results\n\n// Priority scoring\nlet priorityScore = 0;\nif (contextData.sentiment === 'very_negative') priorityScore += 30;\nif (contextData.urgency === 'high') priorityScore += 25;\nif (confidenceScore < 50) priorityScore += 20;\nif (requiresFollowup) priorityScore += 15;\nif (contextData.emotionScore < -1) priorityScore += 10;\nif (contextData.searchResultCount === 0) priorityScore += 10;\n\nconst priority = priorityScore >= 50 ? 'critical' : \n priorityScore >= 30 ? 'high' : \n priorityScore >= 15 ? 'medium' : 'low';\n\n// Estimated response time\nconst estimatedResponseTime = \n priority === 'critical' ? 'Within 1 hour' :\n priority === 'high' ? 'Within 4 hours' :\n priority === 'medium' ? 'Within 24 hours' : 'Within 48 hours';\n\nreturn {\n json: {\n questionId: contextData.questionId,\n conversationId: contextData.conversationId,\n userQuestion: contextData.userQuestion,\n userEmail: contextData.userEmail,\n userName: contextData.userName,\n aiResponse: cleanResponse,\n confidenceScore: confidenceScore,\n requiresFollowup: requiresFollowup,\n suggestedCategory: suggestedCategory,\n needsEscalation: needsEscalation,\n priority: priority,\n priorityScore: priorityScore,\n sentiment: contextData.sentiment,\n emotionScore: contextData.emotionScore,\n urgency: contextData.urgency,\n detectedLanguage: contextData.detectedLanguage,\n keywords: contextData.keywords,\n status: needsEscalation ? 'escalated' : 'auto_resolved',\n timestamp: contextData.timestamp,\n hasHistory: contextData.hasHistory,\n searchResultCount: contextData.searchResultCount,\n estimatedResponseTime: estimatedResponseTime\n }\n};"
},
"typeVersion": 2
},
{
"id": "0afc78ac-509f-4a9b-a183-bf97fdebab25",
"name": "Routing Decision",
"type": "n8n-nodes-base.if",
"notes": "Routes to escalation or auto-response",
"position": [
7808,
1488
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "escalation_condition",
"operator": {
"type": "boolean",
"operation": "true"
},
"leftValue": "={{ $json.needsEscalation }}",
"rightValue": true
}
]
}
},
"typeVersion": 2
},
{
"id": "6c741000-e1d9-46bb-8148-9e688517f69a",
"name": "Slack Escalation",
"type": "n8n-nodes-base.httpRequest",
"notes": "Sends detailed escalation to Slack\nReplace YOUR_ESCALATION_WEBHOOK with actual URL",
"position": [
8032,
1392
],
"parameters": {
"url": "https://hooks.slack.com/services/YOUR_ESCALATION_WEBHOOK",
"method": "POST",
"options": {},
"jsonBody": "={\n \"text\": \"\ud83d\udccb *Support Question Escalation*\",\n \"blocks\": [\n {\n \"type\": \"header\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \"{{ $json.priority === 'critical' ? '\ud83d\udd34' : $json.priority === 'high' ? '\ud83d\udfe0' : '\ud83d\udfe1' }} Support Question Escalated\"\n }\n },\n {\n \"type\": \"section\",\n \"fields\": [\n {\n \"type\": \"mrkdwn\",\n \"text\": \"*Question ID:*\\n`{{ $json.questionId }}`\"\n },\n {\n \"type\": \"mrkdwn\",\n \"text\": \"*Priority:*\\n{{ $json.priority === 'critical' ? '\ud83d\udd34 Critical' : $json.priority === 'high' ? '\ud83d\udfe0 High' : $json.priority === 'medium' ? '\ud83d\udfe1 Medium' : '\ud83d\udfe2 Low' }} ({{ $json.priorityScore }}pts)\"\n },\n {\n \"type\": \"mrkdwn\",\n \"text\": \"*User:*\\n{{ $json.userName }}\\n{{ $json.userEmail }}\"\n },\n {\n \"type\": \"mrkdwn\",\n \"text\": \"*Category:*\\n{{ $json.suggestedCategory }}\"\n },\n {\n \"type\": \"mrkdwn\",\n \"text\": \"*Sentiment:*\\n{{ $json.sentiment === 'very_negative' ? '\ud83d\ude21 Very Negative' : $json.sentiment === 'negative' ? '\ud83d\ude1e Negative' : $json.sentiment === 'positive' ? '\ud83d\ude0a Positive' : '\ud83d\ude10 Neutral' }}\"\n },\n {\n \"type\": \"mrkdwn\",\n \"text\": \"*Confidence:*\\n{{ $json.confidenceScore }}%\"\n }\n ]\n },\n {\n \"type\": \"section\",\n \"text\": {\n \"type\": \"mrkdwn\",\n \"text\": \"*Question:*\\n```{{ $json.userQuestion }}```\"\n }\n },\n {\n \"type\": \"section\",\n \"text\": {\n \"type\": \"mrkdwn\",\n \"text\": \"*AI Suggested Response:*\\n{{ $json.aiResponse.substring(0, 500) }}{{ $json.aiResponse.length > 500 ? '...' : '' }}\"\n }\n },\n {\n \"type\": \"context\",\n \"elements\": [\n {\n \"type\": \"mrkdwn\",\n \"text\": \"{{ $json.hasHistory ? '\ud83d\udcda Has history' : '\ud83c\udd95 New user' }} | \u23f0 {{ $json.timestamp }} | \ud83d\udd50 ETA: {{ $json.estimatedResponseTime }}\"\n }\n ]\n },\n {\n \"type\": \"actions\",\n \"elements\": [\n {\n \"type\": \"button\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \"Respond\"\n },\n \"style\": \"primary\",\n \"url\": \"https://your-dashboard.com/tickets/{{ $json.questionId }}\"\n },\n {\n \"type\": \"button\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \"View History\"\n },\n \"url\": \"https://your-dashboard.com/conversations/{{ $json.conversationId }}\"\n }\n ]\n }\n ]\n}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.2
},
{
"id": "58bb256c-d358-4c2e-b3f1-a1f2c8cfd878",
"name": "Send Auto Response",
"type": "n8n-nodes-base.emailSend",
"notes": "Sends automated email response\nConfigure SMTP/Gmail credentials",
"position": [
8032,
1584
],
"parameters": {
"options": {},
"subject": "={{ $json.detectedLanguage === 'ja' ? '\u304a\u554f\u3044\u5408\u308f\u305b\u3042\u308a\u304c\u3068\u3046\u3054\u3056\u3044\u307e\u3059 - ' + $json.suggestedCategory : 'Thank you for your inquiry - ' + $json.suggestedCategory }}",
"toEmail": "={{ $json.userEmail }}",
"fromEmail": "support@example.com"
},
"typeVersion": 2.1
},
{
"id": "07f7566e-2222-4202-b77e-eb37a9aa42e8",
"name": "Merge After Routing",
"type": "n8n-nodes-base.merge",
"notes": "Merges escalation and auto-response paths",
"position": [
8256,
1488
],
"parameters": {},
"typeVersion": 3
},
{
"id": "2cf2d1e3-b1bc-4985-a85a-52470963dd0c",
"name": "Log to Analytics Dashboard",
"type": "n8n-nodes-base.googleSheets",
"notes": "Logs all interactions to Google Sheets\nReplace YOUR_SPREADSHEET_ID and authenticate",
"position": [
8480,
1488
],
"parameters": {
"columns": {
"value": {
"status": "={{ $json.status }}",
"urgency": "={{ $json.urgency }}",
"category": "={{ $json.suggestedCategory }}",
"keywords": "={{ $json.keywords }}",
"language": "={{ $json.detectedLanguage }}",
"priority": "={{ $json.priority }}",
"question": "={{ $json.userQuestion }}",
"userName": "={{ $json.userName }}",
"sentiment": "={{ $json.sentiment }}",
"timestamp": "={{ $json.timestamp }}",
"userEmail": "={{ $json.userEmail }}",
"aiResponse": "={{ $json.aiResponse }}",
"questionId": "={{ $json.questionId }}",
"confidenceScore": "={{ $json.confidenceScore }}"
},
"schema": [
{
"id": "questionId",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Question ID",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"questionId"
]
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "YOUR_SPREADSHEET_ID"
}
},
"typeVersion": 4.4
},
{
"id": "11f74c26-387e-4510-8691-047eeb7d87cc",
"name": "Save to Database",
"type": "n8n-nodes-base.postgres",
"notes": "Saves conversation to PostgreSQL\nDisable if table doesn't exist",
"position": [
8704,
1488
],
"parameters": {
"query": "=INSERT INTO support_conversations \n(conversation_id, question_id, user_email, user_name, question, ai_response, confidence_score, sentiment, priority, status, created_at)\nVALUES \n('{{ $json.conversationId }}', '{{ $json.questionId }}', '{{ $json.userEmail }}', '{{ $json.userName }}', '{{ $json.userQuestion }}', '{{ $json.aiResponse }}', {{ $json.confidenceScore }}, '{{ $json.sentiment }}', '{{ $json.priority }}', '{{ $json.status }}', CURRENT_TIMESTAMP)\nON CONFLICT (question_id) DO UPDATE SET\n ai_response = EXCLUDED.ai_response,\n confidence_score = EXCLUDED.confidence_score,\n status = EXCLUDED.status,\n updated_at = CURRENT_TIMESTAMP",
"options": {},
"operation": "executeQuery"
},
"typeVersion": 2.4
},
{
"id": "239c977d-4942-4b8e-88ab-d4beabc04c75",
"name": "Send API Response",
"type": "n8n-nodes-base.respondToWebhook",
"notes": "Returns structured API response with metadata",
"position": [
8928,
1488
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "={{ {\n \"success\": true,\n \"questionId\": $json.questionId,\n \"conversationId\": $json.conversationId,\n \"status\": $json.status,\n \"priority\": $json.priority,\n \"message\": $json.status === 'auto_resolved' ? \n ($json.detectedLanguage === 'ja' ? '\u3054\u8cea\u554f\u3042\u308a\u304c\u3068\u3046\u3054\u3056\u3044\u307e\u3059\u3002\u81ea\u52d5\u5fdc\u7b54\u30e1\u30fc\u30eb\u3092\u304a\u9001\u308a\u3057\u307e\u3057\u305f\u3002' : 'Thank you for your question. An automated response has been sent to your email.') :\n ($json.detectedLanguage === 'ja' ? '\u3054\u8cea\u554f\u3042\u308a\u304c\u3068\u3046\u3054\u3056\u3044\u307e\u3059\u3002\u62c5\u5f53\u8005\u304c\u78ba\u8a8d\u5f8c\u3001\u3054\u9023\u7d61\u3055\u305b\u3066\u3044\u305f\u3060\u304d\u307e\u3059\u3002' : 'Thank you for your question. Our team will review and get back to you soon.'),\n \"response\": $json.aiResponse,\n \"metadata\": {\n \"confidenceScore\": $json.confidenceScore,\n \"category\": $json.suggestedCategory,\n \"sentiment\": $json.sentiment,\n \"language\": $json.detectedLanguage,\n \"requiresFollowup\": $json.requiresFollowup,\n \"estimatedResponseTime\": $json.estimatedResponseTime\n }\n} }}"
},
"typeVersion": 1.1
}
],
"connections": {
"Urgency Check": {
"main": [
[
{
"node": "Send Urgent Alert",
"type": "main",
"index": 0
}
],
[
{
"node": "Merge After Urgency",
"type": "main",
"index": 1
}
]
]
},
"Webhook Trigger": {
"main": [
[
{
"node": "Extract & Enrich Data",
"type": "main",
"index": 0
}
]
]
},
"Build AI Context": {
"main": [
[
{
"node": "AI Response Generator",
"type": "main",
"index": 0
}
]
]
},
"Routing Decision": {
"main": [
[
{
"node": "Slack Escalation",
"type": "main",
"index": 0
}
],
[
{
"node": "Send Auto Response",
"type": "main",
"index": 0
}
]
]
},
"Save to Database": {
"main": [
[
{
"node": "Send API Response",
"type": "main",
"index": 0
}
]
]
},
"Slack Escalation": {
"main": [
[
{
"node": "Merge After Routing",
"type": "main",
"index": 0
}
]
]
},
"Send Urgent Alert": {
"main": [
[
{
"node": "Merge After Urgency",
"type": "main",
"index": 0
}
]
]
},
"Send Auto Response": {
"main": [
[
{
"node": "Merge After Routing",
"type": "main",
"index": 1
}
]
]
},
"Merge After Routing": {
"main": [
[
{
"node": "Log to Analytics Dashboard",
"type": "main",
"index": 0
}
]
]
},
"Merge After Urgency": {
"main": [
[
{
"node": "Fetch Conversation History",
"type": "main",
"index": 0
}
]
]
},
"AI Response Generator": {
"main": [
[
{
"node": "Process & Analyze Response",
"type": "main",
"index": 0
}
]
]
},
"Extract & Enrich Data": {
"main": [
[
{
"node": "Sentiment & Context Analysis",
"type": "main",
"index": 0
}
]
]
},
"Search Knowledge Base": {
"main": [
[
{
"node": "Build AI Context",
"type": "main",
"index": 0
}
]
]
},
"Fetch Conversation History": {
"main": [
[
{
"node": "Search Knowledge Base",
"type": "main",
"index": 0
}
]
]
},
"Log to Analytics Dashboard": {
"main": [
[
{
"node": "Save to Database",
"type": "main",
"index": 0
}
]
]
},
"Process & Analyze Response": {
"main": [
[
{
"node": "Routing Decision",
"type": "main",
"index": 0
}
]
]
},
"Sentiment & Context Analysis": {
"main": [
[
{
"node": "Urgency Check",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Receive request via webhook with customer question Analyze sentiment and detect urgency using JavaScript Send urgent alerts to Slack for critical cases Search knowledge base and fetch conversation history from PostgreSQL Generate AI response with context-aware prompts Route…
Source: https://n8n.io/workflows/12891/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This n8n workflow automates the transformation of raw text ideas into structured visual diagrams and content assets using NapkinAI.
PURPOSE: Automatically send professional appointment reminders via email and SMS to reduce no-shows and improve patient experience.
This comprehensive n8n workflow automates the entire travel business call management process, from initial customer inquiries to trip bookings and marketing outreach. The system handles incoming calls
AUTO MESSAGE NEW CONNECTION. Uses stickyNote, googleSheets, postgres, httpRequest. Webhook trigger; 13 nodes.
Scheduled processes retrieve customer feedback from multiple channels. The system performs sentiment analysis to classify tone, then uses OpenAI models to extract themes, topics, and urgency indicator