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:
- Scrape — visits each company URL, crawls key pages (homepage, contact, about, team), and extracts emails, phone numbers, and social profile links.
- 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
}'
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));
}
}
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);
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."
}
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`);
You can also sort by score and take the top N:
const top10 = leads
.sort((a, b) => b.icpFitScore - a.icpFitScore)
.slice(0, 10);
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");
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:
- Job Posting AI Analyzer — extract hiring signals from job posts
- Ecommerce Product AI Enricher — enrich product listings with AI-generated descriptions and categories
- Reddit Lead AI Finder — find buying intent posts on Reddit and score them
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)