Skip to main content

Ticket-automation rules — author, dry-run, and roll out

Ticket automation turns the triage questions every agent used to answer by hand — is this billing, is it stale, who owns it — into a rule you author once and the queue applies forever. This guide walks one rule from the grammar catalog all the way through a dry-run to a rollout checklist, and along the way tells you how the builder stays in sync with the backend’s vocabulary instead of hard-coding it. Everything here is tenant-owned configuration. Authoring, dry-running, and managing rules requires an owner or admin role; reading the grammar catalog is open to any authenticated operator. Nothing a rule does ever reaches the customer’s phone — the notify action is restricted to Slack, in-app, and webhook destinations by the schema itself, so automation can never become an outbound-messaging path.

1. Mental model — one trigger, some conditions, a few actions

A rule subscribes to exactly one trigger, filters with conditions, and runs a handful of actions when they hold: Two knobs control how a rule composes with its neighbors. match is all (AND) or any (OR) over the condition set. priority orders evaluation — lower runs first — and a matching rule with stop_processing: true ends that event’s pass, so a decisive first-match rule is final. The product page explains the per-action behaviour (assign skips an agent already at their channel cap, tags land with canonical casing, priority changes re-scale SLA due windows, escalation combines priority + assignee in one action). The rest of this guide is the doing.

2. Where the builder lives

Inbox → Settings → Ticket automation, RoleGuard’d to owner/admin to match the backend gate on the test and rules endpoints. The page has two halves — the rule editor on top (trigger, conditions, actions) and a dry-run panel below (sample ticket + event) — and is deliberately catalog-driven: the trigger selector, the per-field operator options, and the value dropdowns all render from the backend’s catalog, not from strings bundled into the frontend.

3. The API surface the builder consumes

Two endpoints power the builder, and a rules CRUD set persists what passes the test. The full request/response schemas live in the Inbox API reference; what a guide needs is the contract: The catalog is the answer to the enum-drift problem, and it is the pattern this guide recommends you copy: the page requests the grammar once, renders form options from it, and therefore cannot hard-code a stale list the backend later changes.

3.1 Persisted-rule execution, in milestones

The persisted side is deliberately split into milestones, and the workspace page header names the split: the first milestone owns the builder + preview surface, the follow-up owns durable storage and the execution path. Respect that ordering when you roll rules out — the dry-run contract is stable, so author and validate first, and treat persistence as the thing you enable only once the rule’s preview consistently matches your intent. Also note which triggers the current synchronous path fires: ticket_created, ticket_status_changed, and ticket_priority_changed emit from the ticket mutation service; ticket_tagged and ticket_idle enter through the same rule grammar but are scheduled-event arms — the ticket_idle arm is the one a future sweep closes. The rule mechanics below work identically either way, because the engine is the same pure core the preview endpoint and the executor share.

4. Worked end-to-end — the billing-escalation rule

The target: when a ticket tagged billing-issue goes idle for over two hours, retag it as escalated and notify the supervisor-code over Slack. We author it against the catalog, dry-run it against a sample ticket, then save it as a persisted rule.

4.1 Authoring against the catalog

The builder’s first move for every rule is the same five-step checklist:
  1. Pick the triggerticket_idle. The picker renders from catalog.triggers returned by GET /ticket-automation/catalog, so it names exactly the five lifecycle events above.
  2. Add the tag condition — field tags, operator contains, value "billing-issue".
  3. Add the time gate — field idle_minutes, operator gte, value 120. The catalog reports idle_minutes with only gte/lte in its operator list, which is how the builder narrows the picker for this field instead of offering every operator everywhere.
  4. Pick match: all — both conditions must hold. Distinct from any, which needs only one to hold and would fire the escalation on any tag-less idle ticket.
  5. Wire the actionsadd_tag with tag: "escalated", then notify with channel: "slack" and target: "#billing-supervisor". The dropdowns render from catalog.action_types plus the closed priorities/statuses/types lists the catalog also returns.
Up to ten actions per rule; keep this rule to two so the audit line stays readable.

4.2 Dry-run the rule

