> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getthread.com/llms.txt
> Use this file to discover all available pages before exploring further.

# XLAs & dispatch

> How a service manager keeps flow healthy in Thread: XLA response timers, auto-dispatch profiles, available-for-dispatch, and business hours and closures.

export const MarkComplete = ({id, signalDone = false}) => {
  const KEY = "thread-onboarding-completed";
  const read = () => {
    try {
      return JSON.parse(localStorage.getItem(KEY) || "[]");
    } catch (e) {
      return [];
    }
  };
  const [localDone, setLocalDone] = useState(false);
  const done = signalDone || localDone;
  useEffect(() => {
    const sync = () => setLocalDone(read().includes(id));
    sync();
    window.addEventListener("thread-onboarding-updated", sync);
    return () => window.removeEventListener("thread-onboarding-updated", sync);
  }, [id]);
  const toggle = () => {
    if (signalDone) return;
    const list = read();
    const next = list.includes(id) ? list.filter(x => x !== id) : [...list, id];
    try {
      localStorage.setItem(KEY, JSON.stringify(next));
    } catch (e) {}
    setLocalDone(next.includes(id));
    window.dispatchEvent(new Event("thread-onboarding-updated"));
  };
  return <button onClick={toggle} style={{
    width: "100%",
    padding: "0.7rem 1rem",
    marginTop: "1.5rem",
    borderRadius: "8px",
    border: "1px solid " + (done ? "#00B398" : "rgba(128,128,128,0.35)"),
    background: done ? "rgba(0,179,152,0.12)" : "transparent",
    color: done ? "#00B398" : "inherit",
    fontWeight: 600,
    fontSize: "0.95rem",
    cursor: "pointer",
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    gap: "0.5rem"
  }}>
      <span aria-hidden="true">{done ? "✓" : "○"}</span>
      {done ? "Completed" : "Mark as complete"}
    </button>;
};

