A high-intent enterprise lead lands in the chat and goes to a rep who handles SMB self-serve. The rep does their best, the buyer disengages, and the deal never gets logged. The Conversations inbox routed correctly per the default rules — the rules were the wrong ones. Routing has more knobs than most teams use.
The four routing dimensions you actually need
- Skill: who handles enterprise vs SMB, EMEA vs Americas
- Channel: chat behaves differently from email and forms
- Load: capacity per rep right now
- Hours: rep online and within shift
Default round-robin handles only one of these. Build an automation layer for the rest.
Map skills to inbox properties
Add a skill_tags property on the user record (custom user property via Operations Hub). Possible values: enterprise_sales, smb_sales, tier1_support, tier2_support, billing, emea, apac. Routing logic reads tags and matches to a routing input on the conversation:
Routing input on conversation:
intent: enterprise_sales
region: emea
language: en
Eligible reps =
user.skill_tags includes "enterprise_sales"
AND user.skill_tags includes "emea"
AND user.online_status = available
AND user.current_chats < user.max_concurrent
Load-aware round-robin
A rep with 3 active chats and a rep with 0 should not get equal odds. Build a custom routing action via a workflow + custom code:
// Custom code action in workflow
const eligible = await getEligibleReps(routingInput);
if (eligible.length === 0) return { outputFields: { rep: null } };
eligible.sort((a, b) => {
const loadA = a.current_chats / a.max_concurrent;
const loadB = b.current_chats / b.max_concurrent;
if (loadA !== loadB) return loadA - loadB;
return a.last_assigned_at - b.last_assigned_at; // older first
});
const winner = eligible[0];
await assignConversation(conversationId, winner.id);
return { outputFields: { rep: winner.id } };
Detect intent before routing
The first chat message is your signal. Run a fast classification step before routing decides where it goes:
Pattern -> Intent
"pricing", "quote", "demo" -> sales
"login", "password", "error", "bug" -> support
"invoice", "billing", "subscription" -> billing
"partner", "reseller" -> partnerships
default -> sales (or your fallback)
For higher accuracy, call a Breeze intent classifier or a small LLM with a fixed prompt. Store the intent on the conversation property for analytics later.
Office-hours fallback
A 2 a.m. enterprise chat with no rep online needs a graceful fallback: capture intent, route to email queue, set expectations. The worst pattern is a chat widget that promises live help and then sits unanswered:
{% if not has_online_rep %}
<p>Our team is offline right now. Drop your question and email and
we'll respond within 2 business hours.</p>
{% endif %}
Handoffs without losing context
A chat starting with the bot, escalating to tier 1, and finally landing with tier 2 needs context preserved at every hop. Use the conversation property bag, not the message thread:
conversation.intent: support
conversation.product_area: integrations
conversation.tier: 2
conversation.bot_attempts: 3
conversation.assigned_history: ["bot", "rep_42", "rep_57"]
The receiving rep opens the conversation and sees the route, not just a chat log.
Measure routing health, not just chat volume
Build a dashboard with: time-to-first-response by intent, mis-routes per week (manual reassignments count as a miss), abandoned chats during business hours, and load distribution per rep. Routing tuning is a weekly exercise, not a one-time setup.
What to do this week
Tag each rep with skill values, add an intent classifier on the first chat message, replace round-robin with load-aware routing for your highest-value queue, and instrument mis-route counts on the dashboard.