Verified Website Contact Finder πŸ“§ No Placeholder Emails avatar

Verified Website Contact Finder πŸ“§ No Placeholder Emails

Pricing

from $4.75 / 1,000 domain enricheds

Go to Apify Store
Verified Website Contact Finder πŸ“§ No Placeholder Emails

Verified Website Contact Finder πŸ“§ No Placeholder Emails

Find company contact details on any website: emails, phones and social profiles, published not guessed. Role and personal emails split for GDPR, placeholders removed, MX-checked email list so they bounce less. Failures are never charged.

Pricing

from $4.75 / 1,000 domain enricheds

Rating

5.0

(2)

Developer

Yaniv van der Stigchel

Yaniv van der Stigchel

Maintained by Community

Actor stats

1

Bookmarked

3

Total users

2

Monthly active users

21 days ago

Last modified

Share

Website Contact Finder β€” published addresses, MX-verified, never guessed

Find the emails, phone numbers and social profiles a company publishes on its own website. Every address is one the company put on a page it serves β€” nothing is guessed from a name pattern β€” and every domain is checked against DNS so you know whether it can receive mail before you send anything.

Why lists bounce, and what this does about it

The commonest complaint about lead tools in this category is bounce rate. The cause is usually one of two things, and this Actor addresses both:

Cause of a bounceWhat most tools doWhat this does
The address was invented from a pattern like first.last@domainReturns it as a contactNever generates addresses. Only ones published on the site are returned.
The domain cannot receive mail at allDoes not checkLooks up the MX record for every domain and reports domainHasMx

domainHasMx: false means every address at that domain will bounce, whoever found it. It costs about 12 ms per domain and is on by default.

isDisposable flags known throwaway-inbox hosts, and mxProvider names who runs the mail (Google Workspace, Microsoft 365, Mimecast) so you can plan how you send.

What is deliberately not done: SMTP probing. Verifying a single address by opening a connection to someone's mail server and asking whether that person exists is defeated by catch-all domains, rate-limited by every large provider, and is the technique that gets senders blocklisted. MX, disposable-host and role/personal classification is what an honest verifier can offer without sending anything.

Sort your list before you send it

Every row carries a single confidence value, so you can rank a list without writing a formula:

confidenceWhat it means
highA role address on the company's own domain, and the domain accepts mail
mediumOnly a personal address on the company's own domain
lowNo on-domain address, or no MX record, or a disposable host

bestEmail names the one address to write to β€” an on-domain role address from the contact or imprint page where one exists. It is null rather than a guess when the company published nothing on its own domain.

The placeholder problem

Most contact scrapers return whatever looks like an email address. Tested against real company homepages, that includes:

AddressWhere it came from
damian.michelfelder@example.comStripe's documentation samples
billing@example.comStripe's docs
sara.baker@acme.comMollie's marketing mockup
zoya@lumos.comMollie's mockup, invented brand
logo@2x.pngan image filename

None of those are contacts. A scraper that returns them hands you a list that looks like data and is not. This one filters them out.

Every address carries its evidence

The emails array reports, for each address, the exact page it was found on and what kind of page that is:

FieldMeaning
type: "role"info@, sales@, support@ β€” a company contact point
type: "personal"an individual's address
confidence: "high"role address on the company's own domain
confidence: "medium"personal address on the company's own domain
confidence: "low"address on a different domain
sourceUrlthe exact page it was found on
sourcePagehomepage, contact, imprint, about, legal or other

An address on the contact page or on a German Impressum is a stated contact route. The same address in a homepage footer is weaker evidence, and you can now tell the two apart.

Personal addresses are off by default. Role addresses are company contact points; personal ones are individuals' data and carry GDPR obligations for you as the data controller.

Checking the run did what you asked

Every row echoes your own input string in query, next to the domain it was resolved to and the siteUrl that actually answered. If you passed https://www.acme.nl/about and row one says acme.nl, you can see that in the first row rather than after loading five thousand.

Duplicate domains are collapsed before anything is fetched. stripe.com, https://stripe.com and www.stripe.com/pricing are one domain, so they produce one row and one charge. The number skipped is reported in SUMMARY.duplicatesSkipped. Addresses within a domain are deduplicated too.

Limits, stated up front

LimitValue
Pages fetched per domain10
Of those, contact links followed from the site's own navigation3
Social profiles per domain25
Phone numbers per domain20
Domains processed in parallel1–20
Domains per runno limit

Every one of these is also logged when the run starts and reported in the caps object of SUMMARY. The pages crawled are the homepage, /contact, /contact-us, /about, /impressum and /legal, plus up to three contact links the site itself advertises β€” which is how non-English pages such as /kontakt, /over-ons or /nl/contact-opnemen are found.

Honest reporting

