> ## Documentation Index
> Fetch the complete documentation index at: https://docs.haloagents.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Routing Rules

> First-match-wins rules that send a fresh ticket to the right inbox, priority, category, and assignee. With a live dry-run preview before you save.

## What routing does

Every ticket that lands in HaloAgents needs to end up somewhere: a specific inbox, a priority, a category, and (optionally) a teammate to own it. Routing rules are the engine that decides those four things automatically the moment a ticket is created.

Routing happens **once, at ticket creation**. After that, anyone with permission can reassign manually. Rules don't reroute tickets that already exist when you change the rules.

Find it at **Settings > Routing** (`/dashboard/settings/routing`).

## How rules evaluate

Rules are an **ordered list**. The first rule whose conditions match wins. Halo applies that rule's actions and stops looking. Anything that doesn't match any rule falls through to the implicit catch-all: the **default inbox** with no special priority, category, or assignee.

This means:

* **Order matters.** Move more specific rules above more general ones.
* **There's no "and also."** Two rules can't both match. The second one is ignored.
* **The catch-all is built in.** You don't need a rule like "everything else → default inbox", that's how the system already behaves.

You sort rules with the up and down arrows in the list. The numbered badge on each rule reflects its current priority.

## Anatomy of a rule

Each rule has four parts:

| Part           | What it does                                                                                                   |
| -------------- | -------------------------------------------------------------------------------------------------------------- |
| **Triggers**   | Which events run this rule. Today the only trigger is `ticket.created`, more are coming.                       |
| **Conditions** | A filter expression that decides whether the rule applies. Same builder as audience filters elsewhere in Halo. |
| **Actions**    | What to set on the ticket when the rule matches. Up to four actions per rule.                                  |
| **Enabled**    | An on/off switch so you can keep a rule around without applying it.                                            |

### Conditions

Conditions are built with the standard filter bar. You can mix:

* **Ticket fields**: `status`, `email_subject`, `email_body`, `source` (`email` / `widget` / `slack` / etc.), `priority`, `auto_created`, and any custom field on the ticket via `custom:<key>`.
* **End user fields**: `name`, `email`, `plan`, `last_seen`, `mrr`, `arr`, and any user trait via `custom:<key>`.
* **Company fields**: `company.name`, `company.domain`, `company.industry`, and any company trait.
* **Stripe fields** (when Stripe is connected): `stripe:subscription_status`, `stripe:mrr`, `stripe:plan`, etc.
* **Lead fields** (for unidentified visitors): `lead:lead_status`, `lead:lead_source`.

Within a rule, you can group conditions with **AND** / **OR** for things like *"plan is Enterprise OR mrr is greater than 1000"*.

### Actions

When a rule matches, any of these can be set:

| Action                 | Effect                                                                                                  |
| ---------------------- | ------------------------------------------------------------------------------------------------------- |
| **Assign to inbox**    | Moves the ticket to the specified inbox. The default inbox is the catch-all if no rule matches.         |
| **Set priority**       | `Low`, `Medium`, `High`, or `Urgent`. Used in inbox filters and on the ticket badge.                    |
| **Set category**       | A free-form tag (e.g. `billing`, `technical`, `feature_request`). Useful for analytics and saved views. |
| **Assign to teammate** | Pre-assigns the ticket to a specific teammate. They'll see it in their personal queue.                  |

You can leave any action blank, only the ones you set are applied. The other fields fall back to defaults.

## The dry-run preview

When you're editing a rule, the right-hand panel shows a **live preview** computed against the last 30 days of tickets:

> **42** tickets in the last 30 days would have matched this rule.
> **6** would have been routed by an earlier rule first.
> **36** would actually be routed by this rule.

This is the most important UX in the whole settings page. It tells you, before you save:

* How many tickets your rule actually catches.
* How many it doesn't catch because something earlier matched first (so you can move the rule up if needed).
* Whether the rule is too broad or too narrow.

Tweak conditions or move the rule up and the numbers refresh on every change. If the preview hits a hard cap, you'll see a "(truncated)" note, which means we sampled instead of scanning the whole window.

## A practical example

Say you're a support team that wants:

