Blog · October 16, 2026

How to Replace Keyword Rules in Your Helpdesk Routing

Replacing keyword rules in helpdesk routing: why rules break at scale, running AI alongside them, and which rules to keep as permanent hard overrides.

Saina · October 16, 2026 · 4 min read

Your helpdesk started with rules: subject CONTAINS "invoice" → billing. It worked at 200 tickets a month. At 2,000, you have 140 rules, three people who know what half of them do, and a growing pile of tickets that match nothing and everything (illustrative). This is the guide to automated helpdesk routing without keyword rules: replacing them with a model that reads the text. It is not a "delete your rules" guide; some rules should survive.

Why keyword rules break in helpdesk routing at scale

Rules are literal string matching doing a job that needs reading comprehension. The fix is a model that reads; the discipline is keeping rules for the cases that should never depend on a model's confidence.

Run AI alongside rules first

Don't rip rules out on day one. Shadow mode:

  1. Add the model as a logging-only step: for each ticket, record its route and probabilities, and the probabilities of the rule's route.
  2. Let rules keep routing for two weeks.
  3. Analyze: where do they agree (most tickets, usually)? Where the model is confident and the rule fired differently, which was right? Where the rule matched nothing, what did the model do?

This gives you the case file for what to keep, and it costs a model call per ticket and a log. For a fuller shadow-mode and rollback procedure, see how to classify and route support tickets without GPT.

Which rules survive as hard overrides

Keep a rule when you can state it exactly and the stakes are absolute:

Everything else, the judgment calls, is what the model is for.

The model step

A decision model fits this slot naturally, because your routes become data: the options passed with every request, not prompt text or rule order. Hosted options exist too: a decision API such as OpenAI's GPT-6 Luna Decisions over HTTP, or a chat LLM constrained with structured outputs (where you build the confidence handling yourself). The example below uses Saina Helm, an open-weights decision model you run yourself.

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',
    threshold=0.8,   # example, tune on your data (0.8 is also the default)
    questions={
        'team': {
            'type': 'single_choice',
            'question': 'Which team should handle this ticket?',
            'options': {'billing': 'Charges, invoices, refunds',
                        'technical': 'Outages, errors, account access',
                        'security': 'Account compromise, phishing reports',
                        'other': 'Anything else'},
        },
        'legal_or_privacy': {
            'type': 'yes_no',
            'question': 'Is this a legal request or a request to access or delete personal data?',
        },
        'tags': {
            'type': 'multi_choice',
            'question': 'Which of these does the ticket ask for?',
            'options': {'refund': 'Wants money back',
                        'login': 'Cannot sign in',
                        'bug': 'Something is broken'},
            'threshold': 0.5,   # example, tune on your data; applies per label
        },
    },
)
team = result['answers']['team']
if team['selection'] is None:
    print(team['reason'], team['probabilities'])   # goes to the review queue

In decision mode, a ticket whose top option falls below the threshold comes back with no selection and a reason (below_threshold, or tie if the top two are exactly equal). min_margin defaults to 0, and at a 0.8 threshold any accepted answer is already well clear of the runner-up, so you don't need it here. That unselected bucket is your new "default queue", except now it contains only genuinely ambiguous tickets, each with probabilities attached, instead of everything that missed a string.

In n8n, the Saina node (community node, self-hosted n8n only) gives you this split as two outputs: Selected for accepted decisions and Fallback for rejected ones. On n8n Cloud, use the portable HTTP Request template from run-saina/n8n-nodes instead; the n8n routing guide walks through both.

Order of operations per ticket: hard overrides first (exact, cheap, absolute), then the model, then the review queue.

Handling the edge cases

Migration checklist