Automated Ticket Routing: The Complete Setup Guide
Automated ticket routing, start to finish: four approaches compared, defining routes that hold up, building the test set, and wiring a safety review branch.
Automated ticket routing is the canonical automation decision: read a message, pick from a list you own, send it somewhere. It's also where most teams first feel the difference between a model that answers and a model that decides. This guide covers the four ways to do it, then the setup walkthrough that applies to whichever you pick.
The four approaches
1. Keyword rules
IF body CONTAINS "refund" → billing. Zero ML, instant setup, fully explainable.
- Pros: deterministic, free, auditable, fast.
- Cons: synonyms and phrasing defeat it ("charged twice", "double-billed", "you took my money twice"); every new product or phrasing needs new rules; multi-intent messages break it.
- Fits: tiny route sets that rarely change; hard overrides (see below); compliance surfaces where you must show the rule.
2. Chat LLM prompt
Ask GPT-6/Claude "reply with billing, technical or other" and parse.
- Pros: understands language, handles phrasing variety, no training.
- Cons: off-list answers, format drift, inconsistent across prompt/model changes, no reliable built-in confidence, per-token cost per decision. Structured outputs fix the format problems, not the confidence one.
- Fits: low volume, tolerant consumers, option sets in flux.
3. Hosted decision model
A decision model behind an API, such as OpenAI's GPT-6 Luna Decisions (released 2026-10-06; see the Luna Decisions alternatives guide for specs and other hosted options): probabilities per option, many questions per request.
- Pros: real confidence signal, structured output by contract, no infrastructure to run.
- Cons: data leaves your servers, per-token billing, provider-managed versions.
- Fits: data policies that allow provider egress; teams without servers to run.
4. Self-hosted decision model / classifier
Open-weights decision models (e.g., Saina Helm) or fine-tuned classifiers on your own box.
- Pros: data stays in; flat cost instead of per-token billing; you pin the version; native multi-label and per-question thresholds (Helm: decision mode with reason codes).
- Cons: you run and maintain a server; on CPU it can be slower than a hosted API (Helm measured 0.9–2.7 s per request on CPU, local Docker test, 2026-10-06).
- Fits: data control, high volume, offline, n8n self-hosters.
No row here settles accuracy; measure on your own tickets.
The automated ticket routing setup walkthrough
Step 1: Define the routes
Write each route as an option with an ID and a plain-language description:
"billing": "Charges, invoices, refunds, subscription pricing changes"
Rules that hold up:
- Mutually exclusive as written. If a ticket could plausibly match two descriptions, they overlap; merge or sharpen. "Billing" vs. "refunds" flipping usually means the definitions overlap, so fix those first.
- Include
other, described plainly ("anything that doesn't fit the above"), routed to review, not to a team. - 5–12 routes is a workable band for a first pass (a rule of thumb); more and you need hierarchy (pick department, then team).
- Stable IDs. Teams rename; route IDs shouldn't. Descriptions can evolve; IDs are your joins.
You still maintain this list, whatever approach you use. The difference: with decision models it's data passed per request, rather than prompt prose or rule sprawl.
Step 2: Build the test set
- 50–100 real tickets with agreed-correct routes for a first check; 200+ before you tune thresholds (rules of thumb, not measured cutoffs). Yesterday's manual routing is the corpus.
- Include the weird ones: multi-intent, angry, one-word, wrong-language.
- Store ticket → expected route. This set outlives every model you'll try.
Step 3: Wire the routing step
The Helm shape, one call, route + priority + tags together:
from saina import Saina
client = Saina('https://helm.internal.example', api_key='YOUR_KEY')
result = client.ask(
model='saina-helm-0.8b',
state=ticket_text,
mode='decision', # required for thresholds and reason codes
threshold=0.8, # request-level default
questions={
'team': {
'type': 'single_choice',
'question': 'Which team should handle this ticket?',
'options': route_options, # the data from Step 1
},
'priority': {
'type': 'rating',
'question': 'How urgent is this?',
'levels': ['Routine', 'Same-day attention', 'Drop other work'],
},
'tags': {
'type': 'multi_choice',
'question': 'Which topics apply?',
'options': {'refund': 'Money back requests',
'outage': 'Service down or degraded',
'access': 'Login or permission trouble'},
'threshold': 0.5, # applies per tag; an example to tune
},
},
)
team = result['answers']['team']['selection'] # None if rejected
priority = result['answers']['priority']['level'] # 0, 1 or 2 (zero-based), None if rejected
tags = result['answers']['tags']['selections'] # tags at or above 0.5
The tag question gets its own lower threshold because it applies to each tag independently; at 0.8 a tag question often selects nothing. client.ask returns a plain dict; network or validation failures raise SainaError.
In n8n, the Helm community node (self-hosted n8n) exposes Selected (answer accepted) and Fallback (answer rejected, plus errors if you configure it) as outputs; see Routing support tickets in n8n. The HTTP Request template calls the same /v1/ask endpoint and works on n8n Cloud. A different hosted decision API fits the same slot but needs its own request shape and branch logic.
Step 4: Add the review branch
- Threshold and margin decide what auto-routes; everything else goes to a queue with the probabilities and reason attached. Reason codes depend on the question type: single-choice and yes/no answers can come back
below_threshold,below_marginortie; multi-choice tags are onlyaccepted(at least one tag selected) orbelow_threshold(none); ratings ignore the margin. - A human decisions log on that queue becomes your labeled set for the next iteration.
- Keep hard overrides as rules: VIP accounts, legal-hold keywords, named customers. Rules you can state exactly should stay rules.
Step 5: Validate, then enable
Shadow mode first: log decisions without routing on them; compare to the test set and to current behavior. Fix option wording where disagreement clusters. Then enable, and re-run the test set monthly, and after any change to options, thresholds, or model version.
Checklist
- Routes written as ID + description; mutually exclusive;
otherpresent. - 50–100 ticket test set with expected routes (200+ before tuning thresholds).
- Decision mode with threshold and margin; lower per-question threshold on tag questions; review branch wired and watched.
- Hard overrides kept as explicit rules.
- Shadow run before enabling; test-set re-run monthly and after every change.
- Per-route fallback rates reviewed weekly; option wording fixed where uncertainty clusters.