n8n Automation

Customer Support Automation with n8n

DeviDevs Team
11 min read
#n8n#customer-support#automation#helpdesk#ai-support

Automated customer support improves response times and agent efficiency. This guide covers building intelligent support workflows with n8n for ticket routing, AI responses, and multi-channel management.

Intelligent Ticket Routing

Build smart ticket classification and routing:

// Multi-Channel Ticket Ingestion Workflow
 
// 1. Email Ticket Trigger
const emailTrigger = {
  name: "Email Ticket Trigger",
  type: "n8n-nodes-base.imapEmail",
  parameters: {
    mailbox: "INBOX",
    action: "read",
    options: {
      markSeen: true,
      forceReconnect: true
    }
  }
};
 
// 2. Ticket Classification Function
function classifyTicket(items) {
  const ticket = items[0].json;
  const subject = (ticket.subject || '').toLowerCase();
  const body = (ticket.text || ticket.html || '').toLowerCase();
  const content = subject + ' ' + body;
 
  // Category classification
  const categories = {
    billing: ['invoice', 'payment', 'charge', 'refund', 'subscription', 'pricing', 'bill'],
    technical: ['error', 'bug', 'crash', 'not working', 'broken', 'issue', 'problem', 'failed'],
    account: ['password', 'login', 'access', 'account', 'profile', 'settings', 'security'],
    sales: ['demo', 'pricing', 'enterprise', 'quote', 'trial', 'upgrade', 'plan'],
    general: ['question', 'help', 'information', 'inquiry', 'support']
  };
 
  let detectedCategory = 'general';
  let maxScore = 0;
 
  for (const [category, keywords] of Object.entries(categories)) {
    const score = keywords.filter(kw => content.includes(kw)).length;
    if (score > maxScore) {
      maxScore = score;
      detectedCategory = category;
    }
  }
 
  // Priority detection
  const urgentKeywords = ['urgent', 'asap', 'immediately', 'critical', 'emergency', 'down', 'outage'];
  const highKeywords = ['important', 'blocking', 'cannot', 'broken', 'failed'];
 
  let priority = 'normal';
  if (urgentKeywords.some(kw => content.includes(kw))) {
    priority = 'urgent';
  } else if (highKeywords.some(kw => content.includes(kw))) {
    priority = 'high';
  }
 
  // Sentiment analysis (simple)
  const negativeWords = ['frustrated', 'angry', 'disappointed', 'terrible', 'worst', 'hate', 'unacceptable'];
  const sentiment = negativeWords.some(w => content.includes(w)) ? 'negative' : 'neutral';
 
  return [{
    json: {
      ticket_id: `TKT-${Date.now()}`,
      source: 'email',
      from: ticket.from,
      subject: ticket.subject,
      body: ticket.text || ticket.html,
      received_at: new Date().toISOString(),
      category: detectedCategory,
      priority: priority,
      sentiment: sentiment,
      confidence: Math.min(maxScore / 3, 1),
      raw_email: ticket
    }
  }];
}
 
// 3. Route to Appropriate Queue
const routeTicket = {
  name: "Route Ticket",
  type: "n8n-nodes-base.switch",
  parameters: {
    rules: {
      rules: [
        {
          value1: "={{$json.priority}}",
          operation: "equals",
          value2: "urgent"
        },
        {
          value1: "={{$json.category}}",
          operation: "equals",
          value2: "billing"
        },
        {
          value1: "={{$json.category}}",
          operation: "equals",
          value2: "technical"
        },
        {
          value1: "={{$json.category}}",
          operation: "equals",
          value2: "sales"
        }
      ]
    }
  }
};
 
// 4. Assign to Agent Based on Skills and Availability
async function assignToAgent(items) {
  const ticket = items[0].json;
 
  // Get available agents with matching skills
  const agents = await getAvailableAgents(ticket.category);
 
  // Load balancing - find agent with lowest current workload
  let selectedAgent = null;
  let lowestWorkload = Infinity;
 
  for (const agent of agents) {
    const workload = await getAgentWorkload(agent.id);
    if (workload < lowestWorkload && workload < agent.max_tickets) {
      lowestWorkload = workload;
      selectedAgent = agent;
    }
  }
 
  // If no agent available, add to queue
  if (!selectedAgent) {
    return [{
      json: {
        ...ticket,
        status: 'queued',
        assigned_agent: null,
        queue_position: await getQueuePosition(ticket.category)
      }
    }];
  }
 
  return [{
    json: {
      ...ticket,
      status: 'assigned',
      assigned_agent: {
        id: selectedAgent.id,
        name: selectedAgent.name,
        email: selectedAgent.email
      },
      assigned_at: new Date().toISOString()
    }
  }];
}
 
