DEV Community

Cover image for Stop Making Reps Google Prospects: Build a Self-Enriching Lead Pipeline in n8n
Michael
Michael

Posted on Originally published at getmichaelai.com

Stop Making Reps Google Prospects: Build a Self-Enriching Lead Pipeline in n8n

Your sales reps spend a shocking amount of time not selling. They're on LinkedIn confirming job titles, on Crunchbase checking funding, pasting company names into ChatGPT to summarize what a prospect actually does.

Every minute of that is manual data plumbing that a workflow can do faster, cheaper, and consistently. Here's how to build that workflow in n8n.

The pipeline in one sentence

A new lead comes in, gets enriched with firmographic and contact data, gets researched by an LLM, gets scored against your ICP, and lands in your CRM with a fit rating and a ready-to-use summary.

No human touches it until the point where a human actually adds value: the conversation.

Stage 1: Catch the lead

Every lead source becomes an entry point. Use an n8n Webhook node for form fills, a Gmail/IMAP trigger for inbound emails, or a polling node against your CRM.

Standardize the payload early. The rest of the workflow should never care whether a lead came from a Typeform or a HubSpot form.

{
  "email": "jane@acme.io",
  "company_domain": "acme.io",
  "source": "webinar-2024"
}
Enter fullscreen mode Exit fullscreen mode

One rule: normalize the domain immediately. Strip www., lowercase everything, and derive the domain from the email if the form didn't capture it. Bad domains poison every downstream enrichment call.

Stage 2: Enrich with real data

Before you let an LLM touch anything, get hard facts. Use an HTTP Request node against an enrichment provider (Clearbit, Apollo, People Data Labs, or a scraper of your choice) to pull company size, industry, funding, tech stack, and the contact's role.

The key is to keep this deterministic. Enrichment APIs return structured data. Don't ask an AI to guess headcount when an API will tell you.

// Function node: merge enrichment into a clean lead object
const lead = $input.first().json;
const enrich = $('Enrichment API').first().json;

return [{
  json: {
    email: lead.email,
    domain: lead.company_domain,
    company: enrich.name,
    industry: enrich.category?.industry ?? 'unknown',
    employees: enrich.metrics?.employees ?? null,
    funding: enrich.metrics?.raised ?? null,
    title: enrich.person?.title ?? '',
    seniority: enrich.person?.seniority ?? '',
    source: lead.source
  }
}];
Enter fullscreen mode Exit fullscreen mode

Add an IF node here to short-circuit garbage. No company match, personal Gmail domain, or fewer than N employees? Route it to a low-priority bucket instead of burning LLM tokens on it.

Stage 3: AI prospect research

This is where the LLM earns its keep. Feed it the enriched facts plus scraped context (recent news, the company's homepage copy, the prospect's LinkedIn headline) and ask for the things a rep would otherwise dig up manually.

Structure your prompt to return JSON so downstream nodes can parse it reliably.

// OpenAI / Anthropic node - system + user message
const lead = $input.first().json;

const prompt = `You are a B2B sales researcher.
Company: ${lead.company} (${lead.industry}, ~${lead.employees} employees)
Contact: ${lead.title}
Homepage summary: ${lead.homepage_text}
Recent news: ${lead.news}

Return strict JSON:
{
  "summary": "2-sentence plain-English what they do",
  "pain_hypothesis": "likely problem our product solves",
  "opening_line": "one personalized cold-email sentence",
  "buying_signals": ["list of observed triggers"]
}`;

return [{ json: { prompt } }];
Enter fullscreen mode Exit fullscreen mode

Two things that keep this reliable: set temperature low (0.2-0.3) so the output stays grounded, and use the model's structured-output / JSON mode so you aren't regex-parsing prose at 2am.

Stage 4: Automated qualification

Now score the lead against your Ideal Customer Profile. You can do this with plain logic in a Function node - no AI needed, and it's auditable.

const l = $input.first().json;
let score = 0;

if (l.employees >= 50 && l.employees <= 1000) score += 30;
if (['VP', 'Director', 'C-Suite'].includes(l.seniority)) score += 30;
if (['SaaS', 'Fintech', 'E-commerce'].includes(l.industry)) score += 20;
if (l.funding > 5_000_000) score += 20;

const tier = score >= 70 ? 'A' : score >= 40 ? 'B' : 'C';

return [{ json: { ...l, score, tier } }];
Enter fullscreen mode Exit fullscreen mode

Keep scoring rules in code or a variable, not scattered across nodes. When sales leadership wants to tweak the ICP, you change one block.

Stage 5: Route and write back

Use a Switch node on tier:

  • Tier A → create CRM deal, notify the rep in Slack with the AI summary and opening line, book a task.
  • Tier B → drop into a nurture sequence.
  • Tier C → log and move on.

Write everything back to the CRM: score, summary, buying signals, research timestamp. Your reps open the record and everything they'd have spent 20 minutes gathering is already there.

Slack message that reps actually read

const l = $input.first().json;
return [{ json: { text:
  `:fire: *Tier ${l.tier} lead* (${l.score}/100)\n` +
  `*${l.company}* — ${l.summary}\n` +
  `*Angle:* ${l.pain_hypothesis}\n` +
  `*Opener:* ${l.opening_line}`
}}];
Enter fullscreen mode Exit fullscreen mode

What to watch out for

Cache enrichment. Same domain twice in a week shouldn't cost two API calls. Store results and check first.

Handle rate limits. Wrap HTTP nodes with retries and use n8n's built-in error workflow to catch failures instead of silently dropping leads.

Don't over-trust the LLM. It writes the narrative; your deterministic scoring makes the decision. That separation keeps qualification defensible.

Build this once and every rep gets a research analyst that never sleeps. They stop Googling and start closing - which is the only thing you actually pay them to do.


Originally published at getmichaelai.com

Top comments (0)