DEV Community

Cover image for Building a Production B2B Lead Enrichment & Scoring Agent in n8n for $0.008/Run
Ruesch Manny
Ruesch Manny

Posted on Originally published at mannyverse767.gumroad.com

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 alex@stripe.com or dev@acmecorp.io. 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:

  1. Aggressive Paywalls: Enterprise vendors push for $10,000–$25,000 annual upfront contracts with rigid credit caps.
  2. 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.
  3. 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 ]
Enter fullscreen mode Exit fullscreen mode

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
    }
  };
});
Enter fullscreen mode Exit fullscreen mode

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
  }
}];
Enter fullscreen mode Exit fullscreen mode

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
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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 IF condition 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 as Enrichment: Manual Review Required instead 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.

Top comments (5)

Collapse
 
lucas_him_f7e05624dd9cc96 profile image
Lucas Him •

That 3,000-character body sample can miss evidence below the hero section. I'd keep source URLs and an extraction-status field beside the ICP score. A reusable BrowserAct Cloud Bot can feed n8n defined fields; missing fields should take your manual-review branch.

Collapse
 
mohith_kumar_05846f3211f3 profile image
Mohith kumar •

Great breakdown, and the cost per run is impressive. One thing that improved scoring accuracy a lot for us: feed the model the lead's own words from the form (what they need, timeline) alongside the firmographics. Enrichment tells you who they are; their answers tell you whether they're buying now.

Collapse
 
shawnhamw profile image
Shawn Ham •

Solid architecture — the strict JSON schema point is the one most DIY enrichment builds skip, and it's the one that saves you in production. A few additions from running enrichment pipelines:

Your pipeline enriches the company, not the contact. Scraping the landing page gives you ICP fit, but the record still needs a verified person behind it. Add a Stage 3.5: waterfall contact enrichment — cascade providers from cheapest to most expensive, and only pay for records your earlier tiers missed. Reordering cheap-first routinely cuts enrichment spend by a third because the budget providers find contacts teams were paying premium rates for.

Verify before you score. The biggest silent killer isn't bad fit, it's bad contact data. B2B contact records decay ~20–25% per year (people change jobs), so run every email through an SMTP-level verifier before it enters your scoring/CRM flow. Unverified emails don't just waste LLM tokens — they wreck sender reputation when the outreach layer fires.

Cache with a TTL. Re-scraping a domain on every run is fine at demo volume; at 10k leads/day you'll get WAF-blocked or burn your proxy budget. Cache scrape results for 30–90 days keyed by domain, and only re-fetch when the cache expires or the ICP score needs a refresh trigger.

One more data source worth adding: for B2B companies that import/export, US customs bills of lading are public records — shipment volume, trade lanes, and HS-code categories are hard-to-fake signals about what a company is actually doing (and buying). It's a nice complement to the tech signals you're extracting from landing pages.

(Disclosure: I'm a Marketing Specialist at dureach.com — an AI revenue workspace for B2B growth that includes waterfall enrichment and trade-intel data. The cheap-first cascade advice above applies whether you build or buy.)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.