// Helper functions
async function getAvailableAgents(category) {
  // Would query agent database
  return [
    { id: 'agent1', name: 'Alice', email: 'alice@company.com', skills: ['billing', 'general'], max_tickets: 10 },
    { id: 'agent2', name: 'Bob', email: 'bob@company.com', skills: ['technical', 'general'], max_tickets: 8 }
  ].filter(a => a.skills.includes(category) || a.skills.includes('general'));
}
 
async function getAgentWorkload(agentId) {
  // Would query current ticket assignments
  return Math.floor(Math.random() * 10);
}
 
async function getQueuePosition(category) {
  return Math.floor(Math.random() * 20) + 1;
}

AI-Powered Response Generation

Generate intelligent responses with AI:

// AI Response Generation Workflow
 
// 1. Prepare Context for AI
function prepareAIContext(items) {
  const ticket = items[0].json;
 
  // Build context from ticket history and knowledge base
  const context = {
    ticket_id: ticket.ticket_id,
    category: ticket.category,
    customer_message: ticket.body,
    customer_email: ticket.from,
    priority: ticket.priority,
    sentiment: ticket.sentiment
  };
 
  // System prompt based on category
  const systemPrompts = {
    billing: `You are a helpful billing support agent. Be empathetic about billing concerns.
              Focus on resolving payment issues quickly. Always offer to help with refunds or adjustments when appropriate.`,
    technical: `You are a technical support specialist. Ask clarifying questions about the issue.
                Provide step-by-step troubleshooting instructions. Escalate complex issues appropriately.`,
    account: `You are an account security specialist. Verify identity carefully before making changes.
              Guide users through secure password reset procedures.`,
    sales: `You are a sales representative. Be enthusiastic but not pushy.
            Focus on understanding customer needs and matching them with appropriate solutions.`,
    general: `You are a friendly customer support agent. Be helpful and professional.
              If you can't answer something, offer to connect the customer with the right team.`
  };
 
  context.system_prompt = systemPrompts[ticket.category] || systemPrompts.general;
 
  return [{ json: context }];
}
 
// 2. Generate AI Response
const generateAIResponse = {
  name: "Generate AI Response",
  type: "n8n-nodes-base.openAi",
  parameters: {
    operation: "text",
    model: "gpt-4",
    messages: [
      {
        role: "system",
        content: `{{$json.system_prompt}}
 
Your response guidelines:
- Be professional and empathetic
- Keep responses concise but complete
- Use proper grammar and formatting
- Include specific next steps when applicable
- Never share sensitive information
- If unsure, offer to escalate to a human agent`
      },
      {
        role: "user",
        content: `Customer email: {{$json.customer_email}}
Category: {{$json.category}}
Priority: {{$json.priority}}
Customer sentiment: {{$json.sentiment}}
 
Customer message:
{{$json.customer_message}}
 
Please draft a helpful response.`
      }
    ],
    options: {
      temperature: 0.7,
      maxTokens: 500
    }
  }
};
 
// 3. Response Quality Check
function checkResponseQuality(items) {
  const aiResponse = items[0].json;
  const context = $('Prepare AI Context').first().json;
  const responseText = aiResponse.message?.content || aiResponse.text || '';
 
  const qualityChecks = {
    hasGreeting: /^(hi|hello|dear|thank)/i.test(responseText),
    hasSignoff: /(regards|sincerely|best|thanks)/i.test(responseText),
    appropriateLength: responseText.length >= 50 && responseText.length <= 2000,
    noSensitiveInfo: !/(password|credit card|ssn|social security)/i.test(responseText),
    addressesIssue: responseText.toLowerCase().includes(context.category) ||
                     responseText.length > 100,
    professionalTone: !/(!{2,}|CAPS{3,})/g.test(responseText)
  };
 
  const passedChecks = Object.values(qualityChecks).filter(v => v).length;
  const qualityScore = passedChecks / Object.keys(qualityChecks).length;
 
  const needsReview = qualityScore < 0.8 ||
                      context.sentiment === 'negative' ||
                      context.priority === 'urgent';
 
  return [{
    json: {
      ticket_id: context.ticket_id,
      original_message: context.customer_message,
      ai_response: responseText,
      quality_checks: qualityChecks,
      quality_score: qualityScore,
      needs_human_review: needsReview,
      auto_send_eligible: qualityScore >= 0.9 && !needsReview
    }
  }];
}
 