errorCodeMeaningCharged
β€” (success: true)Contacts foundYes
no-contacts-publishedSite reachable, publishes nothingNo
domain-not-resolvingThe domain does not resolve β€” expired or mistypedNo
site-blockedSite is up and refuses automated clientsNo
site-timeoutNo answer in the time budget β€” worth one retryNo
site-tls-errorCertificate or handshake failure, often expiredNo
site-errorServer error on their side, may be temporaryNo
site-unreachableAnything elseNo
unparseable-domainThe input could not be read as a domainNo

Many companies use contact forms and publish no address at all. You get told that, and you are not charged for it.

The failure codes are split on purpose. Measured on a 400-domain sample (3 September 2026): of the rows a single site-unreachable code used to cover, 35% did not resolve, 28% answered with a 4xx, 15% answered normally on retry, 11% failed on TLS and 7% timed out. Each of those wants a different response β€” drop the dead domain, reach the blocked one another way, retry the flaky one β€” and one code could not tell you which you had.

Input

FieldRequiredDescription
domainsyesDomains or URLs. Duplicates are collapsed.
checkDeliverabilitynoMX lookup per domain. Default true
includePersonalnoDefault false
maxConcurrencyno1–20, default 5
proxynoOptional

No API key, no login, no cookie and no session are needed from you, and the Actor asks for no permission on your Apify account beyond its own run.

Output

Every row has the same fields whether it succeeded or failed, so you can select columns without branching. Failed rows are never charged.

FieldTypeDescription
successbooleanTrue when this row carries data. Failed rows are never charged.
querystringThe exact string you passed, echoed back.
domainstringThe website this row is about.
siteUrlstringThe host that actually answered β€” the bare domain or its www variant.
confidencestringhigh, medium or low. Null on rows that delivered nothing.
bestEmailstringThe single address to write to. Null when nothing on-domain was published.
roleEmailsarrayOn-domain role addresses (info@, sales@, support@) β€” the company's own contact points.
personalEmailsarrayOn-domain addresses belonging to named individuals. Different reliability, and different legal handling under GDPR.
otherDomainEmailsarrayAddresses on a different domain to the site β€” usually the agency that built it. Kept separate rather than presented as the company's contact.
phonesarrayPhone numbers published on the site.
socialsarrayLinks to the company's social profiles.
emailsarrayEvery address with its sourceUrl and sourcePage.
domainHasMxbooleanWhether the domain publishes an MX record. False means every address here bounces.
isDisposablebooleanWhether the domain is a known throwaway-inbox host.
mxProviderstringWho handles the domain's mail, from its MX hostname.
emailCountintegerTotal addresses found, including off-domain ones.
pagesFetchedintegerHow many pages of the site were crawled to produce this row.
errorCodestringMachine-readable failure reason. Null on success.
errorMessagestringHuman-readable explanation of the failure. Null on success.

Example β€” success

{
"success": true,
"query": "https://www.stripe.com/",
"domain": "stripe.com",
"siteUrl": "https://stripe.com",
"confidence": "high",
"bestEmail": "info@stripe.com",
"roleEmails": [
"info@stripe.com",
"sales@stripe.com"
],
"personalEmails": [
"dana.olsen@stripe.com"
],
"otherDomainEmails": [
"hello@buildagency.example"
],
"phones": [
"+18889638955"
],
"socials": [
"https://twitter.com/stripe",
"https://linkedin.com/company/stripe"
],
"emails": [
{
"email": "info@stripe.com",
"type": "role",
"confidence": "high",
"onDomain": true,
"sourceUrl": "https://stripe.com/contact",
"sourcePage": "contact"
}
],
"domainHasMx": true,
"isDisposable": false,
"mxProvider": "Google Workspace",
"emailCount": 4,
"pagesFetched": 8,
"errorCode": null,
"errorMessage": null
}

Example β€” failure

A failure carries the same fields, so nothing downstream has to branch.

{
"success": false,
"query": "contact-form-only.example",
"domain": "contact-form-only.example",
"siteUrl": null,
"confidence": null,
"bestEmail": null,
"roleEmails": null,
"personalEmails": null,
"otherDomainEmails": null,
"phones": null,
"socials": null,
"emails": null,
"domainHasMx": true,
"isDisposable": false,
"mxProvider": "Microsoft 365",
"emailCount": null,
"pagesFetched": 6,
"errorCode": "no-contacts-published",
"errorMessage": "The site was reachable but publishes no email, phone, or social profile. Many companies only offer a contact form."
}

Run summary

SUMMARY in the key-value store reports what the run achieved: requested, unique, duplicatesSkipped, withContacts, noContacts, unreachable, emailsDelivered, roleEmailsDelivered, highConfidence, domainsWithoutMx, disposableDomains, chargedRows, and the caps that applied.

Use it for

  • Role emails from a website β€” info@, sales@, support@, separated from personal
  • Company contact details β€” emails, phones and social profiles per domain
  • Lead lists and CRM enrichment from a domain list
  • Cleaning a bought list: pass the domains and keep the rows with an MX record
  • Sales prospecting without placeholder junk
  • Partner and vendor research

