Building a Production B2B Lead Enrichment & Scoring Agent in n8n for $0.008/Run
Every B2B software company hits the same operational roadblock: lead context bankruptcy. A prospect fills out a simple two-field demo form with [email protected] or [email protected]. Your sales reps or founders then spend
Every B2B software company hits the same operational roadblock: lead context bankruptcy.
A prospect fills out a simple two-field demo form with [email protected] or [email protected]. Your sales reps or founders then spend 10 to 15 minutes checking LinkedIn, reading the company's landing page, trying to guess their tech stack, and looking up headcount before writing a personalized follow-up.
At scale, this manual triage devours 10+ engineering and sales hours weekly.
In this technical breakdown, we'll design an autonomous, deterministic enrichment and ICP (Ideal Customer Profile) scoring pipeline using n8n, lightweight HTTP scraping, and OpenAI Structured Outputs. The running cost? Less than $0.008 per enriched lead.
1. The Bottleneck: The Enterprise Enrichment Trap
Commercial enrichment platforms (ZoomInfo, Clearbit, Apollo) generally present three pain points for engineering teams:
- Aggressive Paywalls: Enterprise vendors push for $10,000–$25,000 annual upfront contracts with rigid credit caps.
- Stale Relational Caches: Traditional providers serve data from quarterly batch scrapes. If a company pivoted or launched a new product line two weeks ago, their database reflects old metadata.
- Probabilistic Hallucinations: Unconstrained LLM extraction often outputs fluctuating schemas that break downstream webhook consumers or CRM schema validators.
The Cost Architecture: Vendor vs. In-House
| Attribute | Legacy Provider | Custom n8n + LLM Pipeline |
|---|---|---|
| Cost / Record | $0.25 – $1.20 | ~$0.006 – $0.010 |
| Annual Commitment | $12,000+ | $0 (Self-hosted or n8n Cloud base) |
| Data Freshness | Stale (30–90 days) | Real-time (Live DOM scrape) |
| Scoring Flexibility | Fixed proprietary formula | Fully configurable deterministic JSON schema |
2. Pipeline Architecture
The entire agent executes deterministically through four lifecycle stages:
[ Inbound Webhook / Form Submit ]
│
▼
[ Node 1: Domain Normalization & Disposable Email Filter ]
│ (Pass: Corporate Domain)
▼
[ Node 2: HTTP Target Site Scraper & DOM Content Extractor ]
│ (Raw HTML -> Clean Text Payload)
▼
[ Node 3: OpenAI Structured Output Engine (Strict JSON Schema) ]
│ (Deterministic ICP Score 0-100 + Analysis)
▼
[ Node 4: Dynamic Router ]
├── Score >= 75 ──> [ Sync to CRM (HubSpot/Airtable) ] ──> [ Alert Slack/Discord ]
└── Score < 75 ──> [ Tag as Low Priority in CRM ]
3. Implementation Logic & Core Code
Let's implement each stage within an n8n environment.
Stage 1: Domain Parsing & Disposable Filtering
We first strip out protocol prefixes, top-level email formatting, subdomains, and throwaway freemail providers (Gmail, Outlook, ProtonMail, TempMail). This runs inside an n8n Code Node (JavaScript).
// n8n Code Node: Domain Normalization & Freemail Scrubbing
const items = $input.all();
const FREEMAIL_DOMAINS = new Set([
'gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com',
'icloud.com', 'proton.me', 'protonmail.com', 'mail.ru',
'tempmail.com', 'guerrillamail.com', '10minutemail.com'
]);
return items.map(item => {
const rawEmail = (item.json.email || '').trim().toLowerCase();
const explicitDomain = (item.json.domain || '').trim().toLowerCase();
let extractedDomain = '';
if (rawEmail.includes('@')) {
extractedDomain = rawEmail.split('@')[1];
} else if (explicitDomain) {
extractedDomain = explicitDomain
.replace(/^(?:https?:\/\/)?(?:www\.)?/i, '')
.split('/')[0];
}
const isFreemail = FREEMAIL_DOMAINS.has(extractedDomain);
const isValidDomain = extractedDomain.includes('.') && !isFreemail;
return {
json: {
...item.json,
extractedDomain,
isFreemail,
isValidCorporateLead: isValidDomain
}
};
});
If isValidCorporateLead is false, the workflow branches directly to a cold drip or standard auto-responder without burning LLM inference tokens.
Stage 2: Clean DOM Scrape
We perform a targeted GET request using an HTTP Request Node to https://${extractedDomain}.
To prevent context window bloat and reduce costs, do not pass complete HTML strings to your model. Pass only targeted meta tags and extracted hero text via a lightweight regex or Cheerio parser in an n8n Code Node:
// n8n Code Node: Lightweight HTML Extraction
const html = $input.first().json.data;
function getMetaTag(tag) {
const match = html.match(new RegExp(`<meta[^>]*?(?:name|property)=["']${tag}["'][^>]*?content=["']([^"']*)["']`, 'i'));
return match ? match[1] : '';
}
const titleMatch = html.match(/<title[^>]*>([^<]+)<\/title>/i);
const title = titleMatch ? titleMatch[1].trim() : '';
const description = getMetaTag('description') || getMetaTag('og:description');
// Strip style, script, and grab visible text tokens
const cleanBody = html
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '')
.replace(/<[^>]+>/g, ' ')
.replace(/\s+/g, ' ')
.trim()
.slice(0, 3000); // Guard rails: cap at 3000 chars
return [{
json: {
pageTitle: title,
metaDescription: description,
bodySample: cleanBody
}
}];
Stage 3: Deterministic ICP Scoring with OpenAI Structured Outputs
Rather than asking the LLM for arbitrary text, enforce a Strict JSON Schema. This guarantees your downstream CRM update never errors due to missing fields or improper types.
Use the OpenAI API via an HTTP node or the official LangChain integration with gpt-4o-mini:
{
"model": "gpt-4o-mini",
"messages": [
{
"role": "system",
"content": "You are an enterprise RevOps intelligence agent. Evaluate target company landing pages and output deterministic evaluation metrics strictly adhering to the schema."
},
{
"role": "user",
"content": "Analyze this company:\nDomain: {{ $('Stage 1').item.json.extractedDomain }}\nTitle: {{ $json.pageTitle }}\nDescription: {{ $json.metaDescription }}\nBody: {{ $json.bodySample }}"
}
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "lead_qualification_schema",
"strict": true,
"schema": {
"type": "object",
"properties": {
"company_summary": {
"type": "string",
"description": "One sentence description of the company core value prop."
},
"target_industry": {
"type": "string",
"enum": ["SaaS", "Fintech", "Healthcare", "Ecommerce", "DevTools", "Other"]
},
"estimated_target_audience": {
"type": "string",
"enum": ["B2B", "B2C", "Both"]
},
"icp_score": {
"type": "integer",
"description": "Fit score from 0 to 100 based on B2B SaaS target relevance."
},
"score_rationale": {
"type": "string",
"description": "Reasoning for this icp_score in under 30 words."
},
"identified_tech_signals": {
"type": "array",
"items": { "type": "string" },
"description": "Any tech indicators detected (e.g. Stripe, AWS, React, Python)."
}
},
"required": [
"company_summary",
"target_industry",
"estimated_target_audience",
"icp_score",
"score_rationale",
"identified_tech_signals"
],
"additionalProperties": false
}
}
}
}
Because strict: true is enabled, the API guarantees zero markdown fencing (`json), zero hallucinated property names, and guaranteed type constraints.
4. Production Hardening & Routing
When running this workflow at volume, keep these structural safeguards in mind:
Handling Scraper Timeouts and WAFs
Many enterprise websites protect themselves with Cloudflare or bot mitigations. To prevent workflow failure:
- Set the HTTP Request Node timeout explicitly to
8000ms. - In n8n, activate "Continue On Fail" under Node Settings.
- Maintain an
IFcondition verifying that the scrape returned a 200 payload. If the scrape times out or returns a 403, route the lead to a fallback branch that marks the record asEnrichment: Manual Review Requiredinstead of dropping the webhook entirely.
CRM Routing Condition
Using an n8n If Node:
`javascript
{{ $json.icp_score >= 75 && $json.estimated_target_audience !== 'B2C' }}
`
-
True Branch (Tier 1 Priority):
- Write data directly to HubSpot Deals / Contacts via the native HubSpot Node.
- Send a rich Slack/Discord alert block to the sales channel detailing company summary, tech signals, and scoring rationale.
-
False Branch (Tier 2 / Passive):
- Upsert to Airtable or marketing newsletter database with a low-priority tag.
5. Conclusion & Ready-to-Use Workflow
With just four core nodes in n8n, you have a completely self-hosted, deterministic enrichment engine that rivals proprietary tools while keeping API costs under a penny per run.
You can implement this architecture manually by following the instructions and code blocks above.
If you prefer a pre-built, production-ready implementation, you can download the complete Autonomous B2B Lead Enrichment & Scoring Workflow:
👉 Get the Autonomous B2B Lead Enrichment & Scoring Workflow on Gumroad
Use promo code EARLYBIRD at checkout to get 20% off. The package includes the complete exportable n8n workflow JSON, full test fixtures, pre-configured JSON schema definitions for OpenAI, and setup documentation for HubSpot and Airtable syncing.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.