// 4. Human Review Queue (for flagged responses)
const humanReviewNode = {
  name: "Send to Review Queue",
  type: "n8n-nodes-base.slack",
  parameters: {
    operation: "postMessage",
    channel: "#support-review",
    text: "🔍 *Response Review Needed*",
    attachments: [
      {
        color: "={{$json.quality_score >= 0.8 ? 'warning' : 'danger'}}",
        fields: [
          { title: "Ticket ID", value: "={{$json.ticket_id}}", short: true },
          { title: "Quality Score", value: "={{($json.quality_score * 100).toFixed(0)}}%", short: true },
          { title: "Customer Message", value: "={{$json.original_message.substring(0, 200)}}..." },
          { title: "AI Draft Response", value: "={{$json.ai_response.substring(0, 500)}}..." }
        ],
        actions: [
          { type: "button", text: "✅ Approve & Send", value: "approve_{{$json.ticket_id}}" },
          { type: "button", text: "✏️ Edit", value: "edit_{{$json.ticket_id}}" },
          { type: "button", text: "❌ Reject", value: "reject_{{$json.ticket_id}}" }
        ]
      }
    ]
  }
};
 
// 5. Auto-Send Approved Responses
const autoSendResponse = {
  name: "Send Response",
  type: "n8n-nodes-base.emailSend",
  parameters: {
    fromEmail: "support@company.com",
    toEmail: "={{$json.customer_email}}",
    subject: "Re: {{$json.subject}}",
    text: "={{$json.ai_response}}",
    options: {
      replyTo: "support@company.com"
    }
  }
};

Escalation Workflow

Handle ticket escalation automatically:

// Ticket Escalation Workflow
 
// 1. Monitor for Escalation Triggers
const escalationTriggers = {
  name: "Check Escalation Needed",
  type: "n8n-nodes-base.function",
  parameters: {
    functionCode: `
      const ticket = items[0].json;
 
      // Escalation rules
      const rules = [
        {
          name: 'sla_breach',
          condition: () => {
            const ageHours = (Date.now() - new Date(ticket.created_at).getTime()) / (1000 * 60 * 60);
            const slaHours = { urgent: 1, high: 4, normal: 24, low: 48 };
            return ageHours > slaHours[ticket.priority] && ticket.status !== 'resolved';
          },
          escalation_level: 'manager',
          reason: 'SLA breach imminent'
        },
        {
          name: 'negative_sentiment_unresolved',
          condition: () => ticket.sentiment === 'negative' && ticket.replies > 2 && ticket.status !== 'resolved',
          escalation_level: 'senior_agent',
          reason: 'Negative sentiment with multiple replies'
        },
        {
          name: 'customer_requested',
          condition: () => ticket.body.toLowerCase().includes('speak to manager') ||
                          ticket.body.toLowerCase().includes('escalate'),
          escalation_level: 'manager',
          reason: 'Customer requested escalation'
        },
        {
          name: 'vip_customer',
          condition: () => ticket.customer_tier === 'enterprise' && ticket.priority !== 'low',
          escalation_level: 'senior_agent',
          reason: 'VIP customer ticket'
        },
        {
          name: 'technical_complexity',
          condition: () => ticket.category === 'technical' &&
                          (ticket.body.includes('data loss') || ticket.body.includes('security')),
          escalation_level: 'engineering',
          reason: 'Complex technical issue'
        }
      ];
 
      // Check each rule
      const triggeredRules = rules.filter(rule => {
        try {
          return rule.condition();
        } catch {
          return false;
        }
      });
 
      return [{
        json: {
          ...ticket,
          escalation_needed: triggeredRules.length > 0,
          escalation_rules_triggered: triggeredRules.map(r => ({
            name: r.name,
            level: r.escalation_level,
            reason: r.reason
          })),
          highest_escalation_level: triggeredRules.length > 0 ?
            getHighestLevel(triggeredRules.map(r => r.escalation_level)) : null
        }
      }];
 
      function getHighestLevel(levels) {
        const hierarchy = ['senior_agent', 'manager', 'engineering', 'executive'];
        return levels.sort((a, b) => hierarchy.indexOf(b) - hierarchy.indexOf(a))[0];
      }
    `
  }
};
 
