Verified email finder
Quick answer
- 01What is it?
- Builds a list of verified business emails from Google Maps, Google SERPs, or a user-supplied URL list. Verification happens inside the same Apify run, no third-party verifier needed. It brings Apify's specific operating context into email marketing, so the agent is guided by a sharper source than a generic prompt.
- 02Inputs
- Context for email marketing: your goals, audience, constraints, and any source material the skill asks for.
- 03Output
- A ready-to-use result for email marketing: the analysis, copy, or recommendations the agent produces.
Add this skill
Install as a package
Installs this one skill package for your coding agent, including any supporting files that skill ships with — not every skill in the repository. Read the tutorial.
$ npx skills add apify/awesome-skills --skill apify-verified-email-finderSkill instructions
The instruction file for this skill. The skill also includes other files you need to install to use it.
Verified Email Finder
Return a list of verified business emails by routing the user's input to the right Apify Actor and turning on the leads enrichment + email verification add-ons in a single run. No third-party verifier (Hunter, NeverBounce, Apollo) needed — verification happens inside the same Actor run.
Prerequisites
(No need to check it upfront)
The skill supports two execution paths. Pick the one that matches your environment — Steps 4 and 5 show commands for both.
MCP path (default in Claude sessions, recommended). If the Apify MCP server is connected, no setup is needed — auth runs through the user's Apify account. Use the call-actor and get-dataset-items MCP tools.
Script path (CLI / scheduled / non-Claude execution). Requires:
.envfile withAPIFY_TOKEN- Node.js 20.6+ (for native
--env-filesupport)
Workflow
Copy this checklist and track progress:
Task Progress:
- [ ] Step 1: Collect the six required anchor inputs
- [ ] Step 2: Route to the correct Actor (confirm if ambiguous)
- [ ] Step 3: Build the Actor input (verification always ON)
- [ ] Step 4: Run the Actor and wait
- [ ] Step 5: Apply the result-scope filter, deduplicate, and render
Step 1: Collect the Six Required Anchor Inputs
Ask all six as one block before any Actor call. Don't bundle Actor-specific optional fields (country code, language, max pages) into this round — surface those as follow-ups.
- What do you have to start with? —
location query/SERP keyword/URL list. This drives the routing decision. - The actual input — the location string, the keyword(s), or the URLs themselves.
- Department filter — one or more of:
c_suite,product,engineering_technical,design,education,finance,human_resources,information_technology,legal,marketing,medical_health,operations,sales,consulting. Default isany(leave the array empty), but ask every time. - Max contacts per domain / business — passed as
maximumLeadsEnrichmentRecords. Default3, but ask every time. - Output format —
CSVorJSON. Ask every time. - Result scope — which leads to keep in the deliverable. The Actor always runs the same way (verification always on); this only controls post-run filtering. Pick one:
verified-only(default) — only leads withemailVerification.result == "ok". Safest for cold email.verified-plus-catchall—okpluscatch_all. Catch-all is often deliverable but unprovable.all-emails— any lead with a non-emptyemail, regardless of verification.with-phone— any lead with a non-empty phone number, regardless of email status. Use for call campaigns.everything— every lead the Actor returned, even incomplete ones.
Step 2: Route to the Correct Actor
Inspect anchor #1 and pick the Actor.
| User has to start with | Actor ID | Use when |
|---|---|---|
| Location + business type ("dentists in Berlin") | compass/crawler-google-places | Local leads list from Maps listings; best when user wants address / phone / hours too |
| Keyword / search query ("best CRM software") | apify/google-search-scraper | Contacts from whichever sites Google ranks for a topic |
| Pre-existing URL list (pasted, file path) | vdrmota/contact-info-scraper | User already has domains; cheapest route since no discovery step |
All three Actors share the same three add-on fields, so verification behavior is identical across routes.
Decision examples
| User says | Route |
|---|---|
| "Dentists in Munich" / "Lawyers in Prague" | Maps |
| "Marketing contacts at the top results for 'AI agent builder'" | Search |
| "Find emails for these 5 URLs: acme-co.example, demo-co.example..." | URL list |
| "Find HR contacts at Fortune 500 companies" | Ask: SERP for "Fortune 500 HR" or a URL list? |
| "Find contacts at SaaS companies in Berlin" | Ask: Maps for "SaaS companies in Berlin" or SERP for "SaaS companies Berlin"? Maps works best when businesses are Google-Maps-listed. |
| (User pastes both a SERP keyword AND a URL list) | Ask: run one route, the other, or both as separate deliverables? |
Ambiguity rule: if anchor #1 is unclear, ask one follow-up before running. Never burn Actor compute on a guessed route.
Mixed deliverables: if the user explicitly asks for two routes in one deliverable, run both Actors and concatenate. The Source column makes the mix clear; dedupe by email across the combined output.
Step 3: Build the Actor Input
Always set these three fields, regardless of which Actor is selected.
| Field | Value |
|---|---|
maximumLeadsEnrichmentRecords | anchor #4 (default 3, min 1) |
leadsEnrichmentDepartments | anchor #3 as array, or [] if "any" |
verifyLeadsEnrichmentEmails | true (always) — guard rail, never set to false |
Full per-Actor input parameters and example payloads are in reference/apify-actor-usage.md.
URL-list pre-validation: before submitting URLs to vdrmota/contact-info-scraper, parse each one and check it is http/https and parseable. Skipped entries must appear in the output as skipped — invalid URL, never silently dropped.
Step 4: Run the Actor
Maps and SERP runs with leads enrichment can take several minutes per query. Raise the timeout for large jobs.
MCP path (default in Claude sessions):
Call the call-actor tool:
actor: one of the three Actor IDs (compass/crawler-google-places,apify/google-search-scraper,vdrmota/contact-info-scraper)input: the JSON payload from Step 3callOptions:{"timeout": 1800, "memory": 4096}for a generous budget
The tool returns runId and datasetId. If status is still RUNNING, poll with get-actor-run (waitSecs up to 45) until SUCCEEDED. Capture both IDs for the run_metadata.json sidecar.
Script path (CLI / scheduled use):
node --env-file=.env ${CLAUDE_PLUGIN_ROOT}/reference/scripts/run_actor.js \
--actor "ACTOR_ID" \
--input 'JSON_INPUT' \
--output YYYY-MM-DD_verified-emails.csv \
--format csv \
--timeout 900
Use --format json for JSON. The script writes the raw dataset to disk; Step 5 still applies the spurious-match + scope filters on top.
Step 5: Filter, Deduplicate, and Render
Pull the dataset:
- MCP path: call
get-dataset-itemswith thedatasetIdfrom Step 4. Use thefieldsparameter (e.g.,title,searchString,countryCode,city,address,phone,website,leadsEnrichment) andclean: trueto keep the response small. For datasets that still exceed the response cap, fetch directly viacurl https://api.apify.com/v2/datasets/<id>/items?fields=...&clean=trueand pipe throughjq. - Script path: the raw dataset is already on disk in the file from Step 4.
Each record contains business fields plus a leadsEnrichment array (Maps, SERP) or top-level lead fields (URL list). Each lead has a departments array, a companyWebsite, and an emailVerification object with result (ok / invalid / disposable / catch_all / unknown / error) and quality (good / risky / bad).
-
Spurious-match filter (mandatory, always on). Apply this first, before any other filter. The lead-enrichment service can return global-fallback leads when no local match exists (real case observed: a single US-zoo CFO whose
companyWebsite=zoo.orgwas attributed to 8 unrelated Polish zoos because the matcher latched onto thezoosubstring). Drop any lead whosecompanyWebsitehostname doesn't equal the source URL's hostname (striphttps?://, leadingwww., anything after/; lowercase). Count drops inrun_metadata.jsonand call them out in the deliverable header if non-zero. -
Filter by result scope (anchor #6). Applied second.
Scope Row-keep logic verified-onlyemailVerification.result == "ok"verified-plus-catchallemailVerification.result in {"ok", "catch_all"}all-emailsemailis non-empty (any result, including missing verification)with-phonephone(or company phone) is non-empty (regardless of email)everythingkeep every lead, no filter -
Dedupe: group by lowercased email; keep the first occurrence and merge
Source Query or URLif the same email appears from multiple sources. Forwith-phonerows that have no email, dedupe by lowercased phone instead. -
Empty-result surfacing: if the department filter (anchor #3) produces zero leads for a given domain, include a row for that domain with
Email = ""andEmail Verification Status = "no leads matched filter". Do not silently drop it. This is separate from the result-scope filter above — empty-domain rows are inserted before scope-filtering and always shown.
Output row schema (16 columns, including Departments) and per-format rendering details are in reference/output-formats.md.
Worked Examples
- Maps: examples/example-maps-input.md
- SERP: examples/example-search-input.md
- URL list: examples/example-url-list-input.md
Quality Rules (always enforce)
- Guard rail: never submit a run with
verifyLeadsEnrichmentEmails: false. - Provenance & traceability: populate the
Sourcecolumn on every row; carry ApifyrunId+datasetIdinrun_metadata.json. - No fabrication: missing dataset fields stay blank.
- Deliverable header transparency: state the active result scope and the spurious-match drop count; offer to re-render under a different scope.
- Ambiguity confirm: if anchor #1 is unclear, ask before running.
Cost & Pricing
Email verification is charged only for decisive results (ok / invalid / disposable); catch_all / unknown / error are free. Leads enrichment is charged per successfully extracted lead. Check the Apify console for live rates (they vary by subscription tier and change over time).
Error Handling
See reference/troubleshooting.md.
Supporting file: examples/example-maps-input.md
Example — Maps route ("dentists in Berlin")
Anchors
| # | Value |
|---|---|
| 1 What do you have | location query |
| 2 Input | dentists in Berlin |
| 3 Departments | marketing, c_suite |
| 4 Max contacts | 3 |
| 5 Format | CSV |
| 6 Scope | verified-only |
Optional follow-ups: language=en, maxCrawledPlacesPerSearch=20, scrapePlaceDetailPage=true (so address / phone come back).
Routing: unambiguous → compass/crawler-google-places.
Actor input
{
"searchStringsArray": ["dentists"],
"locationQuery": "Berlin, Germany",
"maxCrawledPlacesPerSearch": 20,
"language": "en",
"scrapePlaceDetailPage": true,
"maximumLeadsEnrichmentRecords": 3,
"leadsEnrichmentDepartments": ["marketing", "c_suite"],
"verifyLeadsEnrichmentEmails": true
}
Run it via the MCP call-actor tool or the script (SKILL.md Step 4). expected_leads = 20 × 3 = 60 — under the 200-lead warning threshold, no confirm needed.
Sample output
| Source | Business | Full Name | Job Title | Seniority | Status | Quality | Phone | City | |
|---|---|---|---|---|---|---|---|---|---|
| Maps | Example Dental A | Sample Contact 1 | Marketing Lead | manager | contact1@dental-a.example (mailto:contact1@dental-a.example) | ok | good | +49 30 5550001 | Berlin |
| Maps | Example Dental B | Sample Contact 2 | Owner | c_suite | contact2@dental-b.example (mailto:contact2@dental-b.example) | ok | good | +49 30 5550002 | Berlin |
| Maps | Example Dental C | Sample Contact 3 | CMO | c_suite | contact3@dental-c.example (mailto:contact3@dental-c.example) | ok | good | +49 30 5550003 | Berlin |
Deliverable header: Scope verified-only, spurious-match drops: 0. Ask to re-render under a wider scope to include catch-all / unknown. A run_metadata.json sidecar is written next to the CSV.
Supporting file: examples/example-search-input.md
Example — SERP route ("best CRM software")
Anchors
| # | Value |
|---|---|
| 1 What do you have | SERP keyword |
| 2 Input | best CRM software |
| 3 Departments | sales |
| 4 Max contacts | 3 |
| 5 Format | JSON |
| 6 Scope | verified-only |
Optional follow-ups: countryCode=us, languageCode=en, maxPagesPerQuery=1.
Routing: unambiguous → apify/google-search-scraper.
Actor input
{
"queries": "best CRM software",
"maxPagesPerQuery": 1,
"countryCode": "us",
"languageCode": "en",
"maximumLeadsEnrichmentRecords": 3,
"leadsEnrichmentDepartments": ["sales"],
"verifyLeadsEnrichmentEmails": true
}
Run via MCP call-actor or the script (SKILL.md Step 4). expected_leads ≈ 10 × 3 = 30 — well under the warning threshold.
Sample output (truncated to one contact)
{
"runMetadata": {
"runId": "AbCdEfGhIjK",
"datasetId": "LmNoPqRsTuV",
"actor": "apify/google-search-scraper",
"finishedAt": "2026-05-18T13:02:11Z",
"consoleUrl": "https://console.apify.com/actors/runs/AbCdEfGhIjK"
},
"filter": {"scope": "verified-only", "rowKeepLogic": "emailVerification.result == 'ok'"},
"contacts": [
{
"source": "Search",
"sourceQueryOrUrl": "best CRM software",
"business": "acme-crm.example",
"fullName": "Sample Contact 1",
"jobTitle": "Account Executive",
"seniority": "manager",
"email": "contact1@acme-crm.example",
"emailVerificationStatus": "ok",
"emailVerificationQuality": "good",
"linkedin": "http://www.linkedin.com/in/example-user-1",
"businessWebsite": "https://www.acme-crm.example",
"dateScraped": "2026-05-18T13:02:11Z"
}
/* + 2 more, schema in reference/output-formats.md */
]
}
A run_metadata.json sidecar is written next to the JSON output.
Supporting file: examples/example-url-list-input.md
Example — URL-list route (5 URLs in)
Anchors
| # | Value |
|---|---|
| 1 What do you have | URL list |
| 2 Input | https://acme-pay.example, https://demo-card.example, https://sample-bank.example, htp://broken-url, https://example-biz.example |
| 3 Departments | any (empty array) |
| 4 Max contacts | 2 |
| 5 Format | CSV |
| 6 Scope | with-phone (call campaign — keep any lead that has a phone, regardless of email) |
Optional follow-ups: maxRequestsPerStartUrl=10, maxDepth=2, mergeContacts=true.
Routing: unambiguous → vdrmota/contact-info-scraper.
Pre-validation
htp://broken-url is skipped (scheme not http/https) → emitted as a skipped — invalid URL row. The other four URLs go to the Actor.
Actor input (only valid URLs)
{
"startUrls": [
{"url": "https://acme-pay.example"},
{"url": "https://demo-card.example"},
{"url": "https://sample-bank.example"},
{"url": "https://example-biz.example"}
],
"maxRequestsPerStartUrl": 10,
"maxDepth": 2,
"mergeContacts": true,
"proxyConfig": {"useApifyProxy": true},
"maximumLeadsEnrichmentRecords": 2,
"leadsEnrichmentDepartments": [],
"verifyLeadsEnrichmentEmails": true
}
Run via MCP call-actor or the script (SKILL.md Step 4). expected_leads = 4 × 2 = 8 — well under the threshold.
Sample output
Under with-phone scope, every row needs a non-empty phone — verification status is informational, not a gate.
| Source | Business | Full Name | Job Title | Status | Quality | Phone | |
|---|---|---|---|---|---|---|---|
| URL list | acme-pay.example | Sample Contact 1 | Head of BD | contact1@acme-pay.example (mailto:contact1@acme-pay.example) | ok | good | +1 415 555 0101 |
| URL list | acme-pay.example | Sample Contact 2 | Director, Partnerships | contact2@acme-pay.example (mailto:contact2@acme-pay.example) | catch_all | risky | +1 415 555 0102 |
| URL list | demo-card.example | Sample Contact 3 | VP Sales | contact3@demo-card.example (mailto:contact3@demo-card.example) | ok | good | +1 415 555 0201 |
| URL list | sample-bank.example | Sample Contact 4 | Growth Lead | unknown | +1 415 555 0301 | ||
| URL list | example-biz.example | Sample Contact 5 | Account Manager | contact5@example-biz.example (mailto:contact5@example-biz.example) | ok | good | +1 415 555 0401 |
| URL list | htp://broken-url | — | — | — | skipped — invalid URL | — | — |
Deliverable header: Scope with-phone, spurious-match drops: 0, 1 URL pre-skipped. Ask to re-render under verified-only to narrow.
Supporting file: reference/apify-actor-usage.md
Apify Actor Usage
Exact input parameters per Actor. Every payload includes the three shared add-on fields:
| Field | Value | Notes |
|---|---|---|
maximumLeadsEnrichmentRecords | anchor #4 (default 3, min 1) | 0 disables enrichment — never use. |
leadsEnrichmentDepartments | anchor #3 as array, or [] for any | Enum: c_suite, product, engineering_technical, design, education, finance, human_resources, information_technology, legal, marketing, medical_health, operations, sales, consulting. |
verifyLeadsEnrichmentEmails | true always | Guard rail. Adds emailVerification per lead. Never false. |
1. Google Maps — compass/crawler-google-places
Anchor #1 is a location + business type.
| Field | Type | Required? | Notes |
|---|---|---|---|
searchStringsArray | string[] | yes | Business type(s), e.g. ["dentists"]. |
locationQuery | string | yes | Free-form location, e.g. "Berlin, Germany". |
maxCrawledPlacesPerSearch | int | optional, default 20 | Places per search string. |
language | string | optional | UI language, e.g. "en". |
countryCode | string | optional | ISO 3166 alpha-2. |
city, state, postalCode | string | optional | Narrower filters. |
scrapePlaceDetailPage | bool | optional, default false | Set true for address / hours / phone. |
skipClosedPlaces | bool | optional, default false | Drop permanently-closed listings. |
Example:
{
"searchStringsArray": ["dentists"],
"locationQuery": "Berlin, Germany",
"maxCrawledPlacesPerSearch": 20,
"language": "en",
"scrapePlaceDetailPage": true,
"maximumLeadsEnrichmentRecords": 3,
"leadsEnrichmentDepartments": ["marketing", "c_suite"],
"verifyLeadsEnrichmentEmails": true
}
2. Google Search — apify/google-search-scraper
Anchor #1 is a keyword.
| Field | Type | Required? | Notes |
|---|---|---|---|
queries | string | yes | Newline-separated queries, each ≤ 32 words. |
maxPagesPerQuery | int | optional, default 1 | Each page ≈ 10 results. |
countryCode | string | optional, default "us" | Drives the google.xx domain. |
languageCode | string | optional | UI language. |
searchLanguage | string | optional | lr filter — restricts result-page language. |
mobileResults | bool | optional, default false | Mobile SERP. |
Example:
{
"queries": "best CRM software",
"maxPagesPerQuery": 1,
"countryCode": "us",
"languageCode": "en",
"maximumLeadsEnrichmentRecords": 3,
"leadsEnrichmentDepartments": ["sales"],
"verifyLeadsEnrichmentEmails": true
}
3. URL list — vdrmota/contact-info-scraper
Anchor #1 is a pre-existing URL list.
| Field | Type | Required? | Notes |
|---|---|---|---|
startUrls | object[] | yes | [{"url": "https://..."}, ...]. Pre-validate as http/https; emit a skipped — invalid URL row for each rejection. |
proxyConfig | object | yes | Default {"useApifyProxy": true} works for most. |
maxRequestsPerStartUrl | int | optional, default 20 | Pages crawled per start URL. |
maxDepth | int | optional, default 2 | Link-depth from start. |
mergeContacts | bool | optional, default true | Merge per-domain contacts. Keep on. |
sameDomain | bool | optional, default true | Stay inside the start URL's domain. |
useBrowser | bool | optional, default false | Headless browser for JS-heavy sites; raises cost. |
Example:
{
"startUrls": [
{"url": "https://acme-co.example"},
{"url": "https://demo-co.example"}
],
"maxRequestsPerStartUrl": 10,
"maxDepth": 2,
"mergeContacts": true,
"proxyConfig": {"useApifyProxy": true},
"maximumLeadsEnrichmentRecords": 3,
"leadsEnrichmentDepartments": [],
"verifyLeadsEnrichmentEmails": true
}
Supporting file: reference/output-formats.md
Output Formats
One row per contact (subject to anchor #6 scope), in CSV or JSON.
Row schema (16 columns)
The lead object lives on leadsEnrichment[] for Maps and SERP; URL-list leads sit on the same field inside a per-domain merged record. Field names are the actual keys the Apify lead-enrichment service returns.
| Column | Source field |
|---|---|
Source | Literal: Maps, Search, or URL list (set by the route used) |
Source Query or URL | Original search string or input URL |
Business / Domain | title (Maps) / domain of url (SERP) / start URL host (URL list) |
Full Name | leadsEnrichment[].fullName |
Job Title | leadsEnrichment[].jobTitle |
Departments | leadsEnrichment[].departments joined with | (array of enum strings; often empty) |
Seniority | leadsEnrichment[].seniority (entry / manager / director / c_suite / etc., blank if unavailable) |
Email | leadsEnrichment[].email |
Email Verification Status | leadsEnrichment[].emailVerification.result (ok / invalid / disposable / catch_all / unknown / error) |
Email Verification Quality | leadsEnrichment[].emailVerification.quality (good / risky / bad) |
LinkedIn | leadsEnrichment[].linkedinProfile |
Phone | place phone if present (Maps) else leadsEnrichment[].companyPhoneNumber |
City | leadsEnrichment[].city (or place city for Maps) |
Country | leadsEnrichment[].country (or place countryCode for Maps) |
Business Address | place address (Maps only; blank for SERP / URL list) |
Business Website | place website / url / leadsEnrichment[].companyWebsite |
Date Scraped | run finish time (ISO 8601) |
Notes:
departmentsis a plural array on the lead (not a string field calleddepartment). Often empty, but populated for ~30% of leads with values like["marketing"]or["c_suite", "finance"]. Join with|for the CSV column.- Missing fields stay blank — never invent a value.
Filter & dedupe
-
Spurious-match filter (mandatory, always on). The lead-enrichment service sometimes returns global-fallback leads when no local match exists — e.g., a US-zoo CFO with
companyWebsite=zoo.orgattributed to 8 unrelated Polish zoos because the matcher latched onto thezoosubstring.Row-keep logic: extract hostnames (strip
https?://, leadingwww., anything after/; lowercase) from both the source URL and the lead'scompanyWebsite, and keep only if both non-empty and equal. The source URL isplace.website(Maps), the SERP resulturl(Search), or the originalstartUrls[].url(URL list). If either hostname is empty, drop the lead.Count drops in
run_metadata.jsonunderstats.spuriousMatchesDroppedand surface in the deliverable header if non-zero. -
Result-scope filter (anchor #6). Applied after spurious-match.
Scope Row-keep logic verified-only(default)emailVerification.result == "ok"verified-plus-catchallemailVerification.result in {"ok", "catch_all"}all-emailsemailis non-empty (any result, including missing verification)with-phoneplace phoneorcompanyPhoneNumberis non-empty (regardless of email)everythingno filter -
Dedupe: for scopes that produce email rows, group by
email.toLowerCase(). Keep the first occurrence. If the same email comes from multiple sources, concatenateSource Query or URLwith|. Forwith-phonerows that have no email, dedupe by lowercased phone instead. -
Empty-result rows: if the department filter (anchor #3) produced zero leads for a given domain, include one row for that domain with blank
EmailandEmail Verification Status = "no leads matched filter". These rows are inserted before result-scope filtering and are always shown — they tell the user the filter narrowed too much. -
Invalid-URL rows (URL-list route only): include one row per pre-skipped URL with
Email Verification Status = "skipped — invalid URL". Also always shown.
State both the active result scope and the spurious-match drop count in the deliverable header.
Rendering
Post-process the raw dataset (from get-dataset-items or the script's output file) the same way regardless of route:
- Flatten
leadsEnrichmentso each lead becomes its own row. - Apply both filters (spurious-match, then scope).
- Dedupe.
- Project to the 16 columns above (CSV) or to a
contactsarray (JSON).
CSV deliverable — one header row, one row per lead, trailing RUN_METADATA row: RUN_METADATA, runId=..., datasetId=..., actor=..., finishedAt=....
JSON deliverable — envelope:
{
"runMetadata": {"runId": "...", "datasetId": "...", "actor": "...", "finishedAt": "...", "consoleUrl": "https://console.apify.com/actors/runs/..."},
"filter": {"scope": "verified-only", "rowKeepLogic": "emailVerification.result == 'ok'"},
"contacts": [ /* ... */ ]
}
Sidecar — always write run_metadata.json next to the deliverable with the same runMetadata fields plus stats (placesScraped, rawLeads, spuriousMatchesDropped, keptUnderScope). For multi-route deliverables, actor / runId / datasetId become arrays in matching order.
Supporting file: reference/scripts/package.json
{ "type": "module" }
Supporting file: reference/troubleshooting.md
Troubleshooting
Auth / setup
APIFY_TOKEN not found(script path only) — Create.envwithAPIFY_TOKEN=...; get the token fromhttps://console.apify.com/account/integrations. Not needed on the MCP path.Actor not found— Confirm the ID is one ofcompass/crawler-google-places,apify/google-search-scraper,vdrmota/contact-info-scraper. The script converts/to~when calling the API; expected.
Run-time
Run FAILED— Open the console URL printed by the runner and read the Actor log. Most common cause: malformed input JSON.- Timeout — Leads enrichment adds 30–90 s per domain on top of the base scrape. Raise the timeout (try 1500–1800 s). If the run is still progressing, the dataset already has partial results — pull by datasetId.
Run TIMED-OUTfrom Apify — Lower source breadth (maxCrawledPlacesPerSearch,maxRequestsPerStartUrl, fewer queries).
Empty / weak results
- No verified rows after filter — Re-render under
verified-plus-catchallorall-emailsscope. Catch-all SMTP servers can't be proven but often deliver. - No leads at all — Try in order: bump
maximumLeadsEnrichmentRecords; widenleadsEnrichmentDepartmentsto[]; confirm sources have reachable websites (Maps places without awebsitecan't be enriched). - All leads dropped by spurious-match filter — The enrichment service returned only global-fallback leads. There's no real fix — the source domain has no recognizable LinkedIn footprint. Surface the count and move on.
- Empty SERP queries — Confirm the keyword is non-empty and ≤ 32 words. Strip stray quotes.
URL-list route
- Blocked domain — Set
useBrowser: trueinvdrmota/contact-info-scraperinput. Raises cost but unblocks most anti-bot sites. - Invalid URL — Pre-validate in Step 3; emit a
skipped — invalid URLrow. Never submit a bad URL to the Actor.
Routing ambiguity
Ask one follow-up. Common patterns: SERP+URL-list pasted together → pick one or both; industry + no source → Maps or SERP?; industry + city without "Maps" → confirm route.
Cost surprises
Pull the breakdown from the run console. Usual causes: maximumLeadsEnrichmentRecords too high, source breadth uncapped, useBrowser: true left on. Live rates are in the Apify console under the Actor's pricing tab; the 200-lead pre-submit warning rule lives in SKILL.md Step 3.
Common questions
How do I install Verified email finder in Cursor, Claude Code, or Codex?
Run npx skills add apify/awesome-skills --skill apify-verified-email-finder in the project where you want it, then ask your agent for the skill by name. The --skill flag installs only Verified email finder, not every skill in the repository.
Where does Verified email finder come from and what license is it under?
Verified email finder comes from the apify/awesome-skills repository on GitHub. That repository has 246 GitHub stars. The skill is published under the Apache-2.0 license.
Prefer plain text? Read the Verified email finder guide as markdown.
Related skills
More from apify
More Email skills