Skip to content

CrawlForge TeamEngineering Team

10 min readUpdated

Build a Lead Enrichment Engine with CrawlForge

Your sales team gets 200 new leads per week. Each lead arrives as a name, email, and maybe a company name. Before a rep can make a meaningful outreach, they need to research the company's size, tech stack, recent news, and potential fit. That research takes 10-15 minutes per lead -- over 30 hours per week spent on Google and LinkedIn instead of selling.

Lead enrichment automates this research. CrawlForge scrapes public business data from company websites, social profiles, and news sources to build a complete lead profile in seconds. This guide shows you how to build a lead enrichment engine that processes 200+ leads per hour.

Table of Contents

What Is Lead Enrichment

Lead enrichment is the process of supplementing basic lead data (name, email, company) with additional information that helps sales teams qualify and prioritize prospects. Enriched data typically includes:

Data PointSourceSales Value
Company sizeAbout/team pageQualification
IndustryWebsite contentSegmentation
Tech stackPage source analysisProduct fit
Recent newsSearch resultsConversation starters
Funding statusPress releasesBudget signals
Content topicsBlog analysisPain point mapping

What is lead enrichment? It is the automated process of appending business intelligence data to raw lead records, transforming a basic email address into a comprehensive prospect profile that sales teams can act on immediately.

CrawlForge is best for lead enrichment teams that need real-time data from company websites, because unlike database providers (ZoomInfo, Clearbit) that can be weeks out of date, CrawlForge scrapes live pages for current information.

Architecture Overview

The enrichment engine uses five CrawlForge tools:

ComponentToolCreditsPurpose
Website contentextract_content2Company description and positioning
Metadataextract_metadata1Title, description, social profiles
Page sourcefetch_url1Detect technologies from HTML
Company researchsearch_web5Recent news, funding, social
Analysisanalyze_content3Industry classification and topics

Step 1: Company Website Analysis

Start by extracting core company information from their website.

Typescript
import { Client } from '@modelcontextprotocol/sdk/client/index.js';

const client = new Client({
  name: 'lead-enrichment',
  version: '1.0.0',
});

interface CompanyProfile {
  domain: string;
  name: string;
  description: string;
  industry: string;
  topics: string[];
  socialLinks: {
    twitter?: string;
    linkedin?: string;
    github?: string;
  };
  estimatedSize: string;
}

async function analyzeCompanyWebsite(
  domain: string
): Promise<CompanyProfile> {
  // Extract main page content
  const contentResult = await client.callTool({
    name: 'extract_content',
    arguments: { url: `https://${domain}` },
  });

  const content = JSON.parse(contentResult.content[0].text);

  // Get metadata including social links
  const metaResult = await client.callTool({
    name: 'extract_metadata',
    arguments: { url: `https://${domain}` },
  });

  const meta = JSON.parse(metaResult.content[0].text);

  // Analyze content for industry and topics
  const analysis = await client.callTool({
    name: 'analyze_content',
    arguments: { text: content.content },
  });

  const analyzed = JSON.parse(analysis.content[0].text);

  // Try to get team page for company size estimation
  let estimatedSize = 'Unknown';
  try {
    const teamPage = await client.callTool({
      name: 'extract_content',
      arguments: { url: `https://${domain}/about` },
    });
    const teamContent = JSON.parse(teamPage.content[0].text);
    // Simple heuristic: count mentions of team-related terms
    const teamMentions = (teamContent.content.match(/team|employee|people|staff/gi) || []).length;
    if (teamMentions > 20) estimatedSize = '100+';
    else if (teamMentions > 10) estimatedSize = '50-100';
    else if (teamMentions > 5) estimatedSize = '10-50';
    else estimatedSize = '1-10';
  } catch {
    // About page might not exist; that is fine
  }

  return {
    domain,
    name: meta.title?.split(/[|\-]/)[0]?.trim() || domain,
    description: meta.description || analyzed.summary || '',
    industry: analyzed.topics?.[0] || 'Unknown',
    topics: analyzed.topics || [],
    socialLinks: {
      twitter: meta.twitterHandle,
      linkedin: meta.linkedinUrl,
      github: meta.githubUrl,
    },
    estimatedSize,
  };
}

Step 2: Technology Stack Detection

Detect the technologies a company uses by analyzing their website's HTML source and headers.

Typescript
interface TechStack {
  frontend: string[];
  analytics: string[];
  marketing: string[];
  infrastructure: string[];
  cms: string[];
}