// 2. Execute Escalation
function executeEscalation(items) {
  const ticket = items[0].json;
 
  if (!ticket.escalation_needed) {
    return [{ json: { ...ticket, escalated: false } }];
  }
 
  const escalationActions = {
    senior_agent: {
      notify: ['senior-support@company.com'],
      slack_channel: '#senior-support',
      priority_boost: true
    },
    manager: {
      notify: ['support-manager@company.com'],
      slack_channel: '#support-managers',
      priority_boost: true,
      auto_respond: true
    },
    engineering: {
      notify: ['engineering-oncall@company.com'],
      slack_channel: '#engineering-support',
      create_jira: true
    },
    executive: {
      notify: ['vp-support@company.com', 'cto@company.com'],
      slack_channel: '#executive-escalations',
      priority_boost: true,
      auto_respond: true
    }
  };
 
  const actions = escalationActions[ticket.highest_escalation_level] || escalationActions.manager;
 
  return [{
    json: {
      ...ticket,
      escalated: true,
      escalation_level: ticket.highest_escalation_level,
      escalation_actions: actions,
      escalated_at: new Date().toISOString(),
      escalation_reasons: ticket.escalation_rules_triggered.map(r => r.reason)
    }
  }];
}
 
// 3. Notify Escalation Recipients
const notifyEscalation = {
  name: "Notify via Slack",
  type: "n8n-nodes-base.slack",
  parameters: {
    operation: "postMessage",
    channel: "={{$json.escalation_actions.slack_channel}}",
    text: "🚨 *Ticket Escalated*",
    attachments: [
      {
        color: "danger",
        fields: [
          { title: "Ticket ID", value: "={{$json.ticket_id}}", short: true },
          { title: "Escalation Level", value: "={{$json.escalation_level}}", short: true },
          { title: "Customer", value: "={{$json.customer_email}}", short: true },
          { title: "Priority", value: "={{$json.priority}}", short: true },
          { title: "Reasons", value: "={{$json.escalation_reasons.join('\\n')}}" },
          { title: "Subject", value: "={{$json.subject}}" }
        ],
        actions: [
          { type: "button", text: "View Ticket", url: "={{$env.HELPDESK_URL}}/tickets/{{$json.ticket_id}}" },
          { type: "button", text: "Take Ownership", value: "claim_{{$json.ticket_id}}" }
        ]
      }
    ]
  }
};
 
// 4. Auto-Response for Escalated Tickets
const escalationAutoResponse = {
  name: "Send Escalation Notice to Customer",
  type: "n8n-nodes-base.emailSend",
  parameters: {
    fromEmail: "support@company.com",
    toEmail: "={{$json.customer_email}}",
    subject: "Re: {{$json.subject}} - Your ticket has been escalated",
    text: `Dear Customer,
 
Thank you for your patience. We understand the importance of your request and have escalated your ticket to our {{$json.escalation_level.replace('_', ' ')}} team for priority handling.
 
You can expect to hear from a specialist within the next {{$json.priority === 'urgent' ? '30 minutes' : '2 hours'}}.
 
We apologize for any inconvenience and appreciate your understanding.
 
Best regards,
Customer Support Team
 
Ticket Reference: {{$json.ticket_id}}`
  }
};

Support Analytics Dashboard

Track support metrics:

// Support Analytics Workflow
 
// 1. Daily Metrics Collection
const dailyMetricsTrigger = {
  name: "Daily Metrics Trigger",
  type: "n8n-nodes-base.scheduleTrigger",
  parameters: {
    rule: {
      interval: [{ field: "hours", hoursInterval: 24 }]
    }
  }
};
 