export const ByRoleProgress = () => {
  useEffect(() => {
    const KEY = "thread-onboarding-completed";
    const TEAL = "#00B398";
    const BASE = "/start-here/roles/";
    const DONE_ICON = '<svg class="thread-rc-doneicon size-4" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#00B398" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' + '<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>' + '<polyline points="22 4 12 14.01 9 11.01"/></svg>';
    const COURSES = [{
      slug: "technician",
      steps: ["why-thread", "get-around-inbox", "day-one-setup", "daily-workflows", "ai-toolkit", "notifications-and-customers"]
    }, {
      slug: "dispatcher",
      steps: ["why-dispatch-in-thread", "read-the-board", "assign-and-schedule", "ai-for-dispatch"]
    }, {
      slug: "service-ops-manager",
      steps: ["set-up-the-desk", "sla-and-dispatch", "analytics-and-qa", "roll-out-and-adopt"]
    }, {
      slug: "csm-account-manager",
      steps: ["client-intelligence-and-health", "qbrs-and-reporting", "retention-and-expansion"]
    }, {
      slug: "security-compliance-owner",
      steps: ["security-work-in-thread", "data-and-compliance", "security-runbooks"]
    }, {
      slug: "sales-business-development",
      steps: ["the-ai-service-pitch", "quote-and-handoff"]
    }, {
      slug: "msp-owner-leadership",
      steps: ["the-rollout", "outcomes-and-analytics", "scale-ai-service"]
    }, {
      slug: "automation-engineer",
      steps: ["flows-and-intents", "thread-mcp-and-api", "build-with-super-magic"]
    }];
    const COURSE_SLUGS = new Set(COURSES.map(c => c.slug));
    const readLocal = () => {
      try {
        return JSON.parse(localStorage.getItem(KEY) || "[]");
      } catch (e) {
        return [];
      }
    };
    const isDone = (done, id) => done.includes(id);
    const decorate = () => {
      const done = readLocal();
      const scope = document.getElementById("sidebar-content") || document;
      const links = [...scope.querySelectorAll('a[href*="' + BASE + '"]')].filter(a => !a.closest("main"));
      links.forEach(a => {
        const m = (a.getAttribute("href") || "").match(/\/start-here\/roles\/([^/?#]+)\/([^/?#]+)/);
        if (!m) return;
        if (!COURSE_SLUGS.has(m[1])) return;
        const id = m[1] + "/" + m[2];
        const d = isDone(done, id);
        const has = a.querySelector(".thread-rc-check");
        if (d && !has) {
          const s = document.createElement("span");
          s.className = "thread-rc-check";
          s.textContent = "✓ ";
          s.style.color = TEAL;
          s.style.fontWeight = "700";
          a.insertBefore(s, a.firstChild);
        } else if (!d && has) {
          has.remove();
        }
      });
      COURSES.forEach(course => {
        const li = scope.querySelector('li[id="' + BASE + course.slug + '"]');
        if (!li) return;
        const row = li.querySelector(":scope > button") || li.querySelector(":scope > a") || li.firstElementChild;
        if (!row) return;
        const nameSpan = [...row.querySelectorAll("span")].find(s => s.textContent.trim() && !s.classList.contains("thread-rc-coursecheck"));
        if (!nameSpan) return;
        const allDone = course.steps.length > 0 && course.steps.every(s => isDone(done, course.slug + "/" + s));
        const has = nameSpan.querySelector(".thread-rc-coursecheck");
        if (allDone && !has) {
          const s = document.createElement("span");
          s.className = "thread-rc-coursecheck";
          s.setAttribute("aria-hidden", "true");
          s.style.display = "inline-flex";
          s.style.alignItems = "center";
          s.style.verticalAlign = "-0.2em";
          s.style.marginRight = "0.35em";
          s.innerHTML = DONE_ICON;
          nameSpan.insertBefore(s, nameSpan.firstChild);
        } else if (!allDone && has) {
          has.remove();
        }
      });
    };
    decorate();
    const iv = setInterval(decorate, 600);
    window.addEventListener("thread-onboarding-updated", decorate);
    window.addEventListener("storage", decorate);
    return () => {
      clearInterval(iv);
      window.removeEventListener("thread-onboarding-updated", decorate);
      window.removeEventListener("storage", decorate);
    };
  }, []);
  return null;
};

<ByRoleProgress />

With the desk structured, your next job is flow: work reaching the right technician, and the clock starting on the right tickets. This lesson covers the four settings that govern it — **XLA timers**, **auto-dispatch profiles**, **available-for-dispatch**, and **business hours and closures** — and how they reinforce each other.

## XLA, not SLA — and why the difference matters

Your PSA already has SLAs. An **SLA** is a contractual promise in the agreement you sold: first response in 30 minutes, resolution in 8 hours. It exists to be measured after the fact, for billing and for the renewal conversation, and it is satisfied or breached as a matter of record.

Thread tracks an **XLA** — an *Experience Level Agreement*. Same clocks, different question. An SLA asks "did we meet the contract?" An XLA asks "what did this actually feel like for the customer?"

That is not a word game, because the two can disagree:

| Situation                                                              | The SLA says   | The customer experienced           |
| ---------------------------------------------------------------------- | -------------- | ---------------------------------- |
| Auto-acknowledgement fires in 4 seconds, a human replies 6 hours later | Response met   | Six hours of silence               |
| Ticket bounced between three techs, resolved inside the window         | Resolution met | Explaining the problem three times |
| Timer ran overnight while the desk was closed                          | Breached       | Nothing wrong at all               |

An SLA that reads green while the customer is frustrated is the failure mode Thread is built to close. So the timers here measure the experience: they stop on a real human reply rather than an auto-ack, they pause while [Triage Agent](/ai-agents/getting-started-with-triage-agent) is still working the conversation, and they respect your closures — so the number is one you can act on rather than defend.

**Both still matter.** Keep your SLAs in the PSA as the contract. Run the desk on XLAs, because that is the clock your team can see while the work is still in flight.

## Set your XLA timers and response expectations

[XLA timers and response settings](/inbox/sla-timers-and-response-settings) define the promises your desk makes: how fast a new request gets a first response, and how fast it gets resolved. Thread tracks these live, surfaces them on tickets and Views, and rolls attainment into analytics — so an XLA isn't a spreadsheet you reconcile after the fact, it's a clock your team can see ticking.

As the manager, you own the targets. A few principles keep them useful:

* **Set thresholds you can actually hit.** An XLA you breach constantly stops being a signal and becomes noise the desk learns to ignore.
* **Differentiate by priority.** A P1 outage and a routine password reset shouldn't share a response target.
* **Make the timer visible in the work.** Build a **Breaching soon** View (from the previous lesson) so techs act before the clock trips, not after.

## Put the XLA to work in Views and Flows

A timer nobody sees is a report. These are the two places an XLA changes what happens on the desk:

**Views — make risk the thing people open.** Where Experience Level Agreements are enabled you can sort a View by **XLA** risk and slice [View Insights](/inbox/views-and-insights) by it, so "what's tightening?" is a board rather than a question. Two worth building:

* **Breaching soon** — everything with a timer about to trip, across the team. This is the View a dispatcher works from, not a report they read.
* **Needs response** — last sender is the contact, sorted by XLA risk, so the tickets where silence is compounding sit at the top.

**Flows — act on the clock without anyone watching.** [Flows](/inbox/flows) fire on ticket events and can then run any action: reassign, change priority or board, post an internal note, [page an on-call technician](/ai-agents/voice-ai-on-call-escalation), or hand the ticket to a [Super Magic Agent](/skill-library/agents) to work unattended. Point them at the tickets your XLA View surfaces and the escalation stops depending on somebody noticing.

<Note>
  Flows can also alert on the clock directly: a **beta** capability posts to Microsoft Teams or Slack when a ticket's XLA timer drops below a threshold you choose. Otherwise Flows are event-triggered — they run when a ticket changes, not on a timer — so build the View for visibility and the Flow for the action.
</Note>

<Warning>
  XLA clocks only tell the truth if they respect when your desk is open. Configure [business hours and closures](/inbox/holidays-and-custom-closures) before you trust a single XLA number — otherwise timers run overnight and on weekends and every report reads red for no reason. That setup is covered at the end of this lesson.
</Warning>

## Route work automatically with dispatch profiles

Manual dispatch doesn't scale, and it breaks the moment the dispatcher is at lunch. [Auto-dispatch profiles](/inbox/auto-dispatch-profiles) let Thread assign incoming work for you, based on rules you define — so new tickets land on the right team or technician without anyone playing traffic cop.

A dispatch profile ties together the pieces you set up earlier:

| Profile ingredient   | What you control                                                                       |
| -------------------- | -------------------------------------------------------------------------------------- |
| **What it matches**  | The board, client, priority, or category of tickets the profile applies to.            |
| **Who it routes to** | A team or pod (from your structure), so coverage survives any one person being out.    |
| **How it picks**     | The assignment logic — spreading load across a pod rather than dumping it on one name. |

Point profiles at **teams**, not individuals, wherever you can. Routing to a pod means the work still flows when someone's on PTO, and your reporting stays clean because load balances within the group.

<Tip>
  Start simple: one profile per pod that catches its clients' new work, routed to the pod. Add finer rules (priority splits, specialist routing) only once the basic flow is proven. A few reliable profiles beat a maze of clever ones.
</Tip>

## Keep the routing pool accurate with available-for-dispatch

Auto-dispatch is only as good as its picture of who's actually available. [Available-for-dispatch](/inbox/available-for-dispatch) is the toggle that controls whether a technician is in the pool for new automatic assignments — the real-time answer to "who can take the next ticket right now?"

This is what keeps dispatch honest through the day:

* A technician heads into a long on-site or a focus block → they come **off** dispatch, and new work routes around them.
* Someone wraps their current load and has capacity → they go **on**, and the pool rebalances.
* Out sick or on PTO → off, so the profile never assigns to an empty seat.

Coach the team to treat their availability like a status they own, and lean on it yourself when you're balancing load. It's the difference between a dispatch profile that distributes work fairly and one that keeps piling onto whoever forgot to flip a switch.

## Ground the clock in business hours and closures

The setting that makes all of the above trustworthy: [business hours and custom closures](/inbox/holidays-and-custom-closures). This defines when your desk is open, which in turn governs when XLA timers run and when work is expected to move.

Get three things on the calendar:

<Steps>
  <Step title="Set your standard business hours">
    Define the days and hours the desk operates. XLA response and resolution clocks pause outside these hours, so overnight and weekend tickets don't rack up phantom breaches.
  </Step>

  <Step title="Add holidays and custom closures">
    Put your holiday calendar and any one-off closures in. On a closed day, XLA clocks hold — so the Monday-morning queue reflects real elapsed time, not a weekend of dead air.
  </Step>

  <Step title="Reconcile with after-hours coverage">
    If you run after-hours or 24/7 coverage, make sure your business hours, dispatch profiles, and any after-hours team all agree on who's on the clock when. A mismatch here shows up as either missed work or unfair XLA breaches.
  </Step>
</Steps>

<Note>
  These four settings are a system, not a checklist. Business hours make XLAs honest; XLAs tell dispatch what's urgent; dispatch profiles route it; available-for-dispatch keeps the routing pool real. Change one and re-check the others.
</Note>

<MarkComplete id="service-ops-manager/sla-and-dispatch" />

## Next

Work is structured and flowing on time. Now learn to see how it's actually performing — and to run a QA loop that turns those numbers into coaching.

<Card title="Analytics & QA" icon="chart-line" href="/start-here/roles/service-ops-manager/analytics-and-qa">
  Magic Analytics, View insights, CSAT, and a repeatable QA loop.
</Card>


## Related topics

- [Set up the desk](/start-here/roles/service-ops-manager/set-up-the-desk.md)
- [Service & Ops Manager](/start-here/roles/service-ops-manager.md)
- [AI for dispatch](/start-here/roles/dispatcher/ai-for-dispatch.md)