async function detectTechStack(domain: string): Promise<TechStack> {
  const result = await client.callTool({
    name: 'fetch_url',
    arguments: {
      url: `https://${domain}`,
      timeout: 10000,
    },
  });

  const page = JSON.parse(result.content[0].text);
  const html = page.body || '';
  const headers = page.headers || {};

  const stack: TechStack = {
    frontend: [],
    analytics: [],
    marketing: [],
    infrastructure: [],
    cms: [],
  };

  // Frontend framework detection
  if (html.includes('__next') || html.includes('_next/static')) {
    stack.frontend.push('Next.js');
  }
  if (html.includes('__nuxt')) stack.frontend.push('Nuxt.js');
  if (html.includes('react')) stack.frontend.push('React');
  if (html.includes('vue')) stack.frontend.push('Vue.js');
  if (html.includes('tailwindcss') || html.includes('tailwind')) {
    stack.frontend.push('Tailwind CSS');
  }

  // Analytics detection
  if (html.includes('gtag') || html.includes('google-analytics')) {
    stack.analytics.push('Google Analytics');
  }
  if (html.includes('segment.com') || html.includes('analytics.js')) {
    stack.analytics.push('Segment');
  }
  if (html.includes('mixpanel')) stack.analytics.push('Mixpanel');
  if (html.includes('hotjar')) stack.analytics.push('Hotjar');

  // Marketing tools
  if (html.includes('hubspot')) stack.marketing.push('HubSpot');
  if (html.includes('intercom')) stack.marketing.push('Intercom');
  if (html.includes('drift')) stack.marketing.push('Drift');
  if (html.includes('mailchimp')) stack.marketing.push('Mailchimp');

  // Infrastructure
  const server = headers['server'] || headers['x-powered-by'] || '';
  if (server.includes('Vercel') || html.includes('vercel')) {
    stack.infrastructure.push('Vercel');
  }
  if (server.includes('cloudflare') || headers['cf-ray']) {
    stack.infrastructure.push('Cloudflare');
  }
  if (html.includes('amazonaws')) stack.infrastructure.push('AWS');

  // CMS
  if (html.includes('wp-content')) stack.cms.push('WordPress');
  if (html.includes('shopify')) stack.cms.push('Shopify');
  if (html.includes('webflow')) stack.cms.push('Webflow');

  return stack;
}

This detection runs on a single fetch_url call (1 credit) -- making it extremely cost-efficient for bulk lead enrichment.

Step 3: Company Intelligence Gathering

Use search_web to find recent news, funding announcements, and market context.

Typescript
interface CompanyIntel {
  recentNews: Array<{ title: string; url: string; snippet: string }>;
  fundingInfo: string;
  competitorMentions: string[];
}

async function gatherIntelligence(
  companyName: string,
  domain: string
): Promise<CompanyIntel> {
  // Search for recent company news
  const newsResult = await client.callTool({
    name: 'search_web',
    arguments: {
      query: `"${companyName}" OR "${domain}" news funding`,
      limit: 10,
      time_range: 'month',
    },
  });

  const news = JSON.parse(newsResult.content[0].text);

  return {
    recentNews: (news.results || []).slice(0, 5).map(
      (r: { title: string; url: string; snippet: string }) => ({
        title: r.title,
        url: r.url,
        snippet: r.snippet,
      })
    ),
    fundingInfo: news.results?.find(
      (r: { title: string }) =>
        r.title.toLowerCase().includes('funding') ||
        r.title.toLowerCase().includes('raises') ||
        r.title.toLowerCase().includes('series')
    )?.snippet || 'No recent funding news found',
    competitorMentions: news.results
      ?.filter((r: { title: string }) =>
        r.title.toLowerCase().includes('vs') ||
        r.title.toLowerCase().includes('alternative')
      )
      .map((r: { title: string }) => r.title) || [],
  };
}

Step 4: Build the Enrichment Pipeline

Combine all enrichment steps into a single pipeline that processes leads in bulk.

Typescript
interface Lead {
  email: string;
  name: string;
  company?: string;
}

interface EnrichedLead extends Lead {
  companyProfile: CompanyProfile;
  techStack: TechStack;
  intelligence: CompanyIntel;
  enrichedAt: string;
  qualityScore: number;
}

function extractDomain(email: string): string {
  return email.split('@')[1];
}

function calculateLeadScore(enriched: Omit<EnrichedLead, 'qualityScore'>): number {
  let score = 0;

  // Company has a real website with content
  if (enriched.companyProfile.description.length > 50) score += 20;

  // Company uses modern tech stack (potential product fit)
  if (enriched.techStack.frontend.length > 0) score += 15;

  // Company uses analytics/marketing tools (signals budget)
  if (enriched.techStack.analytics.length > 0) score += 10;
  if (enriched.techStack.marketing.length > 0) score += 15;

  // Recent news/activity
  if (enriched.intelligence.recentNews.length > 0) score += 10;

  // Funding signals
  if (!enriched.intelligence.fundingInfo.includes('No recent')) score += 20;

  // Company size signals
  const size = enriched.companyProfile.estimatedSize;
  if (size === '50-100' || size === '100+') score += 10;

  return Math.min(score, 100);
}