// 2. Collect Metrics
async function collectSupportMetrics(items) {
  const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
  const today = new Date();
 
  // Would query actual helpdesk database
  const metrics = {
    period: {
      start: yesterday.toISOString(),
      end: today.toISOString()
    },
    tickets: {
      total_received: 150,
      total_resolved: 142,
      total_escalated: 8,
      by_channel: {
        email: 80,
        chat: 45,
        phone: 20,
        social: 5
      },
      by_category: {
        technical: 55,
        billing: 35,
        account: 25,
        sales: 20,
        general: 15
      },
      by_priority: {
        urgent: 5,
        high: 25,
        normal: 100,
        low: 20
      }
    },
    response_times: {
      first_response_avg_minutes: 15,
      first_response_median_minutes: 8,
      resolution_avg_hours: 4.5,
      resolution_median_hours: 2.1
    },
    sla: {
      first_response_compliance: 0.94,
      resolution_compliance: 0.89,
      breaches: 12
    },
    customer_satisfaction: {
      csat_score: 4.2,
      nps: 45,
      surveys_sent: 142,
      surveys_completed: 68,
      response_rate: 0.48
    },
    agent_performance: {
      tickets_per_agent_avg: 18,
      top_performers: [
        { name: 'Alice', tickets_resolved: 28, csat: 4.8 },
        { name: 'Bob', tickets_resolved: 25, csat: 4.5 }
      ]
    },
    ai_automation: {
      auto_resolved: 35,
      auto_resolved_rate: 0.23,
      ai_responses_sent: 89,
      ai_responses_approved: 82,
      approval_rate: 0.92
    }
  };
 
  return [{ json: metrics }];
}
 
// 3. Store Metrics
const storeMetrics = {
  name: "Store Metrics",
  type: "n8n-nodes-base.postgres",
  parameters: {
    operation: "insert",
    table: "support_metrics",
    columns: "period_start, period_end, metrics_json, created_at"
  }
};
 
// 4. Generate Report
function generateReport(items) {
  const metrics = items[0].json;
 
  const report = {
    title: `Daily Support Report - ${new Date().toLocaleDateString()}`,
    summary: {
      tickets_handled: metrics.tickets.total_received,
      resolution_rate: ((metrics.tickets.total_resolved / metrics.tickets.total_received) * 100).toFixed(1) + '%',
      avg_response_time: `${metrics.response_times.first_response_avg_minutes} minutes`,
      customer_satisfaction: `${metrics.customer_satisfaction.csat_score}/5`
    },
    highlights: [],
    alerts: [],
    recommendations: []
  };
 
  // Add highlights
  if (metrics.sla.first_response_compliance > 0.95) {
    report.highlights.push('✅ Excellent first response SLA compliance');
  }
  if (metrics.ai_automation.auto_resolved_rate > 0.2) {
    report.highlights.push(`✅ AI automation resolved ${metrics.ai_automation.auto_resolved} tickets`);
  }
 
  // Add alerts
  if (metrics.sla.breaches > 10) {
    report.alerts.push(`⚠️ ${metrics.sla.breaches} SLA breaches yesterday`);
  }
  if (metrics.customer_satisfaction.csat_score < 4.0) {
    report.alerts.push('⚠️ CSAT score below target');
  }
 
  // Add recommendations
  if (metrics.tickets.by_category.technical > metrics.tickets.total_received * 0.4) {
    report.recommendations.push('Consider creating more technical documentation to reduce ticket volume');
  }
 
  return [{ json: report }];
}
 
// 5. Send Report
const sendReport = {
  name: "Send Daily Report",
  type: "n8n-nodes-base.slack",
  parameters: {
    operation: "postMessage",
    channel: "#support-metrics",
    text: "*{{$json.title}}*",
    attachments: [
      {
        color: "good",
        title: "Summary",
        fields: [
          { title: "Tickets Handled", value: "={{$json.summary.tickets_handled}}", short: true },
          { title: "Resolution Rate", value: "={{$json.summary.resolution_rate}}", short: true },
          { title: "Avg Response", value: "={{$json.summary.avg_response_time}}", short: true },
          { title: "CSAT", value: "={{$json.summary.customer_satisfaction}}", short: true }
        ]
      }
    ]
  }
};

Conclusion

n8n enables powerful customer support automation from ticket routing to AI-powered responses. Build intelligent classification systems, implement escalation workflows, and track comprehensive metrics. Start with basic routing and gradually add AI capabilities and advanced analytics as your support operation scales.

Weekly AI Security & Automation Digest

Get the latest on AI Security, workflow automation, secure integrations, and custom platform development delivered weekly.

No spam. Unsubscribe anytime.