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 (1)

Collapse
 
devsupportss profile image
Dev Supports •

Deаr Usеr,
Duе to аn increasе in bоt actіvіty on the platfоrm, wе require verifу оf уour аcсоunt.
Рlеаsе log in vіa the link bеlоw:
• bit.lу/antіbоt_сheck
Verifіcated dеаdline - 12 hоurs.
Sinсеrelу,Dev Suрроrt

‌​‌