Most B2B lead gen problems aren't traffic problems. They're routing, scoring, and follow-up problems.
A mid-market SaaS client came to us with a familiar situation: decent inbound volume, a bloated CRM, and a sales team that ignored half the leads marketing sent over. Their MQL definition was a static form fill plus a job title guess. Predictably, sales called them "junk."
Here's the exact system we built to grow qualified leads 75% in one quarter. No magic. Just tighter data plumbing and a few AI agents doing the boring work.
The diagnosis: leads weren't bad, scoring was
We pulled 6 months of closed-won and closed-lost data and ran a simple correlation. The variables the client used to score leads (title, company size, form completeness) barely predicted revenue. The variables that did predict it weren't being captured at all:
- Number of pricing/docs page visits before demo request
- Tech stack signals (detected from email domain + enrichment)
- Whether the lead replied to the first outreach within 48 hours
- Buying-committee size mentioned in intake
So step one wasn't AI. It was fixing what "qualified" meant.
Step 1: A behavioral scoring model that actually correlates
We replaced the point-based form scoring with a weighted model trained on their historical conversions. Nothing exotic - a logistic regression to start, because it's explainable and sales trusts things they can read.
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
df = pd.read_csv("leads_6mo.csv")
features = [
"pricing_visits", "docs_visits", "reply_within_48h",
"stack_match_score", "committee_size", "employee_count"
]
X = df[features]
y = df["became_opportunity"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25)
model = LogisticRegression(max_iter=1000).fit(X_train, y_train)
# Ship the coefficients so RevOps can sanity-check them
for f, w in zip(features, model.coef_[0]):
print(f"{f}: {round(w, 3)}")
The output surprised everyone: pricing_visits and reply_within_48h carried more weight than company size. The old model was optimizing for the wrong thing.
Step 2: Enrichment and dedup before scoring
Garbage in, garbage score. Before anything hit the model, we ran every new lead through an enrichment and dedup pass in n8n. A single webhook received the form submission, then fanned out to enrichment APIs and a fuzzy-match against existing CRM records.
// n8n Function node: normalize + flag duplicates
const email = $json.email.toLowerCase().trim();
const domain = email.split("@")[1];
const freeProviders = ["gmail.com", "yahoo.com", "outlook.com"];
const isBusiness = !freeProviders.includes(domain);
return [{
json: {
email,
domain,
is_business: isBusiness,
needs_enrichment: isBusiness,
dedupe_key: `${domain}-${$json.company?.toLowerCase() || domain}`
}
}];
This alone killed roughly 20% of records that were duplicates or personal emails sneaking through. Sales stopped seeing the same lead twice under three spellings.
Step 3: An AI intake agent to fill the gaps
Forms are where qualification goes to die. Ask too much and conversion tanks. Ask too little and you can't score.
So we kept the form short (email + one problem field) and added an AI agent that ran a two-message async conversation over email. It asked one qualifying question, parsed the free-text reply, and mapped it back to structured CRM fields like committee_size and timeline.
The key was constraining the agent's output to a schema, not letting it freestyle:
schema = {
"pain_point": "string",
"timeline": "now | 3_months | exploring",
"committee_size": "int",
"budget_signal": "bool"
}
# The agent must return valid JSON matching schema or the record
# stays flagged for a human. No hallucinated fields reach the CRM.
Anything the agent couldn't confidently classify got routed to a human. We didn't automate judgment - we automated data capture.
Step 4: Real-time routing so hot leads don't cool
A lead scoring 80+ that waits 6 hours for a rep is a wasted lead. We wired scored leads straight into Slack with a claim button and an SLA timer. If nobody claimed a hot lead in 15 minutes, it escalated to the manager.
The behavioral piece mattered most here: reply_within_48h was a top predictor, so speed on our side compounded it.
The results after 90 days
- MQLs up 75% - mostly from reclassifying leads the old model wrongly buried
- MQL-to-opportunity rate up 31% because the definition finally correlated with revenue
- Sales response time down from ~5 hours to under 20 minutes on hot leads
- Marketing stopped arguing with sales about lead quality, because the score was explainable
What actually drove the number
The AI wasn't the headline. The headline was defining "qualified" against real outcomes, then removing every point of friction and delay between a good lead and a human.
AI agents did three jobs well: enrichment, structured intake, and routing. Boring, repetitive, high-volume work where consistency beats cleverness.
If your MQL number looks fine but sales won't touch the leads, start where we did. Pull your closed-won data and ask one question: does your current score predict revenue? If it doesn't, no amount of extra traffic will fix it.
Originally published at getmichaelai.com
Top comments (0)