How to Automate Support Ticket Triage with n8n
The Problem
A shared support queue treats a customer whose whole system is down the same as one asking about invoice formatting. Someone has to read every new ticket, decide how urgent it is, pick an agent, and warn the team if it is serious. When that someone is busy, the outage ticket waits behind routine requests, and the agent who happens to be online gets all the work. This n8n workflow reads each new ticket as it arrives, scores priority from keywords and customer tier, assigns the next agent in rotation, and routes alerts by urgency, paging on-call only when it truly matters.
What You Will Need
- A self-hosted n8n instance (Community Edition is free) or an n8n Cloud trial
- A ticketing system that can send a webhook on ticket creation and accepts API updates, a Google account for the Sheets state file, a Slack app, and SMTP credentials for the paging email
- All steps use built in n8n nodes, and the n8n Code nodes need only basic JavaScript.
- Estimated setup time: 60 to 90 minutes
- A Google Sheet with an Agents tab (name, email, slack id) and a State tab holding one cell named next_index
Step-by-Step Workflow Setup
1 Receive the ticket with a Webhook
In n8n the Webhook node exposes separate test and production URLs, so activate the n8n workflow before you paste the production URL into the ticketing system.
The Webhook node gives your ticketing system a URL to call when a ticket is created. Use the production URL in the ticketing tool, not the test URL, and protect it with a shared secret header.
Add a Webhook node and configure:
Node: Webhook
HTTP Method: POST
Path: ticket-created
Authentication: Header Auth
Respond: Immediately
Response Code: 200
Responding immediately keeps the ticketing system from retrying while the rest of the n8n workflow runs. Read fields from the body with expressions such as:
Expression: {{ $json.body.ticket.subject }}
{
"event": "ticket.created",
"ticket": {
"id": 48213,
"subject": "Dashboard is down for all users",
"description": "Since 9:10 we get a 502 error on login.",
"requester": { "email": "ops@bigco.example", "tier": "enterprise" }
}
}
2 Classify priority by keyword and tier
This n8n Code node is where support leads can tune the keyword lists, and n8n keeps the previous version in its workflow history.
A Code node scans the subject and description. Keywords map to a base priority, and the customer tier can raise it by one level. Checking the strongest words first guarantees that a ticket mentioning both down and slow is treated as an outage.
Node: Code
Mode: Run Once for Each Item
Language: JavaScript
const t = $json.body.ticket;
const text = (t.subject + ' ' + t.description).toLowerCase();
const has = (words) => words.some(w => text.includes(w));
let level = 3;
if (has(['down', 'outage', 'crash'])) level = 0;
else if (has(['broken', 'error', 'fail'])) level = 1;
else if (has(['slow', 'delay'])) level = 2;
const tier = t.requester.tier;
if (tier === 'enterprise' && level > 0) level = level - 1;
const sla = { 0: 15, 1: 60, 2: 240, 3: 1440 }[level];
return { json: { id: t.id, subject: t.subject, email: t.requester.email,
tier, priority: 'P' + level, slaMinutes: sla } };
{
"id": 48213,
"subject": "Dashboard is down for all users",
"tier": "enterprise",
"priority": "P0",
"slaMinutes": 15
}
The tier bump means an enterprise customer with a slow report becomes P1 rather than P2. The slaMinutes value is used later so alerts show how long the team has to respond. Keep the keyword lists in one place so support leads can tune them without touching the rest of the logic.
3 Pick the next agent round-robin
State has to live outside n8n executions, because each n8n run starts clean; a spreadsheet is the easiest place for it.
Round-robin needs memory, and a workflow run forgets everything when it ends. Store the rotation index in Google Sheets. First, a Google Sheets node reads the agents, and a second one reads the saved index.
Node: Google Sheets (Agents)
Resource: Sheet Within Document
Operation: Get Row(s)
Document: Support Rotation
Sheet: Agents
Return All: on
Node: Google Sheets (State)
Operation: Get Row(s)
Sheet: State
Filters: key = next_index
Then a Code node picks the agent and computes the wrapped index:
Node: Code
Mode: Run Once for All Items
const agents = $('Google Sheets (Agents)').all().map(i => i.json);
const idx = Number($('Google Sheets (State)').first().json.value) % agents.length;
const agent = agents[idx];
const next = (idx + 1) % agents.length;
return [{ json: { ...$('Code').first().json, agent: agent.name,
agentEmail: agent.email, nextIndex: next } }];
{
"agent": "Priya",
"agentEmail": "priya@yourcompany.example",
"nextIndex": 3
}
The modulo on the read protects you if an agent is removed from the sheet and the saved index is now past the end of the list.
4 Save the index and update the ticket
The n8n HTTP Request node supports retries in its settings, which is useful when the ticketing API is briefly unavailable.
Write the incremented index back to the State tab so the next ticket gets the next agent. Then call the ticketing API with an HTTP Request node to set priority and assignee.
Node: Google Sheets
Operation: Update Row
Sheet: State
Column to match on: key
Values: key = next_index, value = {{ $json.nextIndex }}
Node: HTTP Request
Method: PUT
URL: https://tickets.yourcompany.example/api/v1/tickets/{{ $json.id }}
Authentication: Header Auth
Send Body: JSON
Body:
{
"priority": "{{ $json.priority }}",
"assignee_email": "{{ $json.agentEmail }}",
"tags": ["auto-triaged", "tier-{{ $json.tier }}"]
}
Run the sheet update before the API call, or set the HTTP node to Continue On Fail, so a failed API call does not leave the index unchanged and give the same agent a second ticket.
{
"id": 48213,
"priority": "P0",
"assignee_email": "priya@yourcompany.example",
"status": "open"
}
5 Route by urgency with a Switch
The n8n Switch node can have more outputs later, for example a separate path for billing tickets, without redesigning the n8n flow.
The Switch node sends urgent tickets and routine tickets down separate paths.
Node: Switch
Mode: Rules
Rule 1 (Output 0): {{ $json.priority }} matches regex ^P[01]$
Rule 2 (Output 1): {{ $json.priority }} matches regex ^P[23]$
Fallback Output: Output 1
Output 0 carries P0 and P1 tickets to the urgent Slack channel. Output 1 handles P2 and P3 and goes to the queue channel.
6 Post to Slack
Use the n8n expression editor to preview the Slack message with real ticket data before you activate the n8n workflow.
Use two Slack nodes. The urgent message carries full context, including the SLA window, so the agent knows the deadline.
Node: Slack (urgent)
Resource: Message
Operation: Send
Channel: #support-urgent
Text: *{{ $json.priority }}* ticket #{{ $json.id }} for {{ $json.tier }} customer
Subject: {{ $json.subject }}
Assigned to: {{ $json.agent }}
Respond within: {{ $json.slaMinutes }} minutes
Node: Slack (queue)
Channel: #support-queue
Text: {{ $json.priority }} #{{ $json.id }} {{ $json.subject }} assigned to {{ $json.agent }}
7 Page on-call for P0
The n8n IF node and Send Email node together form a tiny paging system that costs nothing extra to run on n8n.
From the urgent Slack node, add an IF node that checks for P0. When true, a Send Email node pages the engineer. Pair it with an SMS gateway address if your on-call tool accepts email.
Node: IF
Condition: {{ $json.priority }} is equal to P0
Node: Send Email
To Email: oncall@yourcompany.example
Subject: P0 ticket #{{ $json.id }}: {{ $json.subject }}
Text: Customer tier {{ $json.tier }}. SLA {{ $json.slaMinutes }} minutes. Assigned: {{ $json.agent }}.
The false branch of the IF node ends the run, so P1 tickets alert Slack only.
Before you activate the n8n workflow, post three sample tickets to the test webhook: an outage, an error report, and a slow report. Watch how n8n classifies each one, which agent n8n picks, and which Slack channel receives the alert. Once all three paths behave, activate the n8n workflow and switch the ticketing system to the production URL.
Troubleshooting
Every ticket gets the same agent
The index cell is stored as text or the update runs after the next ticket arrives. Use a Number cast in the Code node, and process tickets one at a time by setting the workflow to a single concurrent execution so two tickets do not read the same index. In n8n, set the workflow setting for concurrent executions so tickets are processed one at a time.
Routine tickets are marked P0
Substring matching catches words inside other words, such as down inside download. Switch to a word boundary regex like /\bdown\b/ and review the keyword list against a sample of real tickets. Test the regex in an n8n Code node with a handful of real ticket subjects.
The webhook never fires
The ticketing system is likely calling the test URL after you have activated the workflow, or your instance sits behind a proxy without a correct WEBHOOK_URL setting. Check the executions list for incoming calls and confirm the public URL. The n8n executions list shows whether the call arrived at all.
Free n8n Workflow Resources
Get started with the free workflow pack on the LightCube Labs templates page.
Production-Ready Workflow Packs
Get this workflow and more, with setup guides, editable configurations, and a shared failure-alert system.
View the Starter ($29) and Pro ($99) PacksFrequently Asked Questions
How accurate is keyword classification for support tickets?
It is a fast first pass, not a perfect reader. It handles clear outage language well and misses tickets that describe a problem without trigger words. Review a week of results, then tune the lists. You can later add a step that calls an LLM API for ambiguous cases. You can rebuild the classifier inside n8n at any time without changing the ticketing system.
Can agents be skipped when they are off shift?
Yes. Add an Available column to the Agents sheet and filter the list in the Code node before applying the index, or store working hours and compare them with $now. You can do this in n8n with a simple filter.
Why use Google Sheets for the rotation state instead of workflow static data?
Static data works on a single instance but is invisible to your team. A sheet lets a support lead edit the roster and reset the index without opening the n8n editor. n8n can keep that state in a sheet that anyone on the team can read.
What should happen if the ticketing API is down?
Enable Retry On Fail on the HTTP Request node with three tries and a delay of 10 seconds. If it still fails, send the ticket to #support-urgent anyway so a human can assign it by hand. The n8n node settings expose retry options directly.