Field Service & Trades// diagnostic

Jobs sit in the unassigned queue until a customer calls to ask

In short

Jobs go undispatched because the unassigned queue is a state with no clock on it: nothing ages, nothing escalates, and no single person is accountable for emptying it. The confirming check is a daily reading of count and oldest age split by job type, priority tier and business unit. A silent backlog shows up as an oldest-age figure that only ever climbs.

Key takeaways

  • Queue size is the wrong number. Oldest age by tier is the one that shows a silent backlog.
  • Auto-assign that finds no eligible technician must emit a reason, not return quietly.
  • A board renders a subset of statuses. Anything created outside that subset is invisible by design.
  • Parts holds and saved branch filters hide rows that are technically in the queue and practically gone.
  • An escalation addressed to a role is not an escalation. Put a name and a working-hours clock on it.

A job that never reaches a technician is almost never a job that failed loudly. It is a job that entered a state nobody watches, and stayed there because nothing in that state has a clock attached. The unassigned queue is where those jobs live, and in most operations it is the only object on the board with no ageing, no owner and no alert.

That is why the customer finds out first. When the only detector for an undispatched job is a phone call from the person waiting for it, you have outsourced monitoring to the least forgiving auditor available — the same structural mistake as trusting a notification you never confirmed was delivered, described in customers say the on-my-way text never arrived.

Read the queue by its oldest job, not by its size

Queue size is the number everyone reports and it is close to useless. A queue holding 40 jobs created this morning is healthy; a queue holding 6 jobs, one of which arrived 11 days ago, is a complaint that has not been made yet. Age is the diagnostic, and it has to be sampled rather than glanced at.

  1. Snapshot the queue twice daily, at 07:00 before the board is worked and at 16:00 after it should have been. One row per unassigned job: id, created timestamp, job type, priority tier, business unit, arrival channel.
  2. Compute 4 numbers per slice, not one: count, oldest age in hours, the 90th percentile age, and the number already past its tier's limit.
  3. Slice by priority tier first, then job type, then business unit. A blended figure hides the only case that matters — one obligation-driven job aged past its due date inside a pile of flexible ones.
  4. Add arrival and drain counts for the window. A queue that grows by 12 and drains 12 is stable at whatever depth it has; one draining 9 is compounding, and the oldest-age line says when customers will notice.
  5. Run it for 10 working days before changing anything. Two of those will be atypical, and a rule tuned on an atypical day is the rule everybody switches off in week three.
Pattern in the daily readingWhat it points at
Count flat, oldest age climbingThe queue is being worked front-to-back on newest-first. A fixed set of jobs is being skipped every day.
Count climbing, oldest age flatGenuine capacity or intake volume. The dispatch process works; there is more work than board.
Count and oldest age both flat, complaints risingJobs are outside the queue view entirely — wrong status, wrong business unit, or filtered out of the saved view.
Oldest age resets to zero without jobs being dispatchedSomebody is cancelling or re-creating aged rows. The backlog is being hidden rather than worked.
What each reading of the queue is actually telling you

Nobody's name is on the queue

The most common cause is also the least technical. Every other object in a service business has an owner — the job has a technician, the invoice has a controller, the callback has whoever promised it — and the unassigned queue has a team. A team is not an owner. When 3 dispatchers can all see the queue and none of them is accountable for its oldest row, the queue is worked opportunistically, which means newest-first, which means the difficult jobs at the bottom are permanently deferred.

The difficult ones share a profile, which is why the same jobs rot: a licence only 2 technicians hold, a site with restricted access hours, a customer already rescheduled twice, a job nobody can price. Each takes 15 minutes of thought rather than 15 seconds of drag-and-drop, so each loses to whatever arrived at 08:40.

The fix is boring and it works: one named person per business unit owns the queue for the day, works it oldest-first before anything else, and hands over an explicit list of what they could not place. That list is the input to every other fix here.

Auto-assign ran, found nobody, and said nothing

Where automatic assignment exists, the failure is rarely a crash. It evaluates a constraint set, finds zero eligible technicians, and returns an empty result the system treats as a non-event. No error is logged and the queue absorbs the job silently. An empty result is information, and discarding it is the most expensive line of code in most dispatch integrations.

So instrument the empty case. Every run that assigns nobody should write a row naming which constraint eliminated the last candidate:

  • No technician holds the required licence or certification, or the only one who does has it expiring before the scheduled date.
  • Every eligible technician is already committed for the requested window, so the constraint is capacity rather than skill.
  • The site falls outside every eligible technician's working geography, which is a service-area question wearing a dispatch costume.
  • The job carries a customer-preferred technician who is unavailable, and the rule treated a preference as a hard constraint.
  • Required parts are not on any eligible van, so the assignment engine correctly refused and incorrectly stayed quiet.

Those five reasons are the ranked backlog for whoever is improving dispatch, because they name which constraint actually binds. Showing a dispatcher a shortlist with the failing component visible, rather than a blank result, is the argument made in an assignment a dispatcher can argue with. Whether engine or dispatcher gets the final say is settled in auto-dispatch or dispatcher override; either answer still requires the engine to explain itself.

