Contact Email Finder — B2B Email Discovery & Verification
Pricing
from $20.00 / 1,000 email verifieds
Contact Email Finder — B2B Email Discovery & Verification
Company domain in, verified email addresses out. MX checks, pattern detection, deliverability scoring. Stop bouncing.
Pricing
from $20.00 / 1,000 email verifieds
Rating
0.0
(0)
Developer
Creator Fusion
Maintained by CommunityActor stats
0
Bookmarked
97
Total users
11
Monthly active users
4 days ago
Last modified
Categories
Share
Company domain in. Verified emails out. A typed contact-discovery API for sales and outreach agents: Crawlee scrape of the company site + MX validation + role-pattern fallback + 1-5 deliverability confidence score. Returns typed JSON rows — one row per domain, schema below. A Hunter.io / Apollo alternative at a fraction of the cost.
Why agents use this actor
- Deterministic typed output — every row matches the published dataset schema;
emails[]entries always carryaddress,source,found_on,mx_valid,confidence,format. - Confidence-scored + MX-validated — filter on
confidence >= 4ormx_valid = trueinstead of guessing which addresses are real. - Cost-predictable — per-event pricing ($0.03 start + $0.02 per result), no proxy by default, safe for autonomous budgets.
- No auth, cookies, or login — crawls public company pages only.
- Clear error semantics — invalid input fails fast with exit code 1 and a status message; no silent SUCCEEDED-with-0-items runs, no billable error rows in the dataset.
- Never zero results — role-pattern fallback (info@, contact@, sales@, ...) guarantees candidates even when the site exposes no addresses.
Input schema
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
domains | array of strings | no* | ["stripe.com"] (prefill) | Company domains to find emails for (e.g. stripe.com). Preferred field. |
urls | array of strings | no* | — | LEGACY. URLs accepted for v0.0.4 compatibility; hostname is extracted and treated as a domain. |
url | string | no* | — | LEGACY single-URL field. Use domains for new integrations. |
include_patterns | boolean | no | true | Always return common-format role candidates (info@, contact@, ...) even if not scraped. |
mx_validate | boolean | no | true | Verify the domain has working MX servers before scoring. Disable to save ~500ms/domain. |
max_emails_per_domain | integer | no | 10 | Cap on emails returned per domain (1-50). |
maxUrls | integer | no | 30 | LEGACY alias: cap on total domains/URLs processed (1-200). |
proxyConfiguration | object | no | off | Proxy is OFF by default to save you money. Enable Apify Proxy only on 403/429s; proxy data is billed to YOUR Apify account. |
* At least one of domains / urls / url must yield a valid domain, or the run fails with exit code 1.
Email Output Schema
One dataset row per input domain. All fields present in every row.
| Field | Type | Nullable | Description |
|---|---|---|---|
domain | string | no | Normalized bare hostname (e.g. stripe.com). |
emails | array | no | Discovered emails, sorted by confidence desc, capped at max_emails_per_domain. |
emails[].address | string | no | Lowercase address, always @<domain>. |
emails[].source | string | no | "scraped" (found on the site) or "pattern" (generated role candidate). |
emails[].found_on | string | yes | Page URL where scraped. null for source="pattern". |
emails[].mx_valid | boolean | no | Domain has working MX records. false if lookup failed or mx_validate disabled. |
emails[].confidence | integer | no | 1 (low) to 5 (high) deliverability score — see table below. |
emails[].format | string | no | role, first.last, firstlast, firstinitial.last, first_last, first, last.first, f.last, or unknown. |
mx_records | array of strings | no | MX exchange hosts, lowest priority first. Empty if none or mx_validate disabled. |
summary.scraped_count | integer | no | Returned emails with source="scraped". |
summary.pattern_count | integer | no | Returned emails with source="pattern". |
summary.high_confidence_count | integer | no | Returned emails with confidence >= 4. |
scraped_at | string | no | ISO 8601 timestamp for the domain's processing start. |
error | string | yes | Legacy field on rows from older builds only; current builds never push error rows. |
Real example row:
{"domain": "stripe.com","emails": [{"address": "careers@stripe.com","source": "scraped","found_on": "https://stripe.com/careers","mx_valid": true,"confidence": 5,"format": "role"},{"address": "info@stripe.com","source": "pattern","found_on": null,"mx_valid": true,"confidence": 3,"format": "role"}],"mx_records": ["aspmx.l.google.com", "alt1.aspmx.l.google.com"],"summary": { "scraped_count": 3, "pattern_count": 7, "high_confidence_count": 3 },"scraped_at": "2026-08-15T04:00:28.381Z"}
Confidence scoring (1-5)
| Source | MX valid | Format match | Score |
|---|---|---|---|
| scraped | yes | role/first.last | 5 |
| scraped | yes | other | 4 |
| scraped | no | role/first.last | 4 |
| pattern | yes | role | 3 |
| pattern | no | role | 2 |
Error semantics
- No valid domains in input → run FAILS with exit code 1 and status message
"No domains provided...". Nothing is pushed to the dataset (you are not billed for error rows). - Unexpected runtime error → run FAILS with exit code 1 and
"Run failed: <message>"— never a silent SUCCEEDED. - Per-domain crawl failure (site down, hard blocks) → the domain still yields a row with pattern candidates; the failure is recorded in the
SUMMARYkey-value record underfailed_crawls: [{domain, error}]. - Zero scraped emails → not an error; pattern fallback fills the row.
Agents: retry on non-zero exit; treat SUMMARY.failed_crawls as per-item degradation, not run failure.
Use from AI agents (MCP)
{"mcpServers": {"apify": {"url": "https://mcp.apify.com/?tools=apricot_blackberry/contact-email-finder","headers": { "Authorization": "Bearer <YOUR_APIFY_TOKEN>" }}}}
Works in Claude, Cursor, ChatGPT deep research connectors, and any MCP client; the input schema above is the tool's parameter schema.
Use from code
curl:
curl -X POST "https://api.apify.com/v2/acts/apricot_blackberry~contact-email-finder/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"domains": ["stripe.com"], "mx_validate": true, "max_emails_per_domain": 10}'
JavaScript (apify-client):
import { ApifyClient } from "apify-client";const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor("apricot_blackberry/contact-email-finder").call({domains: ["stripe.com"],});const { items } = await client.dataset(run.defaultDatasetId).listItems();
Python (apify_client):
from apify_client import ApifyClientclient = ApifyClient(os.environ["APIFY_TOKEN"])run = client.actor("apricot_blackberry/contact-email-finder").call(run_input={"domains": ["stripe.com"]})items = client.dataset(run["defaultDatasetId"]).list_items().items
Use from automation platforms
- n8n / Make / Zapier: use the native Apify integration and pick
contact-email-finderby name. - LangChain / LlamaIndex: use the Apify actor tool wrappers with actor id
apricot_blackberry/contact-email-finder. - Webhooks: configure an Apify webhook to fire on run completion (
ACTOR.RUN.SUCCEEDED/FAILED) to push results into your pipeline without polling.
Pricing
$0.03 start + $0.02 per result delivered (PAY_PER_EVENT).
Cost transparency — what you pay for
This actor's per-event pricing covers email-discovery work. Two other Apify costs may apply, and they're billed directly to your Apify account, not to us:
| Cost | Default | Who controls it |
|---|---|---|
| Compute units (CU) | ~0.001-0.01 CU per domain | Apify (based on runtime) |
| Apify Proxy | OFF by default | YOU — enable only if needed |
| Storage (dataset) | ~free for small runs | Apify |
Proxy specifically: unless you turn it on, the actor crawls public company pages directly with no proxy. Saves you proxy data fees ($1+/GB on residential). If you're getting 403/429s on hostile domains, enable Apify Proxy in the input — but know that Apify will bill the proxy data to your account, on top of the actor's per-event price.
FAQ
Does it verify deliverability by sending mail? No — it validates MX records (the domain can receive mail) and scores confidence from source + format. No SMTP probing, no emails sent.
Why do I get pattern emails I didn't ask for? include_patterns defaults to true so runs never return zero results. Set it to false to get only scraped addresses (a domain with an empty scrape still falls back to patterns).
Are legacy inputs still supported? Yes — urls, url, and maxUrls from v0.0.4 keep working unchanged.