Use it from an AI agent (MCP)

This Actor is callable as a tool through the Apify MCP server, so Claude, ChatGPT, Cursor and VS Code can run it directly.

Add the server to your MCP client:

{
"mcpServers": {
"apify": {
"url": "https://mcp.apify.com",
"headers": {
"Authorization": "Bearer <YOUR_APIFY_TOKEN>"
}
}
}
}

Then ask for what you want in plain language β€” for example β€œfind the contact details published on this company website” β€” and the agent calls cleanfeed/verified-website-contacts with the right input. Every output field is described in the dataset schema, so the agent knows what it is getting back before it runs anything.

Call it from code

Python

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("cleanfeed/verified-website-contacts").call(run_input={
"domains": ["https://stripe.com"],
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
if item["success"]:
print(item)

JavaScript

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('cleanfeed/verified-website-contacts').call({
domains: ["https://stripe.com"],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.filter((i) => i.success));

cURL

curl -X POST "https://api.apify.com/v2/acts/cleanfeed~verified-website-contacts/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
-H 'Content-Type: application/json' \
-d '{"domains": ["https://stripe.com"]}'
If you needUse
Company data from YouTube channels insteadYouTube Channel Transcript Scraper
Open roles at those same companiesJob Postings Scraper

Limitations

  • Many companies publish no contact details at all, using a form instead. Those return errorCode: no-contacts-published, which is an honest answer rather than a failure.
  • No addresses are generated. If a company does not publish one, none is returned. Tools that emit first.last@domain guesses produce more rows and more bounces β€” that is the trade being made here.
  • Deliverability is checked at the domain, not the mailbox. domainHasMx: true means the domain accepts mail; it cannot tell you that one particular person still works there. Per-address SMTP probing is deliberately not done β€” catch-all domains defeat it and it gets senders blocklisted.
  • Up to 10 pages are fetched per domain β€” the homepage, five standard contact paths, and up to three contact links the site advertises itself. An address published only deep inside a site will be missed.
  • Off-domain addresses are separated, not discarded. An address on another domain is usually the agency that built the site, so it lands in otherDomainEmails rather than being presented as the company's contact.
  • Placeholder addresses are filtered β€” example.com, acme.com, yourdomain.com and asset filenames such as logo@2x.png that match an email pattern.
  • JavaScript is not executed. A contact form or an address rendered only by client-side code is not visible to this crawler.
  • Personal addresses are returned separately from role addresses and are off by default; under GDPR they carry different handling obligations.

FAQ

Will these emails bounce?

Two things cause most bounces, and both are handled. No address is ever generated from a name pattern β€” only ones the company published are returned. And every domain is checked for an MX record: domainHasMx: false means every address there bounces, whoever found it. Filter on confidence: "high" for the rows that pass both.

Do you verify each address by SMTP?

No, on purpose. Probing a mail server to ask whether one person exists is defeated by catch-all domains, rate-limited by every large provider, and is the technique that gets senders blocklisted. Domain-level MX, disposable-host detection and role/personal classification are what can be reported honestly without sending anything.

How is this different from a regex over the page?

A regex also returns fake data. Placeholder domains (example.com, acme.com), asset filenames like logo@2x.png, and the web agency's own address all match an email pattern. Those are filtered or separated here, and each address that survives carries the page it was found on.

What is the difference between role and personal emails?

Role addresses (info@, sales@, support@) are company contact points. Named addresses belong to individuals β€” different reliability, and different handling under GDPR. They are returned in separate fields, and personal ones are off by default.

Which address should I actually write to?

bestEmail. It is the on-domain role address from the contact or imprint page where one exists, and null rather than a guess when the company published nothing on its own domain.

Why did a domain return nothing?

Plenty of companies publish no address and use a contact form instead. That returns errorCode: no-contacts-published, which is a real answer β€” a guessed address that bounces costs you sender reputation. You are not charged for it.

What are otherDomainEmails?

Addresses found on the site but hosted elsewhere, typically the agency that built it. They are kept rather than dropped, but not presented as the company's own contact.

Will I be charged twice for the same domain?

No. stripe.com, https://stripe.com and www.stripe.com/pricing are collapsed to one domain before anything is fetched, so they produce one row and one charge. SUMMARY.duplicatesSkipped reports how many were collapsed.

Does it work on non-English sites?

Yes. Alongside the standard paths it follows the contact links a site advertises in its own navigation, which is how /kontakt, /over-ons, /nl/contact-opnemen and /impressum.html are reached. German imprint pages are legally required to carry contact details and are among the most reliable sources.

Can I process a list of domains?

Yes, with no cap on the number. Pass the whole list and tune maxConcurrency; one row is returned per distinct domain, including the ones with nothing published.

Notes

Only publicly published company contact details are collected, from pages the company itself serves (homepage, contact, about, Impressum, legal). No login, no purchased databases, no guessed addresses.