This workflow corresponds to n8n.io template #5135 — we link there as the canonical source.
This workflow follows the Emailreadimap → Emailsend recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "OUvCtjVgtro3MT2J",
"name": "My workflow 3",
"tags": [],
"nodes": [
{
"id": "2b5c3f5d-bf7d-41be-bae2-c5785add0f07",
"name": "Email Trigger",
"type": "n8n-nodes-base.emailReadImap",
"position": [
220,
240
],
"parameters": {
"options": {
"customEmailConfig": "=[\"UNSEEN\"]"
},
"postProcessAction": "nothing"
},
"typeVersion": 2
},
{
"id": "fcccc1d7-e4d6-4f40-87bf-3eebd058a62d",
"name": "Web Form Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
220,
440
],
"parameters": {
"path": "support-ticket",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 1
},
{
"id": "526cdbed-260d-46e5-b47d-497a4ea1c8d8",
"name": "Normalize Messages",
"type": "n8n-nodes-base.function",
"position": [
600,
340
],
"parameters": {
"functionCode": "// Normalize incoming messages from different channels\nconst items = [];\n\nfor (const item of $input.all()) {\n const source = item.json;\n \n let normalizedTicket = {\n id: 'TKT-' + Date.now() + '-' + Math.random().toString(36).substr(2, 5),\n timestamp: new Date().toISOString(),\n channel: '',\n customer: {\n name: '',\n email: '',\n id: ''\n },\n message: {\n subject: '',\n body: '',\n attachments: []\n },\n metadata: {},\n sourceData: source\n };\n\n // Check if this is from Email Trigger\n if (source.from && source.subject) {\n normalizedTicket.channel = 'email';\n normalizedTicket.customer.email = source.from.value?.[0]?.address || source.from;\n normalizedTicket.customer.name = source.from.value?.[0]?.name || 'Email Customer';\n normalizedTicket.message.subject = source.subject;\n normalizedTicket.message.body = source.text || source.html || '';\n if (source.attachments) {\n normalizedTicket.message.attachments = source.attachments.map(att => ({\n filename: att.filename,\n size: att.size\n }));\n }\n }\n // Check if this is from Web Form\n else if (source.body && (source.body.email || source.body.name)) {\n normalizedTicket.channel = 'webform';\n normalizedTicket.customer.name = source.body.name || 'Web Form User';\n normalizedTicket.customer.email = source.body.email || '';\n normalizedTicket.message.subject = source.body.subject || 'Web Form Submission';\n normalizedTicket.message.body = source.body.message || source.body.description || '';\n }\n // Generic webhook data\n else {\n normalizedTicket.channel = 'webhook';\n normalizedTicket.customer.name = source.name || source.customerName || 'Unknown';\n normalizedTicket.customer.email = source.email || source.customerEmail || '';\n normalizedTicket.message.subject = source.subject || 'Support Request';\n normalizedTicket.message.body = source.message || source.body || JSON.stringify(source);\n }\n\n items.push({\n json: normalizedTicket\n });\n}\n\nreturn items;"
},
"typeVersion": 1
},
{
"id": "0aef988c-3364-4a9a-bbef-e2156febe585",
"name": "Categorize & Prioritize",
"type": "n8n-nodes-base.function",
"position": [
800,
340
],
"parameters": {
"functionCode": "// Categorize and prioritize tickets based on content\nconst items = [];\n\nfor (const item of $input.all()) {\n const ticket = item.json;\n const message = (ticket.message.body + ' ' + ticket.message.subject).toLowerCase();\n \n let category = 'general';\n let priority = 'medium';\n let tags = [];\n let sentiment = 'neutral';\n \n // Category detection rules\n const categoryRules = {\n 'billing': ['invoice', 'payment', 'charge', 'refund', 'subscription', 'cancel', 'upgrade', 'downgrade', 'pricing', 'cost', 'bill'],\n 'technical': ['bug', 'error', 'broken', 'not working', 'crash', 'slow', 'issue', 'problem', 'fix', '500', '404', 'loading'],\n 'account': ['password', 'login', 'access', 'account', 'email', 'username', '2fa', 'locked', 'forgot', 'reset', 'sign in'],\n 'feature': ['feature', 'request', 'suggestion', 'improvement', 'add', 'would be nice', 'enhancement', 'idea'],\n 'complaint': ['terrible', 'awful', 'worst', 'hate', 'angry', 'frustrated', 'disappointed', 'unacceptable', 'ridiculous']\n };\n \n // Check categories\n for (const [cat, keywords] of Object.entries(categoryRules)) {\n if (keywords.some(keyword => message.includes(keyword))) {\n category = cat;\n tags.push(cat);\n break;\n }\n }\n \n // Sentiment analysis (simplified)\n const negativeWords = ['not working', 'broken', 'error', 'angry', 'frustrated', 'terrible', 'awful', 'worst', 'hate', 'disappointed', 'unacceptable'];\n const positiveWords = ['thank', 'great', 'excellent', 'love', 'amazing', 'fantastic', 'wonderful', 'perfect'];\n const urgentWords = ['urgent', 'asap', 'emergency', 'critical', 'immediately', 'now', 'quickly'];\n \n const negativeCount = negativeWords.filter(word => message.includes(word)).length;\n const positiveCount = positiveWords.filter(word => message.includes(word)).length;\n \n if (negativeCount > positiveCount + 1) {\n sentiment = 'negative';\n tags.push('negative_sentiment');\n } else if (positiveCount > negativeCount + 1) {\n sentiment = 'positive';\n tags.push('positive_sentiment');\n }\n \n // Priority rules\n if (urgentWords.some(word => message.includes(word))) {\n priority = 'urgent';\n tags.push('urgent');\n } else if (category === 'complaint' || sentiment === 'negative') {\n priority = 'high';\n } else if (category === 'billing' || category === 'account') {\n priority = 'high';\n }\n \n // Check for VIP customers (you can add your VIP domains here)\n const vipDomains = ['enterprise.com', 'vip.com', 'premium.com'];\n if (ticket.customer.email && vipDomains.some(domain => ticket.customer.email.includes(domain))) {\n priority = priority === 'medium' ? 'high' : priority;\n tags.push('vip_customer');\n }\n \n items.push({\n json: {\n ...ticket,\n category,\n priority,\n tags,\n sentiment,\n requiresHumanReview: priority === 'urgent' || sentiment === 'negative',\n autoResponseEligible: priority !== 'urgent' && sentiment !== 'negative' && category !== 'complaint'\n }\n });\n}\n\nreturn items;"
},
"typeVersion": 1
},
{
"id": "d0186a2f-34b0-4c68-921f-3050f270dd1a",
"name": "Check Auto-Response",
"type": "n8n-nodes-base.if",
"position": [
1000,
340
],
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{$json[\"autoResponseEligible\"]}}",
"value2": true
}
]
}
},
"typeVersion": 1
},
{
"id": "fa5ecd58-23e9-4194-9bb5-954e6b1b7e05",
"name": "Generate Auto-Response",
"type": "n8n-nodes-base.function",
"position": [
1200,
240
],
"parameters": {
"functionCode": "// Generate auto-response based on category\nconst items = [];\n\nfor (const item of $input.all()) {\n const ticket = item.json;\n \n const templates = {\n 'billing': {\n subject: `Re: Your billing inquiry - ${ticket.id}`,\n body: `Hello ${ticket.customer.name || 'Valued Customer'},\n\nThank you for contacting us about your billing concern. We understand how important this is.\n\nYour ticket (${ticket.id}) has been received and forwarded to our billing department. You can expect a response within 2-4 business hours.\n\nIn the meantime, you might find these resources helpful:\n- Billing FAQ: https://support.yourcompany.com/billing\n- View invoices: https://app.yourcompany.com/billing\n\nBest regards,\nCustomer Support Team`\n },\n 'technical': {\n subject: `Re: Technical support request - ${ticket.id}`,\n body: `Hello ${ticket.customer.name || 'Valued Customer'},\n\nWe've received your technical support request (${ticket.id}).\n\nOur technical team is reviewing your issue. To help us resolve this faster, please ensure you've provided:\n- Description of the issue\n- Steps to reproduce\n- Any error messages\n\nExpected response time: Within 4-6 hours\n\nBest regards,\nTechnical Support Team`\n },\n 'account': {\n subject: `Re: Account assistance - ${ticket.id}`,\n body: `Hello ${ticket.customer.name || 'Valued Customer'},\n\nWe've received your account-related request (${ticket.id}).\n\nFor security reasons, our account specialists will handle this personally. You'll hear from us within 1-2 business hours.\n\nBest regards,\nAccount Security Team`\n },\n 'feature': {\n subject: `Re: Feature request received - ${ticket.id}`,\n body: `Hello ${ticket.customer.name || 'Valued Customer'},\n\nThank you for your feature suggestion! We love hearing from our users.\n\nYour request (${ticket.id}) has been logged and will be reviewed by our product team. We consider all feedback when planning future updates.\n\nBest regards,\nProduct Team`\n },\n 'general': {\n subject: `Re: ${ticket.message.subject} - ${ticket.id}`,\n body: `Hello ${ticket.customer.name || 'Valued Customer'},\n\nThank you for contacting support. We've received your request (${ticket.id}) and our team is reviewing it.\n\nWe'll get back to you within 24 business hours.\n\nBest regards,\nCustomer Support Team`\n }\n };\n \n const template = templates[ticket.category] || templates['general'];\n \n items.push({\n json: {\n ...ticket,\n autoResponse: {\n to: ticket.customer.email,\n subject: template.subject,\n body: template.body,\n shouldSend: !!ticket.customer.email && ticket.autoResponseEligible\n }\n }\n });\n}\n\nreturn items;"
},
"typeVersion": 1
},
{
"id": "67eeb519-d0d6-44f4-aef6-3897159621fb",
"name": "Send Auto-Response",
"type": "n8n-nodes-base.emailSend",
"position": [
1400,
240
],
"parameters": {
"text": "={{$json[\"autoResponse\"][\"body\"]}}",
"options": {},
"subject": "={{$json[\"autoResponse\"][\"subject\"]}}",
"toEmail": "={{$json[\"autoResponse\"][\"to\"]}}",
"fromEmail": "user@example.com"
},
"typeVersion": 2,
"continueOnFail": true
},
{
"id": "352f497d-9b66-40c5-8a0a-cfac715db269",
"name": "Notify Slack",
"type": "n8n-nodes-base.slack",
"onError": "continueErrorOutput",
"position": [
1200,
440
],
"parameters": {
"text": "=\ud83c\udfab New {{$json[\"priority\"]}} Priority Ticket",
"otherOptions": {},
"authentication": "oAuth2"
},
"credentials": {
"slackOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "b6eb4722-5a89-41b0-a865-f2a2ea8851c7",
"name": "Store in CRM",
"type": "n8n-nodes-base.function",
"position": [
1400,
440
],
"parameters": {
"functionCode": "// Store ticket in database or CRM\n// This is a placeholder - replace with your actual CRM node (Zendesk, HubSpot, etc.)\n\nconst items = [];\n\nfor (const item of $input.all()) {\n const ticket = item.json;\n \n // Example structure for CRM integration\n const crmTicket = {\n external_id: ticket.id,\n subject: ticket.message.subject || `Support Request from ${ticket.channel}`,\n description: ticket.message.body,\n priority: ticket.priority,\n status: 'new',\n channel: ticket.channel,\n tags: ticket.tags,\n custom_fields: {\n sentiment: ticket.sentiment,\n category: ticket.category,\n auto_response_sent: ticket.autoResponseEligible\n },\n requester: {\n name: ticket.customer.name,\n email: ticket.customer.email\n },\n created_at: ticket.timestamp\n };\n \n // In production, you would send this to your CRM API\n console.log('Would create ticket in CRM:', crmTicket);\n \n items.push({\n json: {\n ...ticket,\n crmTicket,\n stored: true\n }\n });\n}\n\nreturn items;"
},
"typeVersion": 1
},
{
"id": "92de134e-ac68-4f99-87a4-d7fcbada6a1f",
"name": "Log Success",
"type": "n8n-nodes-base.function",
"position": [
1600,
340
],
"parameters": {
"functionCode": "// Log success metrics\nconst items = [];\n\nfor (const item of $input.all()) {\n const ticket = item.json;\n \n const metrics = {\n timestamp: new Date().toISOString(),\n ticketId: ticket.id,\n channel: ticket.channel,\n category: ticket.category,\n priority: ticket.priority,\n sentiment: ticket.sentiment,\n autoResponseSent: ticket.autoResponseEligible,\n processingTime: Date.now() - new Date(ticket.timestamp).getTime(),\n success: true\n };\n \n // Log to console (in production, send to analytics platform)\n console.log('Ticket processed successfully:', metrics);\n \n items.push({ json: metrics });\n}\n\nreturn items;"
},
"typeVersion": 1
},
{
"id": "aec8fd1f-6a51-4f8b-b001-541646619b8a",
"name": "Error Handler",
"type": "n8n-nodes-base.function",
"position": [
1400,
640
],
"parameters": {
"functionCode": "// Handle errors and log them\nconst error = $json[\"error\"];\nconst originalData = $json[\"originalData\"] || {};\n\nconst errorLog = {\n timestamp: new Date().toISOString(),\n workflow: 'customer-support-automation',\n error: {\n message: error?.message || 'Unknown error',\n code: error?.code,\n node: error?.node\n },\n ticketId: originalData.id || 'unknown',\n channel: originalData.channel || 'unknown',\n recovery: 'manual_intervention_required'\n};\n\n// In production, send to error tracking service (Sentry, etc.)\nconsole.error('Workflow error:', errorLog);\n\n// Could also send critical errors to Slack\nreturn [{ json: errorLog }];"
},
"typeVersion": 1
},
{
"id": "043ceb80-fe46-4f59-8a31-dffd67144364",
"name": "Merge",
"type": "n8n-nodes-base.merge",
"position": [
400,
340
],
"parameters": {},
"typeVersion": 2
},
{
"id": "fade1855-3973-493d-8d17-841cf1ad1097",
"name": "Notify Error to Slack",
"type": "n8n-nodes-base.httpRequest",
"position": [
1600,
640
],
"parameters": {
"url": "=https://hooks.slack.com/services/YOUR/WEBHOOK/URL",
"method": "POST",
"options": {},
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "text",
"value": "=\u26a0\ufe0f Error in support automation: {{$json[\"error\"][\"message\"]}}"
}
]
}
},
"typeVersion": 3,
"continueOnFail": true
},
{
"id": "67fa2e0e-c3b8-47ba-807e-46af2720408e",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-300,
140
],
"parameters": {
"width": 389,
"height": 464,
"content": "## Multi-Channel Customer Support Automation\n\nThis workflow automates customer support across multiple channels.\n\n### Features:\n- \ud83d\udce7 Email support via IMAP\n- \ud83c\udf10 Web form submissions\n- \ud83c\udff7\ufe0f Automatic categorization\n- \ud83c\udfaf Smart prioritization\n- \ud83d\udcac Auto-responses\n- \ud83d\udd14 Slack notifications\n- \ud83d\udcca CRM integration ready\n\n### Setup Required:\n1. Configure IMAP credentials\n2. Configure SMTP credentials\n3. Configure Slack credentials\n4. Update webhook URL in error handler\n5. Replace CRM placeholder with your system\n\n### Categories:\n- Billing\n- Technical\n- Account\n- Feature requests\n- General\n\n### Priority Levels:\n- Urgent (immediate attention)\n- High (4 hours)\n- Medium (24 hours)\n- Low (72 hours)"
},
"typeVersion": 1
},
{
"id": "1eb04a7a-6519-4336-9588-f74b6528a6a7",
"name": "Webhook Response",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1800,
340
],
"parameters": {
"options": {}
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "d53058f5-8312-4553-913b-45de35c35900",
"connections": {
"Merge": {
"main": [
[
{
"node": "Normalize Messages",
"type": "main",
"index": 0
}
]
]
},
"Log Success": {
"main": [
[
{
"node": "Webhook Response",
"type": "main",
"index": 0
}
]
]
},
"Notify Slack": {
"main": [
[
{
"node": "Store in CRM",
"type": "main",
"index": 0
}
],
[
{
"node": "Error Handler",
"type": "main",
"index": 0
}
]
]
},
"Store in CRM": {
"main": [
[
{
"node": "Log Success",
"type": "main",
"index": 0
}
]
]
},
"Email Trigger": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"Error Handler": {
"main": [
[
{
"node": "Notify Error to Slack",
"type": "main",
"index": 0
}
]
]
},
"Web Form Webhook": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"Normalize Messages": {
"main": [
[
{
"node": "Categorize & Prioritize",
"type": "main",
"index": 0
}
]
]
},
"Send Auto-Response": {
"main": [
[
{
"node": "Notify Slack",
"type": "main",
"index": 0
}
]
]
},
"Check Auto-Response": {
"main": [
[
{
"node": "Generate Auto-Response",
"type": "main",
"index": 0
}
],
[
{
"node": "Notify Slack",
"type": "main",
"index": 0
}
]
]
},
"Generate Auto-Response": {
"main": [
[
{
"node": "Send Auto-Response",
"type": "main",
"index": 0
}
]
]
},
"Categorize & Prioritize": {
"main": [
[
{
"node": "Check Auto-Response",
"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.
slackOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Transform your customer support operations with this enterprise-grade automation workflow that unifies, categorizes, and intelligently routes support tickets from multiple channels.
Source: https://n8n.io/workflows/5135/ — 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.
Email AI Auto-responder. Summerize and send email. Uses emailReadImap, emailSend, httpRequest, googleDrive. Event-driven trigger; 78 nodes.
Receive booking requests via webhook with automatic validation, duplicate detection, availability checking, confirmation emails, Google Calendar sync, and Slack notifications.
Email AI Auto-responder. Summerize and send email. Uses emailReadImap, emailSend, httpRequest, googleDrive. Event-driven trigger; 26 nodes.
Email AI Auto-responder. Summerize and send email. Uses emailReadImap, emailSend, httpRequest, googleDrive. Event-driven trigger; 26 nodes.
Automate short-term trading research by generating high-quality trade ideas using MCP (Market Context Protocol) signals and AI-powered analysis. 📈🤖 This workflow evaluates market context, catalysts, m