Scoring & routing
Score fit and intent on separate axes, match the lead to its account, then route on a clock. The model is not the point. The clock is.
The first scoring model I inherited summed one number out of firmographics and clicks. It lied in a specific way: a prospect who binged five blog posts outscored a VP at a perfect-fit account who had not lifted a finger. Sales stopped trusting it inside a month, and once a rep stops trusting a score, the score is dead. Nobody works a queue they think is random. The fix was not a better weighting. It was splitting the number in two, and then admitting the model was never the bottleneck. The clock was.
RevenueHero mystery-shopped 1,000 B2B websites in 2025. 63.5% never responded to a qualified inbound at all, and the ones that did answered in an average of 29 hours. Read that as the opening it is. Buyers now expect a reply inside the hour, and most vendors take more than a day or ghost entirely. That gap is the moat your routing stack is built to hold, not a benchmark to match.
Keep fit and intent on separate axes
Fit is whether the account should ever buy from you: industry, size, tech stack, region. It moves slowly and comes from firmographics. Intent is whether they are moving right now: a demo request, three pricing-page visits in a week, a repeat session in the same day. It spikes, it decays, and it comes from behavior. When you add the two into one total, a flood of low-value clicks masks a bad-fit account, and a great-fit account with no recent activity sinks to the bottom of the queue. A fit score that drops because someone stopped opening emails is a confused composite reporting two unrelated facts as one.
HubSpot rebuilt its scoring in 2025 around exactly this split: a fit letter grade crossed with an engagement number, so a lead reads as A1 or C3, never a single blended total. You act on the top-left corner, high fit and high intent, and you route the rest to the play each quadrant deserves. Intent prioritizes inside the high-fit pool. It never rescues a low-fit one. A C-grade account requesting five demos is a tire-kicker with a fast trigger finger, not a lead.
Here is the grid I hand every team that asks how to read the two axes at once. The quadrant a lead lands in is the routing decision, before any rep sees it.
The scoring rubric that produces those grades is a points table you can write down and a rep can read, not a mystery model. Two rule sets, two outputs, no blending.
| Signal | Axis | Points | Why |
|---|---|---|---|
| Enterprise employee count | Fit | +30 | Target segment, slow-moving firmographic |
| Target industry | Fit | +20 | ICP core (HubSpot 2025 fit-grade inputs) |
| Target tech in stack | Fit | +15 | Technographic fit, from enrichment |
| Personal email domain | Fit | -20 | gmail/outlook signals non-buyer or bad match |
| Demo request | Intent | +40 | Strongest first-party intent |
| Pricing-page visit | Intent | +15 | High-commercial-intent page |
| Three-plus sessions in seven days | Intent | +15 | Repeat behavior, last-7-days weighs ~3× older (Woodpecker signal-decay data) |
| Unsubscribed | Intent | -100 | Hard suppression |
Notice the two axes never touch. Fit sums to a letter band, intent sums to a number band, and the pair is the grade. A -20 on the fit axis for a personal email domain cannot be bought back by clicking the pricing page nine times, because those points live on a different axis and never meet in a sum.
Speed-to-lead is the reason the stack exists
The old five-minute rule gets quoted as gospel, and the 21x and 100x multipliers behind it come from studies old enough to vote (2007 to 2012). Treat the exact number as a heuristic, not a law. The shape holds anyway: contact odds fall off a cliff in the first hour and keep bleeding for a week. A qualified A1 that sits unrouted overnight is worth a fraction of the same lead answered in ten minutes, and no scoring refinement recovers that decay.
View as table
| Point | Value |
|---|---|
| 5 min | 100 |
| 30 min | 62 |
| 1 hr | 45 |
| 4 hr | 28 |
| 24 hr | 12 |
| 48 hr | 7 |
That is what your scoring-and-routing stack buys: a shorter clock, not a cleverer model. I wrote the longer argument for treating routing as a latency problem in routing is latency. The one-line version: if a hot A1 waits overnight because the round-robin was down or the L2A match failed, the model quality is irrelevant. You lost the deal in the queue, not in the score.
Lead-to-account matching happens before you route
Routing before you know the account is how two reps end up dialing one company on the same afternoon. Lead-to-account matching (L2A) resolves an inbound lead to the account it belongs to, and it is harder than it looks. Matching on email domain alone gets you roughly 70% coverage. Layering fuzzy company-name matching on top adds another 15 to 20 points, because half your inbounds arrive from a gmail address or type “Acme Corp.” against an account stored as “Acme, Inc.” Native CRM dedup does exact matches. It does not do fuzzy. That is the whole reason L2A is a tooling category and not a checkbox.
| Match method | Coverage | Notes |
|---|---|---|
| Exact email domain | ~70% | Native CRM can do this; misses free-mail and typos |
| + Fuzzy company name | +15 to 20 pts | Handles “Acme, Inc.” vs “Acme Corp”; needs a real matching engine |
| + LinkedIn company URL | +5 to 10 pts | Best disambiguation for common names (matches the enrichment-side lift) |
| Native CRM dedup alone | Exact only | No fuzzy; “Block” mode can silently reject API inserts and lose the lead |
Match first, then route. If an AE already owns the resolved account, the inbound goes to that AE, not into the round-robin. Here is the domain-match cut I run first in Salesforce before any fuzzy layer, to see how many inbounds resolve cleanly and how many fall through to the fuzzy engine:
SELECT l.Id, l.Email, l.Company, a.Id accountId, a.Name
FROM Lead l
LEFT JOIN Account a
ON a.Website LIKE '%' + SUBSTRING(l.Email, INSTR(l.Email,'@')+1) + '%'
WHERE l.IsConverted = false
AND l.Email != null
Salesforce SOQL will not do that join or SUBSTRING natively, so in practice you extract the domain into an Email_Domain__c field on the Lead with a formula, then match it against a normalized Account_Domain__c. The lesson from the field is the same one that governs enrichment: standardize the domain into its own column before you match anything, or the fuzzy layer inherits your dirty data. I go deeper on that in enrichment waterfalls.
The build order, in the order I build it
This is the sequence I stand up on every routing engagement. Each step is a precondition for the next, so the order is not a suggestion. Skip enrichment and there is no fit grade to route on. Skip L2A and you route duplicates. Skip the SLA clock and the whole stack is decoration.
- 1
1. Enrich before you score
Fit attributes (size, industry, tech, domain) must exist before a fit grade can be computed. No enrichment, no fit axis. Run the company waterfall first so 40 to 60% of junk drops before you spend a credit on people.
- 2
2. Score fit and intent on separate axes
Two rule sets, two outputs: a letter for fit, a number for intent. Never a single sum. Write the points table down so a rep can read why a lead graded A1.
- 3
3. Match to the account (L2A)
Resolve to the existing account by domain, then fuzzy name, then LinkedIn URL. If an AE owns it, the inbound is theirs. This runs before any owner assignment.
- 4
4. Apply the routing gate
Only leads clearing both the fit floor and the intent floor enter sales routing. Everything else goes to nurture, self-serve, or suppression per its quadrant.
- 5
5. Assign by territory and capacity
Round-robin inside the right segment with a per-rep daily cap, so nobody drowns while another rep idles. Territory rules win over round-robin.
- 6
6. Notify and start the SLA clock
A Slack ping plus a task the second it lands, with a timestamp. The clock is the product, not the assignment.
- 7
7. Escalate on breach
A lead untouched inside the window reassigns or alerts a manager and logs the miss, so the P90 is auditable and the breach has an owner.
The capacity cap in step 5 is where round-robins quietly fail. Naive round-robin ignores who is buried, so leads-per-rep can spread 20 to 40% across a team while the assignment log looks perfectly even. Cap it. Here is the shape of the guard in Apex, checking today’s assigned count before the rep is eligible:
// Skip a rep who has already hit the daily cap; fall through to the next in ring
Integer todayCount = [
SELECT COUNT() FROM Lead
WHERE OwnerId = :candidateRepId
AND Routed_At__c = TODAY
];
if (todayCount >= repDailyCap) {
continue; // move to next rep in the round-robin ring
}
lead.OwnerId = candidateRepId;
lead.Routed_At__c = System.now();
Rules-based versus predictive: start with rules
Every team eventually asks whether to replace the points table with a machine-learned model. The honest answer is: not yet, and probably not for a while. Under about 50 conversions, a model has nothing to learn from, and it will confidently mislabel everything for a quarter while reps lose faith in the whole system.
| Rules-based | Predictive (ML) | |
|---|---|---|
| Cold-start | Works on day one | Needs history; under ~50 conversions it guesses |
| Transparency | Reps see the points and trust them | Black box; reps reject scores they cannot explain |
| Data floor | None to speak of | HubSpot wants 25 won + 25 lost; Einstein ~1,000 leads / 120 conversions |
| Upkeep | You tune it on purpose | Drifts silently as the market shifts |
| Verdict | Start here, always | Add only once rules provably miss buyers, keep a rules floor under it |
When you do earn the right to add a model, do not swap it in live. Run it in shadow mode: score every lead both ways, route on the rules, and log where the model disagreed and who converted. That is the only way to know the model beats the table before you bet the queue on it. I laid out that pattern in shadow-mode scoring.
Report the P90, not the mean
The mean response time is the number that makes a broken SLA look fine. Ten leads answered in two minutes and one answered at 3am average out to a respectable figure while that one overnight lead was the deal you lost. Watch the P90. That is where deals die, and it is the number a manager can act on. Pull it straight from the routing timestamps:
SELECT Owner.Name,
AVG(Minutes_To_First_Touch__c) meanMins,
MAX(Minutes_To_First_Touch__c) worstMins,
COUNT(Id) leads
FROM Lead
WHERE Routed_At__c = LAST_N_DAYS:30
AND First_Touch_At__c != null
GROUP BY Owner.Name
ORDER BY MAX(Minutes_To_First_Touch__c) DESC
SOQL has no percentile function, so pull the raw Minutes_To_First_Touch__c per lead and compute the P90 downstream, or approximate with MAX per rep to surface the worst offenders first. The reps at the top of that worstMins list are where your speed-to-lead advantage leaks out.
Before you touch a single weight, pull your own speed-to-lead distribution and find the P90. If a qualified inbound waits hours, no scoring change moves the number, because the score was never the constraint. The clock was. Fix the clock first, then earn the right to argue about weights. For the deeper systems view of routing as a distributed-latency problem, read lead routing as a distributed system.
Keep reading
All guides →Enrichment waterfalls
Chain providers so a miss from one becomes a hit from the next, order by marginal recovery instead of hit rate, and pay mostly on verified data. The build, the queries, the credit math.
BuildSignal-based outbound
Trigger outreach off events that mark an account entering a buying window, rank signals by how tightly they predict a buy, and route each one to the play and the human that fit. The workflow, the reply math, the decay curve.
BuildAI & agentic workflows
The 3% who got revenue from AI SDRs fed their agents proprietary context and fenced what they could say. Here is the guardrail stack, the shadow-mode scoring loop, and the human-in-loop gate that separate the two outcomes.