DEV Community

Mozi Mohidien
Mozi Mohidien

Posted on

How to find company emails and enrich leads with AI

Cold outreach lives or dies on two things: finding the right contact and saying something worth reading. Most tools handle one of those. This article shows you how to do both in a single automated run.

We'll use an Apify actor — Company Contact Scraper & AI Lead Enricher — as the worked example. By the end you'll have a repeatable pipeline that takes a list of company URLs and returns contact data plus AI-generated sales intelligence for each one.


What the pipeline does

The actor runs in two stages:

  1. Scrape — visits each company URL, crawls key pages (homepage, contact, about, team), and extracts emails, phone numbers, and social profile links.
  2. Enrich — passes each scraped company through Claude (you supply the API key) to generate: detected industry, team size estimate, ICP fit score from 1–10, tech stack hints, and a personalized cold outreach opener.

You end up with a ranked list. The ICP fit score alone changes how you work: instead of writing to 100 companies equally, you write to the 20 that scored 7+.


Prerequisites

  • An Apify account (free tier works)
  • An Anthropic API key (for Claude enrichment)
  • Node.js or any HTTP client to hit the Apify API

Running it via the Apify API

1. Start a run

curl -X POST \
  "https://api.apify.com/v2/acts/reverend_bluebell~company-contact-ai-enricher/runs" \
  -H "Authorization: Bearer YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "companyUrls": [
      "https://acmecorp.io",
      "https://example-saas.com",
      "https://another-b2b.co"
    ],
    "anthropicApiKey": "sk-ant-YOUR_KEY_HERE",
    "maxCompanies": 10
  }'
Enter fullscreen mode Exit fullscreen mode

The response includes a runId. Save it — you'll need it to poll for results.

2. Wait for the run to finish

const apifyToken = process.env.APIFY_TOKEN;
const runId = "YOUR_RUN_ID";

async function waitForRun(runId) {
  const url = `https://api.apify.com/v2/actor-runs/${runId}`;

  while (true) {
    const res = await fetch(url, {
      headers: { Authorization: `Bearer ${apifyToken}` },
    });
    const { data } = await res.json();

    if (data.status === "SUCCEEDED") return data;
    if (data.status === "FAILED") throw new Error("Run failed");

    console.log(`Status: ${data.status} — waiting 5s`);
    await new Promise((r) => setTimeout(r, 5000));
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Fetch the results

async function getResults(runId) {
  const url = `https://api.apify.com/v2/actor-runs/${runId}/dataset/items`;

  const res = await fetch(url, {
    headers: { Authorization: `Bearer ${apifyToken}` },
  });

  return res.json();
}

const run = await waitForRun(runId);
const leads = await getResults(run.id);

console.log(leads);
Enter fullscreen mode Exit fullscreen mode

4. What comes back

Each item in the results array looks like this:

{
  "url": "https://acmecorp.io",
  "emails": ["hello@acmecorp.io", "sales@acmecorp.io"],
  "phones": ["+1-415-555-0192"],
  "linkedin": "https://linkedin.com/company/acmecorp",
  "twitter": "https://twitter.com/acmecorp",
  "industry": "B2B SaaS / HR Tech",
  "teamSize": "11-50",
  "icpFitScore": 8,
  "techStack": ["HubSpot", "Intercom", "Vercel"],
  "primaryProduct": "Employee onboarding automation platform",
  "targetDecisionMaker": "Head of People or VP HR",
  "outreachHook": "Acme's self-serve onboarding flow suggests you're scaling sales without adding headcount — exactly the scenario where [your product] cuts ramp time by half."
}
Enter fullscreen mode Exit fullscreen mode

Filtering by ICP fit

Once you have results, filter before you touch your CRM:

const qualifiedLeads = leads.filter((lead) => lead.icpFitScore >= 7);

console.log(`${qualifiedLeads.length} qualified leads out of ${leads.length} total`);
Enter fullscreen mode Exit fullscreen mode

You can also sort by score and take the top N:

const top10 = leads
  .sort((a, b) => b.icpFitScore - a.icpFitScore)
  .slice(0, 10);
Enter fullscreen mode Exit fullscreen mode

Pushing results to a CSV

If you're loading into a spreadsheet or CRM that accepts CSV:

import { writeFileSync } from "fs";

const headers = [
  "url", "emails", "phones", "industry",
  "teamSize", "icpFitScore", "techStack", "outreachHook"
];

const rows = leads.map((lead) =>
  headers.map((h) =>
    Array.isArray(lead[h]) ? lead[h].join("; ") : lead[h] ?? ""
  )
);

const csv = [headers, ...rows]
  .map((row) => row.map((v) => `"${String(v).replace(/"/g, '""')}"`).join(","))
  .join("\n");

writeFileSync("leads.csv", csv);
console.log("Saved leads.csv");
Enter fullscreen mode Exit fullscreen mode

What the AI enrichment actually costs

The actor charges $0.01 per 1,000 results — effectively free at the scale most outbound teams run. The Anthropic API usage on top depends on how many companies you process, but a 50-company run typically costs under $0.05 in Claude tokens. You control this because you supply your own key.


How it finds emails

The scraper doesn't rely on a static database. It visits the actual company website — homepage, /contact, /about, /team — and extracts email addresses from page content and mailto: links. This means you're getting current contact data, not year-old exports.

For sites that block scrapers, you can pass Apify Proxy config in the proxyConfig input field.


Other actors in the same series

If you're building a broader lead gen stack, these fit the same pattern — scrape then enrich with Claude:


Summary

The pipeline is: list of URLs → scrape contacts → Claude enrichment → ranked leads with outreach copy. You can run it via the Apify UI or trigger it programmatically from any HTTP client. At $0.01/1,000 results, the bottleneck is your Anthropic API quota, not the platform cost.

Actor link: https://apify.com/reverend_bluebell/company-contact-ai-enricher

Top comments (0)