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.

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
  }
}];

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:

MetricBeforeAfter
Avg response time4 hours2 minutes
Emails auto-resolved0%60-70%
Agent emails/day8030
Customer satisfaction3.84.3
Cost per ticket$8$3

Next Steps

Once basic email automation works:

  1. Add chat support: Same logic, real-time channel
  2. Build internal knowledge search: Help agents find answers faster
  3. Create customer self-service: AI-powered help center
  4. 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.

Sources