Key takeaways
- Routing consumes triage fields; it should not reinterpret the customer request. That boundary keeps assignment failures diagnosable.
- Filter for eligibility before ranking: region, permissions, language and product access are hard constraints; skill fit and capacity rank eligible owners.
- Assignment needs a time-bounded ownership lease and an acknowledgement event. Setting an assignee field is not evidence that someone accepted the work.
- Every fallback needs a timer, destination and reason code. Silently relaxing a required skill turns a queue delay into a quality problem.
- Measure reroutes, unacknowledged leases and queue age by route rule; average handle time cannot tell you whether routing works.
Routing is an ownership contract, not an assignee-field update
The trigger is a triage-completed event or an explicit reroute event. Inputs are the ticket ID, stable category, language, region, product, priority, entitlement, required permissions, current SLA deadline, candidate rosters, agent status, declared skills and capacity. Outputs are the chosen queue and person, route-rule version, reasons, ownership lease, acknowledgement state and any fallback event.
The systems are the help desk, identity/roster source, workforce status source and event log. Actors are the routing service, support lead and accepting agent. The process runs at ticket arrival and again only on a defined event such as rejection, shift end or lease expiry. Success means every ticket has one accountable owner, no ineligible person receives restricted work, and reroutes have an intelligible cause.
| Layer | Examples | Treatment |
|---|---|---|
| Eligibility | Region, tenant access, language requirement, product permission | Hard filter; never relaxed silently |
| Skill fit | Billing-trained, API specialist, enterprise support | Required or optional, explicitly configured |
| Availability | Online state, shift, planned absence | Point-in-time filter with freshness limit |
| Capacity | Open weighted work, concurrent chats, protected reserve | Rank or cap among eligible candidates |
| Continuity | Prior owner or active related incident | Tie-breaker unless policy makes it required |
The manual queue hides three different routing decisions
A coordinator usually scans the category, remembers who knows the product, glances at workload and assigns the ticket. When the specialist is away, it sits until someone notices. When the first owner rejects it, the ticket returns to the front of the same queue without preserving why. This looks flexible, but the real rules live in one person's memory and cannot be evaluated.
Separate the work into eligibility, ranking and acceptance. Ask which constraints may never be broken, which improve the match, how current capacity data must be, how long an owner gets to acknowledge, and what happens when nobody qualifies. Historical assignee data is not automatically the correct training label: it includes convenience assignments and organizational accidents.
A route moves through candidate set, lease and acknowledgement
- 01Receive a stable triage recordTriggerReject events without the required category, priority, language and policy version. Routing never guesses a missing triage field.
- 02Build the eligible candidate setCodeIntersect roster, permissions, required skills, region and current availability. Record every eliminated candidate by rule class, without exposing private roster data to the ticket.
- 03Read capacity onceCodeTake a timestamped snapshot of weighted open work and channel concurrency. If it is stale, route to a queue rather than pretend the number is current.
- 04Rank eligible candidatesCodeApply the published order: mandatory fit, continuity, capacity, oldest-last-assignment and deterministic tie-breaker. A model is unnecessary for arithmetic over trusted fields.
- 05Create an ownership leaseCodeAtomically assign one owner with route ID, created time and acknowledgement deadline. Compare-and-set prevents two workers from assigning the same ticket.
- 06Acknowledge or rejectHuman approvalThe agent accepts responsibility or chooses a bounded rejection reason such as wrong permission, stale skill or ending shift. Free-text rejection accompanies rather than replaces the code.
- 07Run the fallback timerCodeOn expiry, re-read ticket state. If still unacknowledged, invalidate the lease and apply the configured fallback—next eligible owner, duty queue or manager.
- 08Close the route eventCodeWrite the accepted owner, elapsed time, candidate-set hash, rule version and any fallback history. SLA monitoring now has a real owner to notify.
Fallbacks must say which promise they relax
| Condition | Fallback | Evidence retained |
|---|---|---|
| Optional skill unavailable | Wait for configured timeout, then rank without that optional skill | Skill relaxed, timeout and candidate sets |
| Required skill unavailable | Named specialist queue or duty manager | No eligible candidate and missing skill |
| Capacity data stale | Assign queue ownership, not an individual | Snapshot age and data-source error |
| Lease unacknowledged | Invalidate and offer next candidate | Owner, deadline and notification receipts |
| Agent rejects | Reroute using rejection code; alert on repeated rejection | Reason and route attempts |
| Ticket materially changes | Return to triage before rerouting | Change event and prior route history |
Zendesk's current documentation describes routing that combines status, capacity, skills and priority, including configurable timeouts for optional skills. That is a useful implementation reference. Your contract still needs to define which skill is truly required, because a product setting cannot determine the consequence of relaxing it in your operation.
Worked sample: preserve language eligibility under load
Triage emits a P2 API-authentication ticket in German for an enterprise account. Five agents are online. Two lack enterprise access and are removed. One lacks German and is removed because language is required for this queue. The remaining two both have the API skill; agent M has weighted load 5 and agent K has load 3, so K receives a ten-minute lease.
K does not acknowledge before the deadline because their status feed was stale. The workflow checks that the ticket is still open, invalidates only that lease, records the timeout and offers it to M. It does not relax German or enterprise access. M accepts, and the route event closes with two attempts. The stale status feed becomes an operational alert rather than an invisible routing error.
Routing quality shows up before resolution time
Measure time to acknowledged ownership, percentage with no eligible candidate, lease-expiry rate, human rejection rate by code, reroutes within one hour, cross-queue hops, capacity imbalance and queue-age percentiles. Segment them by route-rule version and required skill. Resolution time is downstream of case difficulty and agent work; it cannot diagnose whether the initial assignment was sound.
Audit each route as append-only events: source triage version, candidate-set hash, hard filters applied, capacity snapshot time, rank result, write response, notification receipts, acknowledgement and fallback. Avoid storing unnecessary personnel details in the ticket itself. A support lead should be able to replay why the chosen owner was eligible with only the event record and versioned rules.
Ticket routing SOP
An operator procedure for eligibility, candidate ranking, lease acknowledgement, fallback and rerouting.
Preview the fileHide preview
# Ticket routing SOP
Version: 1.0
Owner: Support operations
Review cadence: Monthly and after roster, permission, skill, capacity, or queue changes
## Purpose
Assign one triaged ticket to one eligible owner and obtain acknowledgement. Routing consumes triage fields and must not reinterpret the request or relax a hard eligibility rule silently.
## Trigger and completion
- Start: `triage.completed`, `route.rejected`, `route.lease_expired`, or approved reroute event.
- Complete: an eligible owner acknowledges the lease, or a named fallback queue owns the exception.
- Idempotency key: `{ticket_id}:{triage_version}:{route_attempt}`.
## Required inputs
- Stable category, product, language, region, priority and entitlement
- Required permissions and required/optional skills
- Timestamped roster, availability and weighted capacity snapshot
- Current assignment state and SLA deadline
- Versioned ranking, lease and fallback policy
## Procedure
1. Reject incomplete or superseded triage records.
2. Build the roster from the authoritative source and remove candidates lacking permission, region, required language, required skill or current availability.
3. Record eliminated candidates by rule class without copying private roster detail into the ticket.
4. Read capacity once. If stale beyond policy, assign a queue rather than an individual.
5. Rank eligible candidates using published criteria: mandatory fit, continuity, capacity, oldest-last-assignment and deterministic tie-breaker.
6. Atomically create an assignment and time-bounded ownership lease.
7. Notify the selected owner with the acknowledgement deadline.
8. Owner accepts or rejects using a bounded reason code.
9. On expiry or rejection, re-read ticket and owner state, invalidate only the current lease and apply the configured fallback.
10. Close the route record after acknowledgement; expose the owner to SLA monitoring.
## Fallback policy
- Optional skill: wait for its configured timeout, record the relaxation, then rerank.
- Required skill: never relax automatically; send to specialist queue or duty lead.
- Stale capacity: queue ownership only.
- Missing acknowledgement: next eligible owner, then duty queue after attempt ceiling.
- Material ticket change: return to triage before rerouting.
- Repeated rejection: alert route-policy owner for taxonomy/roster review.
## Controls and measures
Use compare-and-set on assignment writes. Give every lease one active token. Track time to acknowledged ownership, no-eligible-candidate rate, lease expiry, rejection by code, reroutes within one hour, queue hops, capacity imbalance and queue-age percentiles by route-rule version.
## Audit record
Keep source triage version, candidate-set hash, hard filters, capacity snapshot time, rank result and reason, route-rule version, assignment response, lease, notifications, acknowledgement, rejection and fallbacks.
## Rollback
Disable individual assignment and route into transparent, staffed queues that preserve the same eligibility boundaries. Never roll back by sending restricted work to a general queue.
Ticket routing workflow blueprint
A build contract for route rules, ownership leases, concurrency, timeout handling, events and acceptance tests.
Preview the fileHide preview
# Ticket routing workflow blueprint ```yaml name: ticket-routing version: 1.0.0 trigger: events: [triage.completed, route.rejected, route.lease_expired, route.reroute_requested] input: required: [ticket_id, triage_version, category, language, region, priority, policy_version] output: required: [route_id, owner_or_queue, lease_state, candidate_set_hash, reasons] side_effects: allowed: [assign_ticket, create_lease, notify_owner, emit_route_event] forbidden: [change_triage, relax_required_permission, send_customer_reply] ``` ## States `received -> eligible_set_built -> capacity_read -> ranked -> lease_offered -> acknowledged` Branches: `no_eligible_candidate`, `capacity_stale`, `rejected`, `lease_expired`, `superseded`, `write_recovery`. ## Ranking contract 1. Hard-filter permission, region, required language, required skills and availability. 2. Reject capacity snapshots older than `capacity_max_age`. 3. Rank by continuity flag, skill score, ascending weighted load, oldest last assignment, stable agent ID. 4. Keep the complete rule version and candidate-set hash. ## Concurrency - Assignment uses compare-and-set against current ticket assignment version. - Only one active lease token exists per ticket. - A timeout handler must present the active token before invalidation. - Acknowledgement after invalidation is rejected and recorded. ## Events `route.requested`, `route.no_candidate`, `route.lease_created`, `route.notified`, `route.acknowledged`, `route.rejected`, `route.lease_expired`, `route.fallback_applied`, `route.failed`. ## Acceptance tests 1. Ineligible candidates never enter ranking. 2. Required skill is never silently relaxed. 3. Optional skill relaxes only after configured timeout and records why. 4. Stale capacity produces queue ownership. 5. Identical inputs produce deterministic tie-breaking. 6. Concurrent workers create one assignment. 7. Duplicate notification uses the same idempotency key. 8. Expired lease cannot be acknowledged. 9. Rejected lease records a bounded reason. 10. Material ticket change returns to triage. 11. No candidate sends to the named duty destination. 12. Reroute preserves prior route attempts. 13. Ticket closure cancels pending lease actions. 14. Audit replay explains eligibility and rank without private roster details.
Limitations and when not to use this
- Do not automate routing until triage fields and roster permissions have named owners and freshness expectations.
- Skills inferred from past assignments can reproduce stale or unfair workload patterns; use declared, reviewed eligibility data.
- Very small teams may be better served by a transparent shared queue and duty rotation than an elaborate individual-ranking system.
Sources
- About using skills to route tickets — Zendesk Documentation Accessed 2 August 2026
- Skill-based Routing API — Zendesk Developer Documentation Accessed 2 August 2026
- About system ticket rules — Zendesk Documentation Accessed 2 August 2026
Write the routing contract
Map one route from triage fields through eligibility, assignment, acknowledgement and fallback.
Write the routing contractUli Prantz
Builds and operates all-agents
Uli Prantz builds all-agents, the process-automation platform this site documents. He writes about the operational side of automating recurring business work: where deterministic code beats model judgment, where it does not, and where a human still has to approve.