Your support team answers the same questions over and over. Password resets. Shipping status. Return policies. It’s soul-crushing work that doesn’t scale.
AI can handle 60-80% of these inquiries automatically—instantly, 24/7, without burning out your team.
This tutorial shows you how to build an AI-powered email support system from scratch.
What We’re Building
[Support Email] → [AI: Classify & Understand] → [Route/Respond]
↓
┌───────────────────────────┼───────────────────────────┐
↓ ↓ ↓
[Auto-Reply] [Draft for Review] [Escalate]
(High confidence) (Medium confidence) (Complex/Angry)
The system will:
- Automatically classify incoming support emails
- Generate personalized responses for common questions
- Draft responses for human review when unsure
- Escalate complex or sensitive issues immediately
- Learn from feedback to improve over time
Prerequisites
- n8n (self-hosted or cloud)
- Anthropic API key (Claude)
- Email account with IMAP/SMTP access
- Help desk or ticketing system (optional but recommended)
- Knowledge base content (FAQ, docs, policies)
Step 1: Email Intake Setup
First, get emails into your workflow.
Option A: IMAP Trigger
Poll for new emails directly:
Node: Email Trigger (IMAP)
Host: imap.gmail.com (or your provider)
Port: 993
Credentials: Your support email
Settings:
Mailbox: INBOX
Polling Interval: 1 minute
Mark as Read: No (we'll do this after processing)
Option B: Help Desk Webhook
If using Zendesk, Freshdesk, etc.:
Node: Webhook
Path: /support-email
Method: POST
Configure your help desk to POST new tickets here.
Option C: Gmail Watch (Recommended for Gmail)
Uses push notifications instead of polling:
Node: Gmail Trigger
Event: Message Received
Label: Support (create a filter that labels support emails)
Step 2: Email Preprocessing
Clean and extract useful information.
Extract Email Data
const email = $input.first().json;
return [{
json: {
// Core email data
from: email.from.text || email.from,
subject: email.subject,
body: email.text || email.html?.replace(/<[^>]*>/g, ' ').trim(),
date: email.date,
// Metadata
has_attachments: (email.attachments?.length || 0) > 0,
is_reply: email.subject?.toLowerCase().startsWith('re:'),
thread_id: email.messageId,
// Clean body (remove signatures, quoted text)
clean_body: cleanEmailBody(email.text || email.html)
}
}];
function cleanEmailBody(text) {
if (!text) return '';
// Remove HTML tags
let clean = text.replace(/<[^>]*>/g, ' ');
// Remove quoted replies
clean = clean.split(/^>|^On .* wrote:/m)[0];
// Remove common signatures
clean = clean.split(/^--|^Best regards|^Sent from my/mi)[0];
return clean.trim().substring(0, 3000); // Limit length
}
Step 3: AI Classification
Determine what the email is about and how to handle it.
Classification Prompt
Node: Anthropic
Model: claude-3-5-sonnet-20241022
System:
You are a customer support email classifier. Analyze incoming emails and classify them.
Categories:
- BILLING: Payment issues, invoices, refunds, subscription changes
- TECHNICAL: Product bugs, errors, how-to questions
- SHIPPING: Order status, delivery issues, tracking
- ACCOUNT: Login problems, password reset, account changes
- FEATURE_REQUEST: Product suggestions, enhancement requests
- COMPLAINT: Unhappy customer, service issues
- SPAM: Marketing, irrelevant, automated messages
- OTHER: Doesn't fit other categories
Urgency levels:
- HIGH: Customer angry, business-critical, legal mention
- MEDIUM: Clear issue needing resolution
- LOW: General inquiry, not time-sensitive
Sentiment:
- POSITIVE: Happy customer, praise
- NEUTRAL: Factual inquiry
- NEGATIVE: Frustrated, disappointed
- ANGRY: Hostile, threatening
Confidence (your certainty about classification):
- HIGH: Very clear what this is about
- MEDIUM: Reasonably sure but some ambiguity
- LOW: Multiple possible interpretations
Return JSON only:
{
"category": "CATEGORY",
"urgency": "LEVEL",
"sentiment": "SENTIMENT",
"confidence": "LEVEL",
"summary": "One sentence summary of the request",
"key_entities": ["order number", "product name", etc.],
"needs_human": true/false,
"reason_for_human": "Why human needed (if applicable)"
}
User:
From: {{$json.from}}
Subject: {{$json.subject}}
{{$json.clean_body}}
Sample Output
{
"category": "SHIPPING",
"urgency": "MEDIUM",
"sentiment": "NEGATIVE",
"confidence": "HIGH",
"summary": "Customer asking about delayed order #12345",
"key_entities": ["order #12345", "3 weeks ago"],
"needs_human": false,
"reason_for_human": null
}
Step 4: Knowledge Base Retrieval
Pull relevant information to answer the query.
Simple Approach: Keyword Matching
const category = $json.category;
const entities = $json.key_entities;
// Map categories to knowledge base sections
const kbSections = {
BILLING: `
Refund Policy: Full refund within 30 days of purchase.
To request a refund, email billing@company.com with order number.
Subscription changes take effect at next billing cycle.
...
`,
SHIPPING: `
Standard shipping: 5-7 business days.
Express shipping: 2-3 business days.
International: 10-14 business days.
Track order: Visit company.com/track and enter order number.
...
`,
TECHNICAL: `
Common issues:
- Can't login: Reset password at company.com/reset
- App crashes: Update to latest version
- Feature not working: Clear cache and refresh
...
`
};
return [{
json: {
...$input.first().json,
knowledge_context: kbSections[$json.category] || kbSections.OTHER
}
}];
Advanced Approach: Vector Search
For larger knowledge bases, use embeddings:
// Embed the query
const queryEmbedding = await getEmbedding($json.summary);
// Search vector database (Pinecone, Weaviate, etc.)
const results = await vectorDb.query({
vector: queryEmbedding,
topK: 3,
includeMetadata: true
});
return [{
json: {
...$input.first().json,
knowledge_context: results.matches.map(m => m.metadata.content).join('\n\n')
}
}];
Step 5: Response Generation
Generate a helpful, personalized response.
Response Prompt
Node: Anthropic
Model: claude-3-5-sonnet-20241022
System:
You are a friendly, helpful customer support agent for [Company Name].
Guidelines:
- Be warm but professional
- Address the customer by name if available
- Directly answer their question
- If you need more information, ask specific questions
- Include relevant next steps
- Keep responses concise (under 200 words)
- Never make up information not in the knowledge base
- If uncertain, say "I'll have a team member look into this"
Company voice:
- Use "we" not "I"
- Contractions are fine (we're, don't, can't)
- No corporate jargon
- Empathize before solving
Knowledge base:
{{$json.knowledge_context}}
User:
Customer email:
From: {{$json.from}}
Subject: {{$json.subject}}
{{$json.clean_body}}
---
Classification:
Category: {{$json.classification.category}}
Summary: {{$json.classification.summary}}
Key entities: {{$json.classification.key_entities}}
Generate a response email. Do not include subject line.
Sample Output
Hi Sarah,
Thanks for reaching out about your order #12345.
I checked on this and see it shipped on January 10th via Standard Shipping.
Based on the carrier's tracking, it shows "In Transit" and should arrive
by January 17th.
You can track the latest status here: [tracking link]
If it hasn't arrived by the 18th, please let us know and we'll investigate
further with the carrier.
Is there anything else I can help with?
Best,
The [Company] Support Team
Step 6: Routing Logic
Decide what to do with the response.
Routing Decision Tree
Node: Switch
Conditions:
1. Auto-send (high confidence, not angry):
- classification.confidence = "HIGH"
- classification.sentiment != "ANGRY"
- classification.needs_human = false
2. Draft for review (medium confidence):
- classification.confidence = "MEDIUM"
- OR classification.sentiment = "NEGATIVE"
3. Escalate immediately:
- classification.confidence = "LOW"
- OR classification.sentiment = "ANGRY"
- OR classification.needs_human = true
- OR classification.urgency = "HIGH"
Auto-Send Path
[Response Generated] → [Create Ticket (Resolved)] → [Send Email] → [Log Success]
Review Path
[Response Generated] → [Create Draft Ticket] → [Slack: Review Request]
↓
[Agent Reviews & Edits]
↓
[Send Email]
Escalation Path
[Classification] → [Create Urgent Ticket] → [Assign to Senior Agent] → [Slack: 🚨 Alert]
Step 7: Human Review Interface
For medium-confidence responses, create an approval workflow.
Slack Approval Message
📧 *Support Response Ready for Review*
*From:* {{$json.from}}
*Subject:* {{$json.subject}}
*Category:* {{$json.classification.category}}
*Confidence:* {{$json.classification.confidence}}
*Customer said:*
> {{$json.clean_body.substring(0, 200)}}...
*AI drafted response:*
{{$json.response}}
[✅ Approve & Send] [✏️ Edit] [🚫 Reject & Escalate]
Handle Approval Actions
Node: Webhook
Path: /support-approval
If action = "approve":
→ Send email as-is
→ Close ticket
If action = "edit":
→ Open ticket for editing
→ Agent makes changes
→ Send edited version
If action = "reject":
→ Escalate to senior agent
→ Log rejection reason for training
Step 8: Learning and Improvement
Track performance and improve over time.
Metrics to Collect
const metrics = {
email_id: $json.email_id,
timestamp: new Date().toISOString(),
category: $json.classification.category,
confidence: $json.classification.confidence,
sentiment: $json.classification.sentiment,
// Outcome
action_taken: 'auto_sent' | 'reviewed' | 'escalated',
response_time_seconds: calculateResponseTime(),
// If reviewed
was_edited: $json.was_edited,
edit_percentage: calculateEditDistance(), // How much changed
// Customer feedback (if available)
customer_rating: $json.rating,
follow_up_required: $json.follow_up
};
Weekly Performance Report
📊 *Support AI Weekly Report*
*Volume:*
- Total emails: 847
- Auto-resolved: 512 (60%)
- Reviewed: 254 (30%)
- Escalated: 81 (10%)
*By Category:*
- Shipping: 312 (37%)
- Billing: 198 (23%)
- Technical: 156 (18%)
- Account: 108 (13%)
- Other: 73 (9%)
*AI Performance:*
- Avg response time: 47 seconds
- Review edit rate: 23% (77% sent unchanged)
- Customer satisfaction: 4.2/5
*Improvement Areas:*
- "Integration" questions have low confidence (add to KB)
- Billing complaints often escalated (review policies)
Feedback Loop
When agents edit AI responses, capture the changes:
// Compare original to edited version
const similarity = calculateSimilarity(
$json.ai_response,
$json.sent_response
);
if (similarity < 0.8) {
// Significant edit - log for training
logForTraining({
input: $json.email,
original_response: $json.ai_response,
improved_response: $json.sent_response,
category: $json.category
});
}
Complete Workflow
1. [Email Trigger: New Support Email]
↓
2. [Code: Preprocess & Clean]
↓
3. [Claude: Classify Email]
↓
4. [Code: Fetch Knowledge Context]
↓
5. [Claude: Generate Response]
↓
6. [Switch: Route Based on Confidence]
├── High → [Send Email] → [Create Resolved Ticket]
├── Medium → [Slack: Review Request] → [Wait for Approval]
└── Low/Angry → [Create Urgent Ticket] → [Alert Team]
↓
7. [Log Metrics to Database]
Handling Edge Cases
Multi-Part Questions
When emails contain multiple questions:
System prompt addition:
If the email contains multiple distinct questions:
1. Address each question separately
2. Use numbered points
3. If any question needs escalation, draft what you can and note "I'm also looping in a team member to help with [specific question]"
Foreign Languages
System prompt addition:
If the email is not in English:
1. Respond in the same language
2. Set needs_human: true
3. Add to reason: "Non-English email - please verify translation"
Attached Files
if ($json.has_attachments) {
// Don't auto-send - attachments often need human review
$json.classification.needs_human = true;
$json.classification.reason_for_human = "Email contains attachments";
}
Reply Threads
if ($json.is_reply) {
// Fetch conversation history
const thread = await getEmailThread($json.thread_id);
// Include in prompt
$json.context = `Previous conversation:\n${thread}`;
}
Security Considerations
Don’t Expose Sensitive Data
// Redact before logging
const sanitized = {
...$json,
body: redactSensitiveInfo($json.body),
from: hashEmail($json.from)
};
Rate Limiting
// Track emails per sender
const senderKey = `rate:${$json.from}`;
const count = await redis.incr(senderKey);
await redis.expire(senderKey, 3600); // 1 hour window
if (count > 10) {
// Too many emails - might be spam or attack
return { action: 'throttle' };
}
Prompt Injection Protection
// Basic sanitization
const cleanInput = $json.body
.replace(/ignore previous instructions/gi, '[removed]')
.replace(/you are now/gi, '[removed]')
.replace(/system:/gi, '[removed]');
Results to Expect
Based on implementations I’ve seen:
| Metric | Before | After |
|---|---|---|
| Avg response time | 4 hours | 2 minutes |
| Emails auto-resolved | 0% | 60-70% |
| Agent emails/day | 80 | 30 |
| Customer satisfaction | 3.8 | 4.3 |
| Cost per ticket | $8 | $3 |
Next Steps
Once basic email automation works:
- Add chat support: Same logic, real-time channel
- Build internal knowledge search: Help agents find answers faster
- Create customer self-service: AI-powered help center
- Implement sentiment trending: Catch product issues early
Need help building an AI support system for your business? Book a free consultation and we’ll design a solution that fits your workflow.