Before saving anything, dry-run the inline rule against a sample ticket whose last-touch timestamp we fake with idle_minutes: 150:
A trimmed response:
Three fields to read off every preview:
  • matched — did the rule’s conditions hold on that sample.
  • actions — the fire list in evaluation order.
  • effect — the resulting ticket patch plus the notifications the actions would emit. The patch is folded so only fields that actually change show up — a no-op add_tag on an already-tagged ticket contributes nothing.
Flip the sample’s idle_minutes to 60 and the call returns matched: false with an empty actions and effect — the dry-run is symmetric, so use it to probe both sides of the gate.

4.3 Save the rule

Once the preview says yes, persist the rule with POST /api/v1/inbox/ticket-automation/rules — the body is exactly the rule object above, unchanged:
stop_processing: false is the polite default — leave evaluation open so the generic triage rules after this one still run. Flip it to true only when a rule’s first-match should be authoritative (a VIP fast-track, for example).

5. Reading catalog enums — the no-hardcoding pattern

The catalog endpoint exists so the builder does not need its own copy of the backend’s vocabulary. The mental model:
  • Fields drive operator pickers. A field’s operators array is the closed set for that field — idle_minutes lists only gte/lte, the enum fields list eq through is_not_empty without contains, and so on. Render the picker from the field record, not from the global operator list.
  • Closed value lists ride with the catalog. priorities, statuses, types ship as arrays on the top level, and the per-field records repeat the same closed lists (so a priority condition can render its value dropdown straight from its values entry).
  • Triggers are the picker for the rule’s binding event. Rendered from catalog.triggers.
  • Action chips render from catalog.action_types. The notify action’s channel dropdown is the schema-narrowed slack | in_app | webhook triple, and that narrowing is the compliance gate — automation never becomes an outbound-messaging path.
A builder that hard-coded these would drift the first time the backend added a trigger or renamed an operator. Fetch the catalog at page load, cache it for the session (it is static grammar, not live state), and render every picker from it — that is how the page can truthfully say “the catalog keeps it in sync with the BE.”

6. Rollout checklist — author to live

  1. Start from the catalog. Load GET /ticket-automation/catalog once per session; your pickers should match its grammar without any manual enum list.
  2. Draft the rule. One trigger, conditions on the fields that actually distinguish the tickets you care about, and at most a few narrowly-scoped actions.
  3. Dry-run the positive case. Send a sample ticket that should match to POST /ticket-automation/test; confirm matched: true and the actions and effect you intend.
  4. Dry-run the negative case(s). Flip one input — drop the tag, lower the idle minutes — and confirm matched: false. If the negative case matches, tighten conditions before persisting anything.
  5. Save the rule. POST /ticket-automation/rules. Give the rule a verb-first name (“Billing idle → escalate + notify”) so the timeline entry and the audit row read plainly.
  6. Test the persisted path. Fire a real ticket-create / status-change / priority-change event carrying the trigger your rule subscribes to, then read the ticket timeline: the applied rule shows up as a system-authored note plus the audit row.
  7. Iterate conservatively. PATCH /ticket-automation/rules/:id with the same body shape; dry-run the revision before you replace the live one. When a rule is genuinely done, DELETE removes it — there is no soft-disable path beyond the enabled: false flag.
  8. Treat execution as a milestone. The persist + execution half of the split is announced in the page header as the follow-up milestone; until it lands on your workspace, the builder + dry-run contract above is the shipped surface and the rules you save are validated but not yet firing.
Expect the second milestone to light up ticket_idle schedule-driven runs — the synchronous triggers (ticket_created, ticket_status_changed, ticket_priority_changed) already fire; the idle and tag arms are grammar-complete where the executor is trigger-agnostic.

See also

  • Ticket automation — event-driven ticket rules — the product page for the same engine: action behaviour, delivery guarantees, and how automation composes with SLA escalation policies and reply approvals.
  • Inbox tickets workflow — tickets, the queue SLA clocks they pick up at open time, and the ticket_created trigger they raise.
  • Inbox SLA timers — the conversation-level clocks whose due windows a priority-changing rule re-scales; a nice pairing for a set_priority automation.
  • Webhook events — subscribe to the ticket.automation_applied event the executor emits on every applied rule.
  • Inbox API reference — full request/response schemas for the endpoints above (catalog, test, rules CRUD).