An assignment engine that finds no eligible technician has learned something. Discarding that result is how a constraint problem becomes a backlog nobody can explain.

The job exists in a status the board never draws

Dispatch boards render a subset of job statuses. Whatever platform you run — ServiceTitan, Jobber, Housecall Pro, Simpro or something in-house — somebody once chose which statuses appear in the unassigned view, and that choice is now years old and undocumented. Jobs created in any other status are not hidden by a bug. They are working exactly as configured, and configuration is invisible.

Three creation paths reliably produce jobs outside the rendered set. A won estimate converting to work in a needs-scheduling status of its own. A recurring agreement releasing visits into a future-dated status the day view filters out, which is how the tune-ups that were due never got booked. And a technician raising follow-up work from the field, which often lands in a status the office board was never told about.

The check takes an afternoon: count every job created in the last 30 days grouped by status, then compare that list against the statuses the unassigned view renders. Anything in the first list and not the second is a leak, and the count sizes it.

Parts holds and saved filters, hiding rows in plain sight

Two things make a job invisible while sitting in the queue you are looking at. The first is a job waiting on a part, parked in the same pile as jobs waiting on a decision. Those are different states with different clocks — one waits on a supplier, the other on a person — and merging them means aged parts jobs pad every count until nobody trusts the number. Giving that job its own state with a re-entry trigger is the argument of the job that should not be on the board yet.

The second is the saved filter. Multi-location operations set a default business unit per user, and a job booked by a central call centre into a unit nobody has open is technically in the queue and practically gone. It surfaces in no default view until someone runs an unfiltered report — a consequence of how the business unit is modelled, covered in the business unit as a data model decision.

An ageing limit per tier, and an escalation with a name on it

The prescription is a maximum age per priority tier, measured from job creation rather than from when somebody noticed, with a defined action at each breach. Tiers below are illustrative shapes rather than industry limits; set yours from your own promises.

TierMaximum time unassignedWhat happens at the limit
Emergency and no-heat15 minutesAlert the on-duty dispatcher by phone, not by email. If unplaced at 30 minutes, escalate to the operations manager by name.
Obligation-driven work with a due date1 working dayFlag against the due date, not the creation date. A test or inspection with a statutory deadline should escalate on days remaining.
Contracted response window4 working hoursAuto-escalate with the remaining response clock in the alert text, so the recipient sees urgency rather than a row id.
Routine and maintenance3 working daysDaily digest to the queue owner, oldest first, with a required disposition on anything over 5 days.
An ageing model for the unassigned queue, by tier

The obligation-driven tier deserves care, because its clock is not yours. Work carrying a legally mandated deadline — a periodic device test, an inspection, a certification retest — ages against that external date, and an unassigned job in that tier is a compliance problem before it is a scheduling one. The evidence trail those jobs must produce is a separate discipline, set out in before a backflow test report leaves the truck.

Two rules make the escalation real. It goes to a person, not a role inbox, with a fallback name for leave. And it demands a disposition rather than an acknowledgement: placed, deferred with a date, or escalated further. An alert that can be dismissed will be, and a queue whose alerts are dismissed is the queue you started with.

Mechanically this is small — a scheduled unfiltered query, an ageing calculation, a rules table and a notification path — sitting alongside the assignment engine rather than inside it, which is the shape of work we describe under AI agents and automation. If you are commissioning it, the questions to ask first are in choosing an AI development partner. It belongs with dispatch, scheduling and route sequencing, inside our field service work.

Frequently asked questions

Short answers to the follow-ups this page tends to raise.

Why do jobs stay in the unassigned queue without anyone noticing?

Because the queue is the only object on a dispatch board with no clock, no owner and no alert. Every other record has someone accountable for moving it; unassigned work has a team, which in practice means nobody. Add an age per job, an ageing limit per tier, and a named owner who works it oldest-first.

What should we measure to prove there is a backlog?

Oldest age by priority tier, sampled twice a day, not queue size. Size moves with intake volume and tells you nothing; a climbing oldest-age line with a flat count proves specific jobs are skipped every day. Record count, oldest age, the 90th percentile age and the number past tier limit, split by job type and business unit.

Why does auto-assign leave jobs unassigned instead of failing?

Because an empty candidate list is treated as a valid result rather than an event. The engine evaluates skills, licences, capacity and geography, finds nobody eligible, returns nothing, and the job stays put with no log line. Make the empty case emit the binding constraint — no licence, no capacity in window, outside service area — and the backlog becomes a ranked list of fixable causes.

How long should a job be allowed to sit unassigned?

Set the limit per tier from the promise you made, not from a generic figure. Emergency work should breach in minutes and escalate by phone; contracted-response work should breach well inside the response clock; routine work can tolerate days provided somebody dispositions it. What matters more than the limit is that breaching it reaches a named person who must respond with an outcome.

  • dispatch
  • backlog
  • queue design
  • escalation
// shipped work

The work behind this page

Builds from our portfolio that this page draws on.

Working on something in this space?

Tell us where you are in a sentence or two. We'll tell you honestly whether we're the right team, and what a sensible first slice of the work looks like.

Start the conversation