32 · Helpdesk rules
The rules engine automates ticket workflows. A rule has three parts:
| Part | What it is |
|---|---|
| Trigger | When the rule is evaluated |
| Conditions | Filters that must all match (AND) |
| Actions | What happens when conditions are true |
Rules are tenant-scoped, ordered, and toggleable. Build them at
/admin/helpdesk/rules.
Triggers
| Trigger | Fires |
|---|---|
| Ticket created | Once per new ticket. Routing, auto-assign, welcome reply. |
| Ticket updated | Whenever any field changes. changedFrom / changedTo available in the rule context. |
| Comment added | On each comment. commentIsInternal lets you branch on internal vs public. |
| Schedule (every 5 min) | Walks every open ticket every 5 minutes. The path for "remind after N days" / "auto-close N hours after resolved" — no event needed, just time elapsed. |
Conditions
The condition picker exposes these fields:
- Status / Priority / Source — enum, with operators is / is
not / is one of (comma-separated). Status options are
OPEN / IN_PROGRESS / RESOLVED / CLOSED; Source options areEMAIL / PORTAL / AGENT. - Assignee ID / Customer ID / Contact email / Ticket type ID / Title / Description / Has tag (id) — string, with operators is / is not / contains / is set.
- Is unassigned / Is merged dupe / SLA breached / Comment is internal — boolean.
- Time since created / updated / resolved / customer reply / any reply — duration, with operators is more than / is at least / is less than / is at most. Entered as days + hours + minutes; stored internally as minutes.
The builder UI exposes the fields above. The engine additionally
understands changedFrom / changedTo references on the
TICKET_UPDATED trigger, though there is no dedicated picker row for
them.
Conditions combine with AND. Leave empty to fire unconditionally on the trigger.
Actions
- Set status / Set priority / Set assignee / Set ticket type — direct field writes
- Add tag / Remove tag — auto-creates the tag if missing
- Add comment — internal or public, supports
{{ticket.title}}/{{ticket.id}}/{{ticket.contactEmail}}placeholders - Send email — via SMTP, recipient =
customer/assignee/ a literal address; subject + body templated - Stop further rules — halts the chain (Outlook-style)
Actions run in order. A SET_STATUS = RESOLVED followed by SET_PRIORITY = LOW reads the new status when the priority action runs.
Quick-start templates
/admin/helpdesk/rules opens a 📋 Quick-start templates drawer with:
| Template | What it does |
|---|---|
| 📧 Remind customer after 3 days of silence | Sends a templated email when minutesSinceLastCustomerReply > 4320. |
| 🔒 Auto-close 48 hours after RESOLVED | Flips RESOLVED tickets to CLOSED once minutesSinceResolved > 2880. |
| 🚨 Tag URGENT-priority tickets on creation | Adds an urgent tag for queue filtering. |
| ⚠️ Internal note when ticket sits unassigned > 2 hours | Adds a triage-delay note. |
Click a template to open the form pre-filled — edit before saving.
Builder UI
The form has three sections:
- Name & description — what to call the rule.
- Trigger picker — one of the four triggers above. The hint under the dropdown describes the trigger semantics.
- Conditions builder — repeat-add typed rows. Each row has a field dropdown, operator dropdown (filtered to operators the field's kind supports), and a value input that adapts to the field type (enum dropdown, days+hours+minutes spinner, etc.).
- Actions builder — repeat-add typed rows. Each action shows
only the params it actually accepts (e.g.
Set statusshows just a status dropdown;Send emailshows recipient + subject + body).
📷 Screenshot placeholder: screenshots/admin-helpdesk-rules.png
Run history
Every rule evaluation is recorded in a per-rule run log (the
helpdesk_rule_runs table). The rule card also shows a lifetime
fired N times counter and the last-run timestamp. Click ▾
Recent runs on a rule to see the last 100 entries:
| Column | Meaning |
|---|---|
| When | Timestamp of evaluation |
| Status | MATCHED (conditions true, actions ran) / SKIPPED (conditions false) / ERROR |
| Ticket | Link to the ticket the rule fired on |
| Notes | JSON of which actions ran or which condition failed |
Engine internals
- Conditions and actions are stored as JSON-as-TEXT on the
helpdesk_rulesrow. Adding a new comparator or action type means appending to the engine'sFIELD_META/ action switch — no schema change. - The scheduled tick runs every 5 minutes from
helpdesk-service(HELPDESK_RULES_TICK_MSenv override available). - Event triggers (CREATED / UPDATED / COMMENT_ADDED) are fired
non-blocking from
tickets.service.ts— a misconfigured rule can't fail a ticket write. - Cycle protection isn't currently needed: the engine doesn't automatically re-trigger UPDATED on its own writes (Prisma write doesn't loop back through the helpdesk service's mutators).
⚠️ Caution. Send email recipient customer resolves to
ticket.contactEmail, falling back to ticket.contact.email. If
both are null the action errors and is logged — no silent drops.
Permissions
| Action | Role |
|---|---|
| View rules + run history | any helpdesk user |
| Create / edit / delete | admin |