1. Enterprise customers always go to the **VIP** inbox at **Urgent** priority.
2. Anything mentioning "refund" or "cancel" goes to the **Billing** inbox.
3. Anything from the widget on a logged-in Pro user goes to the **Pro Support** inbox at **High** priority.
4. Everything else stays in the default inbox.

Your rules, top to bottom:

| # | Name                 | Conditions                                                                                               | Actions                            |
| - | -------------------- | -------------------------------------------------------------------------------------------------------- | ---------------------------------- |
| 1 | Enterprise customers | `plan` is `enterprise`                                                                                   | Inbox: VIP, Priority: Urgent       |
| 2 | Billing keywords     | `email_subject` contains `refund` OR `email_subject` contains `cancel` OR `email_body` contains `refund` | Inbox: Billing                     |
| 3 | Pro widget tickets   | `source` is `widget` AND `plan` is `pro`                                                                 | Inbox: Pro Support, Priority: High |

Rule 4 is unnecessary because the catch-all already routes everything else to the default inbox.

If an Enterprise customer asks for a refund, **Rule 1 wins** because it's first. That's usually what you want, the VIP inbox sees the ticket and a Billing person can be CC'd. If you'd rather refunds always go to Billing regardless of plan, move Rule 2 above Rule 1.

## Common patterns

<AccordionGroup>
  <Accordion title="Route by plan tier">
    Conditions: `plan` is `enterprise`. Actions: assign to a "VIP" inbox, priority `Urgent`. Add similar rules for Pro, Free, etc.
  </Accordion>

  <Accordion title="Route by topic from the email subject">
    Conditions: `email_subject` contains `refund`. Actions: inbox `Billing`. Stack a few of these for `password`, `import`, `integration`, etc.
  </Accordion>

  <Accordion title="Route by Stripe MRR">
    Conditions: `stripe:mrr` greater than `500`. Actions: inbox `High Value`, priority `High`. Useful when "Enterprise" isn't a clean enum on your end.
  </Accordion>

  <Accordion title="Route by source">
    Conditions: `source` is `slack`. Actions: assign to the teammate who owns Slack support. Same idea for `widget`, `email`, or `voice`.
  </Accordion>

  <Accordion title="Auto-prioritize low-engagement leads">
    Conditions: `auto_created` is `true` AND `lead:lead_source` is `pricing_page`. Actions: priority `High`, assign to a sales-friendly inbox.
  </Accordion>

  <Accordion title="Round-robin to a team">
    Today, "assign to teammate" picks one specific person. For round-robin, leave the assignee blank and let the inbox handle it via [Auto-assign on reply](/settings/inbox-views) instead.
  </Accordion>
</AccordionGroup>

## Tips

* **Start broad, then refine.** Get one or two big rules right (Enterprise, Billing) before micro-managing every category. Most of your tickets will fall through the catch-all and that's fine on day one.
* **Watch the dry-run.** If a rule says "0 would match", check the conditions, you probably typed a value that doesn't exist on tickets.
* **Disable, don't delete.** Toggle a rule **off** with the switch when experimenting. Easier to roll back than recreating it.
* **Read top to bottom.** When you're triaging an unexpected route, walk the list from rule 1 down. The first match wins.
* **Use "Test rule" before saving big changes.** Editing a high-priority rule with a broken condition can dump everything into the wrong inbox. The preview is your safety net.

## Permissions

| Permission      | What it lets you do                                      |
| --------------- | -------------------------------------------------------- |
| `settings:view` | See the routing page and all rules.                      |
| `settings:edit` | Create, edit, reorder, enable/disable, and delete rules. |

Routing rules are workspace-scoped: every rule applies to every ticket in the workspace, regardless of inbox or agent.

## Where to go next

<CardGroup cols={2}>
  <Card title="Inbox Views" icon="inbox" href="/settings/inbox-views">
    Configure the default inbox, auto-assign on reply, and saved views.
  </Card>

  <Card title="Escalation" icon="up-right-from-square" href="/agents/escalation">
    How AI agents decide to escalate to a human in the first place.
  </Card>

  <Card title="Health Scores" icon="heart-pulse" href="/contacts/health-scores">
    Use health score in your routing conditions for at-risk customers.
  </Card>

  <Card title="Tickets" icon="ticket" href="/inbox/working-tickets">
    What teammates see once a ticket is routed.
  </Card>
</CardGroup>
