Automation rules
Automation rules let a project react to ticket events — or run on a clock — without anyone watching the board. Each rule pairs one trigger or one schedule with an optional set of conditions and one or more actions: escalate bugs that are due soon, label everything a customer comments on, or post a canned reply when a ticket is created. Rules are configured per project, with a separate set of global rules that apply across every project.
Where to find it
- Project rules —
/projects/:key/automationin the app (breadcrumb: project → Settings → Automation rules). Managing them requires the project admin role on that project; org-wide administrators pass automatically. - Global rules —
/admin/automation("Global automation"). This page requires the org ADMIN role. - API — project rules live under
/api/v1/projects/{projectKey}/automation-rules; global rules under/api/v1/admin/automation-rules.
Anatomy of a rule
The builder on the automation page has four parts: a name (required, at most 120 characters), a When selector, an If section of conditions, and a Then section of actions. The When selector offers the two schedules (*Every hour (scheduled)*, *Every day (scheduled)*) followed by the six event triggers.
- Exactly one of an event trigger or a schedule — the server rejects a rule with both or neither:
A rule needs exactly one of: an event trigger or a schedule. - Conditions are optional and are ANDed: the builder labels them "(optional — all must match)". There is no OR; create a second rule instead.
- At least one action is required (
A rule needs at least one action). Actions run in the order listed.
New rules are created enabled. Each rule row has *Disable*/*Enable* and *Delete* controls; a disabled rule is skipped entirely (event rules do not fire, scheduled rules are not claimed).
Triggers
An event-triggered rule runs after the triggering change commits, in its own transaction — a failing rule can never roll back or block the user's edit. The six triggers are the values of the TicketChangeKind enum:
| Trigger | Builder label | Fires when |
|---|---|---|
| CREATED | Ticket created | A ticket is created — by a user, the customer portal, email intake or a recurring template. |
| UPDATED | Ticket updated | A ticket's fields are edited. |
| STATUS_CHANGED | Status changed | A ticket is transitioned to another workflow status. |
| ASSIGNED | Assignee changed | The assignee is changed. |
| COMMENTED | Comment added | A comment is added — including customer portal replies and inbound email replies. |
| LABEL_ADDED | Label added | A label is attached by a user (set-labels or CSV attach). |
Loop-proofing. Rule actions mutate tickets directly and never re-publish a ticket-changed event, so automation cannot trigger itself: an ADD_COMMENT action does not fire COMMENTED rules, an automation ADD_LABEL does not fire LABEL_ADDED, and so on. Chained or cyclic rules are impossible by construction.
Scheduled rules
Pick *Every hour (scheduled)* or *Every day (scheduled)* instead of a trigger to make a clock-driven rule (schedule = HOURLY or DAILY). The backend checks for due rules every 15 seconds; each run is claimed with a compare-and-swap on the rule's next-run timestamp, so in a multi-node cluster exactly one backend executes it, and the next slot is set one hour or one day from the moment the run is claimed — a freshly created hourly rule first fires roughly an hour after creation. A scheduled run sweeps the project's tickets: it takes the first 200 tickets (in board order — rank, then ticket number), applies the conditions to each, and runs the actions on every match, then records one summary row in the run history, e.g. scheduled sweep: changed 3 of 7 matching tickets.
The 200-ticket sweep budget is a hard cap. In a project with more than 200 tickets, tickets beyond the first 200 in board order are not examined on that run. A global scheduled rule shares a single 200-ticket budget across *all* projects, consumed project by project — with many projects, later projects may not be swept at all. Prefer event triggers where possible, and keep scheduled rules for time-based conditions such as approaching due dates.
Conditions
Each condition names a ticket field and an expected value. All five fields are available to project rules; global rules may use only TYPE, PRIORITY and DUE_WITHIN (see below).
| Field | Value | Matches when |
|---|---|---|
| TYPE | Type name or code (case-insensitive), e.g. BUG | The ticket's type matches — custom, SAFe and sub-task types included; the builder lists the project's configured types. |
| PRIORITY | One of HIGHEST, HIGH, MEDIUM, LOW, LOWEST | The ticket has that priority. |
| STATUS | Workflow status id (the builder shows status names) | The ticket is currently in that status. |
| LABEL | Label id (the builder shows label names) | The ticket carries that label. |
| DUE_WITHIN | Whole number of days N (builder input: 0–365) | The ticket has a due date on or before today + N days. |
DUE_WITHIN deserves care: overdue tickets match too (the test is "due date not after today + N"), 0 means "due today or already overdue", and a ticket without a due date never matches. Combined with a daily schedule it makes a reliable escalation rule.
Actions
Actions are deliberately conservative: an action that would change nothing, or whose target no longer exists, is a silent no-op rather than an error, and if one action fails the remaining actions of the rule still run. Applied actions are written to the ticket's change history, attributed to the user who created the rule — except ADD_LABEL, which attaches the label without a history entry.
| Action | Builder label | Value | Effect / no-op cases |
|---|---|---|---|
| ASSIGN | Assign to | User id (builder lists project members) | Sets the assignee. No-op if already assigned to that user or the user no longer exists. |
| SET_PRIORITY | Set priority | HIGHEST … LOWEST | Sets the priority. No-op if unchanged. |
| TRANSITION | Transition to | Workflow status id | Moves the ticket — only if the workflow allows that transition from the current status for the ticket's type; otherwise no-op. |
| ADD_LABEL | Add label | Label id | Attaches the label. No-op if already present or the label belongs to another project. |
| ADD_COMMENT | Add comment | Free text (smart values supported) | Posts a comment — internal by default, optionally customer-visible. |
| ADD_CANNED_COMMENT | Add canned comment | Canned response id | Posts the body of a canned-response template as a comment; same visibility option. No-op if the template was deleted or belongs to another project. |
Automated comments and customer visibility
Comments posted by ADD_COMMENT and ADD_CANNED_COMMENT default to internal notes — customers never see them. Ticking the visible to customer checkbox on the action makes the comment a public reply instead; for tickets raised through the customer portal or email intake, a public automated reply is also emailed to the requester. The comment is authored by the rule's creator, so it reads like any other reply. Canned-comment templates come from the project's canned responses (managed in project settings) — pointing a rule at a template keeps the wording in one place: edit the response and every rule that posts it picks up the new text.
Smart values
Comment bodies may embed {{token}} placeholders resolved from the triggering ticket at post time. Token names are case-insensitive and tolerate surrounding spaces ({{ issue.key }}). A known token with no value renders empty; an unknown token is left as literal text so typos stay visible instead of silently vanishing. A comment that renders to nothing but whitespace is not posted at all.
| Token | Renders as |
|---|---|
{{issue.key}} | Ticket key, e.g. PROJ-42 |
{{issue.title}} / {{issue.summary}} | Ticket title |
{{issue.priority}} | Priority name, e.g. HIGH |
{{issue.status}} | Current workflow status name |
{{issue.assignee}} | Assignee display name, or Unassigned |
{{issue.type}} | Ticket type name |
{{issue.dueDate}} | Due date (YYYY-MM-DD), empty if none |
{{now}} | Today's date |
{{now.plusDays(N)}} / {{now.minusDays(N)}} | Today shifted by N days (N up to 4 digits) |
{{issue.key}} "{{issue.title}}" is due {{issue.dueDate}}.
Escalated to {{issue.priority}} on {{now}}; follow up by {{now.plusDays(3)}}.Run history
The Recent runs section at the bottom of the project automation page (with a *Refresh* control) shows the last 50 executions, newest first; the platform retains the newest 200 runs per project, pruning older rows automatically as new runs are logged. Each row records the rule name, the ticket, the result and a timestamp; ticket keys link straight to the ticket. Global-rule runs are logged under each affected project, so they appear here too.
| Result | Shown as | Meaning |
|---|---|---|
| APPLIED | applied | Conditions matched and at least one action changed the ticket. |
| SKIPPED | matched, no change | Conditions matched but every action was a no-op (e.g. priority already set). |
| FAILED | failed | The rule threw; the error message is shown next to the rule name (truncated to 500 characters). |
Event-triggered runs are logged per matched ticket. A scheduled sweep logs one row with * in the ticket column and a summary message such as scheduled sweep: changed 2 of 5 matching tickets (global rules: global sweep: …). An event rule whose conditions do not match logs nothing; a project scheduled sweep records its summary row even when no ticket matched, while a global sweep logs only in projects with at least one match.
Project rules vs global rules
Global rules run in every project but are restricted to project-agnostic vocabulary — nothing that names a per-project object. The server enforces this on save and rejects, for example, a STATUS condition with Global rules cannot use the STATUS condition (project-scoped).
| Project rules | Global rules | |
|---|---|---|
| Managed at | /projects/:key/automation | /admin/automation |
| Required role | Project admin (or org ADMIN) | Org ADMIN |
| Conditions | All five | TYPE, PRIORITY, DUE_WITHIN only |
| Actions | All six | ASSIGN, SET_PRIORITY, ADD_COMMENT only |
| Scheduled sweep budget | 200 tickets in that project | 200 tickets shared across all projects |
| Runs appear | On the project's runs list | On each affected project's runs list |
API
| Method & path | Purpose |
|---|---|
GET /api/v1/projects/{projectKey}/automation-rules | List the project's rules |
POST /api/v1/projects/{projectKey}/automation-rules | Create a rule (returns 201) |
PUT /api/v1/projects/{projectKey}/automation-rules/{ruleId} | Update a rule (also used to enable/disable) |
DELETE /api/v1/projects/{projectKey}/automation-rules/{ruleId} | Delete a rule (returns 204) |
GET /api/v1/projects/{projectKey}/automation-rules/runs | Last 50 runs for the project |
GET|POST|PUT|DELETE /api/v1/admin/automation-rules[/{ruleId}] | Same operations for global rules |
{
"name": "Escalate bugs due this week",
"enabled": true,
"trigger": null,
"schedule": "DAILY",
"conditions": [
{ "field": "TYPE", "value": "BUG" },
{ "field": "DUE_WITHIN", "value": "7" }
],
"actions": [
{ "type": "SET_PRIORITY", "value": "HIGHEST" },
{ "type": "ADD_COMMENT",
"value": "{{issue.key}} is due {{issue.dueDate}} — escalated on {{now}}.",
"customerVisible": false }
]
}For an event-triggered rule, set trigger to one of CREATED, UPDATED, STATUS_CHANGED, ASSIGNED, COMMENTED or LABEL_ADDED and omit schedule (or send null). customerVisible applies to ADD_COMMENT and ADD_CANNED_COMMENT and defaults to false; rules saved before the flag existed are treated as internal.
Auditing rule changes
Every create, update and delete of an automation rule is written to the configuration audit log under the AUTOMATION_RULE category, recording who made the change, when, the action (CREATE, UPDATE or DELETE) and the rule's id and name — enabling or disabling a rule counts as an UPDATE. Org administrators can review these entries at /admin/config-audit (API: GET /api/v1/admin/config-audit). The audit records the fact of the change, not a before/after diff.