Website Contact & Email Extractor — Verified Emails + Socials
Pricing
from $8.00 / 1,000 contact records
Website Contact & Email Extractor — Verified Emails + Socials
Turn a list of website URLs into clean CRM rows: verified emails (MX-checked, role-tagged), E.164 phones, and social profiles — one de-duplicated row per company. You pay only per company we find a real contact for; empty and failed sites are free. Junk addresses and numbers are filtered out.
Pricing
from $8.00 / 1,000 contact records
Rating
0.0
(0)
Developer
Blackcube
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
7 days ago
Last modified
Categories
Share
| Website Contact & Email Suite • 9 Actors, one codebase, one contact record billed once | ||
| Website Contact & Email Extractor ➤ You are here | B2B Leads Finder Verified Emails, Phones & Socials | Company Contact Scraper Emails, Phones & Socials |
| Phone Number Scraper Business Numbers From Any Website | MX Verified Email Finder Deliverable Business Emails | Website Email Scraper Verified Emails From Any Site |
| Decision Maker Email Finder Skip the info@ Inbox | Contact Page Scraper Emails, Phones & Socials by Site | Email Finder Business Emails From a List of Domains |
More from this account: YouTube Transcript Suite · Career Site & ATS Jobs Suite · Google News Suite · Keyword Research Suite · Shopify Store Intelligence Suite · eBay Data Suite · Amazon Reviews Suite · Reddit · Meta Ad Library · Vinted · Trustpilot Review Intelligence Suite · App Store & Google Play Reviews Suite · Business Reviews Suite · Amazon Product Data Suite
Feed it a list of websites. Get back one clean CRM row per company — verified emails, phone numbers, and social profiles.
Point it at any list of company websites (a Google Maps export, a Shopify store list, a jobs-board scrape, or a CSV your sales team already has) and it returns a deduplicated contact record for each one: every email it can find, MX-verified and tagged by role, phone numbers normalized to E.164, and canonical LinkedIn / X / Facebook / Instagram / YouTube / TikTok / GitHub profile URLs.
You pay per company we actually reach a contact for. Not per page crawled. A website that returns nothing, times out, or no longer exists costs you nothing — it still appears in your results with a reason, so gaps in your list stay auditable. A record has to carry a real email or phone number to be billable: if all we could find was a social profile link, you still get the record, free.
Unofficial — not affiliated with, endorsed by, or connected to any of the websites it visits. It reads only data those sites already publish publicly to any browser.
Why this one
1. Predictable billing — per company, not per page
Most contact scrapers bill per page crawled, so a headline like "$2 per 1,000" turns into a much larger invoice once a single site with deep navigation gets crawled for hundreds of pages. That mismatch is the most common complaint in this category.
Here, the unit you are billed for is the unit you care about: one company record. A company whose contact details take 12 pages to find costs exactly the same as one that gives them up on the homepage. And because the total page budget is bounded by websites × maxPagesPerDomain before the run even starts, a runaway crawl is structurally impossible.
| This actor | Typical per-page scrapers | |
|---|---|---|
| Billing unit | 1 company record | 1 page crawled |
| Website with no contacts | Free | Charged for every page |
| Dead / timed-out domain | Free, with a reason | Charged for the attempt |
| Record with no email or phone | Free | Charged as a result |
| Cost of a deep site | Unchanged | Multiplies without warning |
| Email verification | Included | Usually a paid add-on |
2. Clean data — the junk is filtered out, not shipped
Regex-only extractors are notorious for reporting things like //cdn.jsdelivr.net/npm/bootstrap@5.2.3 as an email address, and for emitting "uncertain" phone columns full of digit noise like 035500000000002. This actor validates before it delivers:
- Emails are checked against the public suffix list, so image filenames (
logo@2x.png), CDN package specs (bootstrap@5.2.3), Sentry DSNs, build hashes, and placeholder addresses (you@example.com,firstname.lastname@…) never reach your dataset. - Phone numbers are parsed with Google's libphonenumber and returned only if they are genuinely valid, normalized to E.164 (
+14155550132). Order numbers, dates, and prices are dropped. There is no "uncertain" column, because uncertain data is not a deliverable. - Social links are canonicalized to real profile URLs; share and intent widgets (
facebook.com/sharer,twitter.com/intent/tweet) are discarded.
3. It finds addresses other scrapers miss
Contact details are deliberately hidden from naive scrapers. This one reads all of it:
mailto:links and Cloudflare-obfuscated addresses (data-cfemail//cdn-cgi/l/email-protection, XOR-decoded)- HTML-entity obfuscation (
info@acme.com) and bracketed forms (info [at] acme [dot] com) - JSON-LD structured data —
Organization,LocalBusiness,ContactPoint, andsameAsblocks, which carry the authoritative company name, email, phone, and postal address - Plain text and footers
And it looks in the right places. With a limited page budget, which pages get crawled decides whether you get a decision-maker's address or nothing, so pages are ranked before fetching: legally-mandated imprint pages first (/impressum, /mentions-legales — in the EU these always carry a real address), then contact, about, team, support, press. Non-English paths are recognized, so a German or French site does not come back empty.
4. Verified emails, honestly described
Every address is checked and labelled — included in the per-record price, not a paid add-on:
status | Meaning |
|---|---|
valid | The domain publishes a working mail exchanger and the address is well-formed. |
invalid | The domain has no mail exchanger, or the address is malformed. It will bounce. |
disposable | A throwaway-inbox provider (mailinator, guerrillamail, …). Worthless as a lead. |
risky | Real, but lower confidence: a free consumer provider (gmail, outlook) or a shared role inbox (info@, support@). |
unknown | The DNS lookup itself failed. Never reported as invalid — you are not told an address is dead when we simply could not check. |
What this check is: syntax validation, MX/DNS resolution, and disposable / free-provider / role-address classification.
What it is not: a per-mailbox SMTP probe. Apify's infrastructure blocks outbound port 25, so no actor running natively on the platform can perform a true RCPT-TO handshake — and even where it is possible, Gmail, Outlook, and Yahoo tarpit those probes from datacenter IPs. Tools that advertise "SMTP verified" from inside Apify are describing a check they cannot run. This one tells you exactly what it did, and the reason field on every result says why it reached that status.
5. Role tagging and decision-maker filtering
Every email is classified: executive (founder, CEO, owner), personal (a named individual), sales, marketing, support, hr, finance, legal, info (generic inbox), or other. Use Only these contact roles to keep just the ones you want, or flip Decision-makers only to drop every generic inbox in one click.
Each record also carries a primaryEmail — the single best contact, picked by ranking the company's own domain above third-party addresses, then executives above named people above sales above generic inboxes. When you need exactly one address per company for a mail merge, that field is it.
6. One row per company, already deduplicated
Paste acme.io, https://www.acme.io/, and https://acme.io/pricing?utm_source=news and you get one record, charged once. Deduplication happens by registrable domain, across the whole run, on both input and output. No second "merge & deduplicate" actor required.
Input
| Field | Type | Default | What it does |
|---|---|---|---|
| startUrls | array | — | The company websites. Full URLs or bare domains both work. Several URLs for one company collapse into one row. |
| domains | array of strings | (empty) | Alternative plain-text list of domains, merged with the above. |
| maxPagesPerDomain | integer | 10 | Hard cap on pages per company (1–50). Total run pages can never exceed websites × this. |
| verifyEmails | boolean | true | Run the MX/DNS deliverability check. Included in the price. |
| roleFilter | array | (empty) | Keep only these roles. Empty returns every address. |
| excludeGenericEmails | boolean | false | Decision-makers only: keep executive and personal, drop every generic inbox. |
| skipDomainsWithoutContacts | boolean | false | Omit the free "nothing found" rows entirely. They are never charged either way. |
| respectRobotsTxt | boolean | false | Skip pages disallowed by robots.txt. Off by default, because many sites disallow crawlers wholesale for pages any browser can open. |
| maxConcurrency | integer | 20 | Pages fetched in parallel across all websites. |
| proxyConfiguration | object | Apify Proxy (auto) | Automatic selection picks a datacenter IP — enough for ordinary company sites, and what keeps the price low. Switch to residential only for hardened targets. |
Output
One JSON record per company. See .actor/sample-output.json for complete records from a real run.
// abridged from a real run — see .actor/sample-output.json for full records{"domain": "fastmail.com","companyName": "Fastmail","primaryEmail": "press@fastmail.com","emails": [{"address": "press@fastmail.com","role": "marketing","foundOn": "https://www.fastmail.com/company/about/","verification": {"status": "risky","hasMx": true,"isDisposable": false,"isFreeProvider": false,"isRoleAddress": true,"reason": "roleAddress"}}],"phones": ["+61294750859"],"socials": {"linkedin": "https://www.linkedin.com/company/fastmail","twitter": "https://x.com/Fastmail","facebook": "https://www.facebook.com/Fastmail","github": "https://github.com/fastmail"},"emailCount": 5,"phoneCount": 1,"pagesCrawled": 8,"errorReason": null}
| Field | Type | Notes |
|---|---|---|
domain | string | Registrable domain. The dedup key and the billing unit. |
startUrl, inputUrls | string, array | Where the crawl began, and every input line that mapped to this company. |
companyName, description, address | string | null | From JSON-LD, og:site_name, or the page title/meta. |
primaryEmail | string | null | The single best contact. Identical to emails[0].address. |
emails | array | { address, role, foundOn, verification }, best contact first. |
phones | array | Valid numbers only, E.164. |
socials | object | Canonical profile URLs: linkedin, twitter, facebook, instagram, youtube, tiktok, github. |
emailCount, phoneCount | integer | For filtering a CSV export without parsing arrays. |
pagesCrawled, crawledUrls | integer, array | Exactly which pages were fetched, in order. |
scrapedAt | string | ISO-8601. |
errorReason, errorMessage | string | null | null on a billed record; set on a free row. |
Dataset views
Contacts and Skipped & failed (free). Views project columns but cannot filter rows, so both kinds of row appear in both views with the other's columns blank. To separate them in code, split on errorReason: it is null on a contact record and set on a free row.
Why a website returns no contacts
Every one of these is a free row — never billed — and tells you what happened, so a gap in your lead list is explained rather than silent.
errorReason | Meaning |
|---|---|
noContactsFound | Crawled fine, but the site publishes no contact details (or your filters removed them all). |
domainNotResolved | The domain does not resolve — expired, parked, or mistyped. |
connectionFailed | Connection refused, or an unusable TLS certificate. |
timeout | The site did not respond in time. |
notFound | The start URL returned 404/410. |
accessDenied | The site refused the request (401/403). |
serverError | The site returned a 5xx. |
blocked | Rate-limited or stopped by bot protection. Try residential proxies. |
invalidInput | That input line is not a usable website URL or domain. |
unknown | Something else; errorMessage carries the detail. |
Tips for better results
- Raise
maxPagesPerDomainfor stubborn sites. The default of 10 is tuned for cost. Large corporate sites that bury contact details behind several navigation levels do better at 20–30. The cost per company does not change. - Use
excludeGenericEmailsfor outreach, leave it off for coverage. Generic inboxes are often the only published address for small businesses. - Switch to residential proxies only if you see
blockedrows. Datacenter is cheaper and sufficient for the large majority of company websites. - Chain it after any list-building actor. Google Maps, Shopify, jobs boards, directories — anything that produces website URLs feeds straight in.
- Raise the run timeout for very large lists. The default is 1 hour, which comfortably covers roughly 1,500–2,000 websites at the default page budget. Results are written and billed per company as each one finishes, so even if a run does hit its timeout you keep every company already completed — you never lose (or pay twice for) finished work.
Legal & responsible use
This actor collects only publicly published business contact information — the addresses and numbers companies put on their own websites specifically so customers can reach them. It does not log in, bypass authentication, or touch private data.
Contact data about identifiable people is personal data under the GDPR and similar laws. You are the controller for how you use it: you are responsible for having a lawful basis (commonly legitimate interest for B2B outreach), for honoring opt-outs and suppression lists, and for meeting the disclosure and marketing rules in your recipients' jurisdictions. The role and isRoleAddress fields exist partly to help you target shared business inboxes rather than individuals where that is the safer choice.
Unofficial project. Uses only publicly available data. Not affiliated with any website it visits.
Use it from n8n, MCP, the API or a schedule
Built to be called by a workflow, not only from the Store form. The Actor is vonsensey/website-contact-email-extractor; every snippet below sends {}, which runs the defaults shown on the form — replace it with your own input.
n8n
Install the Apify community node (@apify/n8n-nodes-apify under Settings → Community Nodes, or search "Apify" on n8n Cloud). Add Apify → Run Actor with Actor vonsensey/website-contact-email-extractor and your input JSON, then Apify → Get Dataset Items on the run's defaultDatasetId and pipe the rows anywhere. For scheduled runs, the On new Apify Event trigger fires when a run of this Actor finishes.
MCP (Claude, Cursor, VS Code, any MCP client)
{"mcpServers": {"apify": {"url": "https://mcp.apify.com?tools=vonsensey/website-contact-email-extractor","headers": {"Authorization": "Bearer <YOUR_APIFY_TOKEN>"}}}}
Your agent then calls vonsensey/website-contact-email-extractor as a tool with the same input the form takes and reads the dataset back.
REST API (one call, rows in the response)
curl -X POST "https://api.apify.com/v2/acts/vonsensey~website-contact-email-extractor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-H "Content-Type: application/json" -d '{}'
Python
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("vonsensey/website-contact-email-extractor").call(run_input={})for row in client.dataset(run["defaultDatasetId"]).iterate_items():print(row)
JavaScript
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('vonsensey/website-contact-email-extractor').call({});const { items } = await client.dataset(run.defaultDatasetId).listItems();
Make, Zapier, LangChain, CrewAI
The Apify app in Make and Zapier has a Run an Actor module: pick vonsensey/website-contact-email-extractor. In LangChain and CrewAI the Apify tool wrappers take the same Actor id. A daily schedule needs nothing but the Console: Schedules → Create → this Actor → cron, and the dataset fills on its own.
Run it without configuring anything — Get emails from a list of websites, a ready-made example you can start as-is or copy.
Use cases
- Turn a domain list into CRM rows. Company URLs in, one de-duplicated row out per company with email, phone and social profiles.
- Enrich a list you already have. You have the companies but not the contacts — this fills the gap without a data-vendor subscription.
- Reach a person, not an inbox. Prefer a named contact over the generic info@ address where the site publishes one.
- Re-run weekly. Sites add and change contacts constantly; a schedule keeps the list current instead of ageing out.
Run it on a schedule
A one-off pull answers a question; a schedule answers it every day without you. Open Schedules in the Apify Console, point a cron at this Actor, and the dataset keeps filling on its own — no server, no cron box, no babysitting. Everything here is built to be re-run: you are billed per contact record delivered, so a scheduled run that finds nothing new costs nothing.
FAQ
Are the email addresses verified?
Yes. Every address is checked against the domain's real mail servers (MX) and tagged role vs personal, so an undeliverable address does not reach your CRM.
Do I pay for websites that return nothing?
No. Sites that are unreachable, blocked or simply have no published contact details come back as free rows with a reason.
Can I pass bare domains instead of full URLs?
Yes. example.com and https://example.com/contact are both accepted, and both resolve to the same company row.
How is this different from a lead database?
Nothing here is a stale purchased list. Every row is read from the live website at the moment you run it.
Something wrong, or a field you need that is missing? Open an issue on the Issues tab — it is read and it gets fixed. If this saved you time, a rating on the Store page helps the next person find it.