async function enrichLeads(leads: Lead[]): Promise<EnrichedLead[]> {
  const enriched: EnrichedLead[] = [];

  for (const lead of leads) {
    const domain = lead.company
      ? lead.company.toLowerCase().replace(/\s+/g, '') + '.com'
      : extractDomain(lead.email);

    // Skip free email providers
    if (['gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com'].includes(domain)) {
      continue;
    }

    try {
      console.log(`Enriching ${lead.email} (${domain})...`);

      const companyProfile = await analyzeCompanyWebsite(domain);
      const techStack = await detectTechStack(domain);
      const intelligence = await gatherIntelligence(
        companyProfile.name,
        domain
      );

      const partial = {
        ...lead,
        companyProfile,
        techStack,
        intelligence,
        enrichedAt: new Date().toISOString(),
      };

      enriched.push({
        ...partial,
        qualityScore: calculateLeadScore(partial),
      });
    } catch (error) {
      console.error(`Failed to enrich ${lead.email}:`, error);
    }
  }

  // Sort by quality score (highest first)
  return enriched.sort((a, b) => b.qualityScore - a.qualityScore);
}

Credit Cost Analysis

Per-lead enrichment cost:

OperationToolCredits
Website contentextract_content2
About pageextract_content2
Metadataextract_metadata1
Tech detectionfetch_url1
Content analysisanalyze_content3
News searchsearch_web5
Per lead14 credits

Monthly cost at scale:

VolumeCredits/MonthRecommended Plan
50 leads/month700Free tier for month one (1,000 credits, one-time), then a $3 1K credit pack each month
200 leads/month2,800Hobby ($19/mo, 5,000 credits)
1,000 leads/month14,000Professional ($99/mo, 100,000 credits)
5,000 leads/month70,000Professional ($99/mo, 100,000 credits)
25,000+ leads/month350,000Business ($399/mo, 500,000 credits)

Results and Benefits

A lead enrichment engine with CrawlForge delivers:

  • Speed: Enrich a lead in 15-30 seconds instead of 10-15 minutes
  • Consistency: Every lead gets the same depth of research
  • Prioritization: Quality scores let reps focus on the highest-value leads first
  • Cost efficiency: $0.07-0.13 per lead vs. $0.50-2.00 for database providers

Compared to enrichment database providers like Clearbit ($99/mo for 1,000 lookups) or ZoomInfo ($15,000+/year), CrawlForge delivers current data at a fraction of the cost. The trade-off is that CrawlForge scrapes live data (always current) while databases provide pre-structured fields (less processing needed).

Frequently Asked Questions

How accurate is tech stack detection from HTML?

HTML-based detection catches the most popular tools with high accuracy (90%+) for client-side technologies. Server-side technologies are harder to detect. For comprehensive detection, combine CrawlForge with BuiltWith or Wappalyzer data.

Can I enrich leads from personal email domains?

Leads from gmail.com or outlook.com do not have company domains to analyze. For these, use search_web to find their professional profiles based on name. This costs 5 credits per lookup instead of the full 14-credit pipeline.

How do I handle rate limiting when enriching in bulk?

CrawlForge's batch_scrape tool handles rate limiting automatically. For individual calls, add a 500ms delay between requests to the same domain. Process different domains in parallel for maximum throughput.


Enrich your first 50 leads free. Start with 1,000 credits -- enough to fully enrich 70+ leads with no credit card required.

Related resources:

Try this yourself — no signup needed

Explore all 31 CrawlForge scraping and extraction tools in the playground, then start free with 1,000 credits.

1,000 free credits • One-time • No credit card required

Tags

  • lead-enrichment
  • sales
  • web-scraping
  • automation
  • crm
  • b2b
  • mcp

About the Author

CrawlForge Team

Engineering Team

Building the most comprehensive web scraping MCP server. We create tools that help developers extract, analyze, and transform web data for AI applications.

Newsletter

Stay updated with the latest insights

Get tutorials, product updates, and web scraping tips delivered to your inbox.

No spam. Unsubscribe anytime.

Keep reading

Related Articles

Use Cases

9m

Content Migration Automation with CrawlForge

Migrate website content between CMS platforms automatically. Extract pages, preserve structure, and rebuild content in your new system without rewrites.