Verified email finder

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.
Install-only

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.

Terminal
$ npx skills add apify/awesome-skills --skill apify-verified-email-finder

Skill instructions

The instruction file for this skill. The skill also includes other files you need to install to use it.

SKILL.md

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:

  • .env file with APIFY_TOKEN
  • Node.js 20.6+ (for native --env-file support)

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.

  1. What do you have to start with?location query / SERP keyword / URL list. This drives the routing decision.
  2. The actual input — the location string, the keyword(s), or the URLs themselves.
  3. 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 is any (leave the array empty), but ask every time.
  4. Max contacts per domain / business — passed as maximumLeadsEnrichmentRecords. Default 3, but ask every time.
  5. Output formatCSV or JSON. Ask every time.
  6. 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 with emailVerification.result == "ok". Safest for cold email.
    • verified-plus-catchallok plus catch_all. Catch-all is often deliverable but unprovable.
    • all-emails — any lead with a non-empty email, 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 withActor IDUse when
Location + business type ("dentists in Berlin")compass/crawler-google-placesLocal leads list from Maps listings; best when user wants address / phone / hours too
Keyword / search query ("best CRM software")apify/google-search-scraperContacts from whichever sites Google ranks for a topic
Pre-existing URL list (pasted, file path)vdrmota/contact-info-scraperUser 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 saysRoute
"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.

FieldValue
maximumLeadsEnrichmentRecordsanchor #4 (default 3, min 1)
leadsEnrichmentDepartmentsanchor #3 as array, or [] if "any"
verifyLeadsEnrichmentEmailstrue (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 3
  • callOptions: {"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-items with the datasetId from Step 4. Use the fields parameter (e.g., title,searchString,countryCode,city,address,phone,website,leadsEnrichment) and clean: true to keep the response small. For datasets that still exceed the response cap, fetch directly via curl https://api.apify.com/v2/datasets/<id>/items?fields=...&clean=true and pipe through jq.
  • 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.org was attributed to 8 unrelated Polish zoos because the matcher latched onto the zoo substring). Drop any lead whose companyWebsite hostname doesn't equal the source URL's hostname (strip https?://, leading www., anything after /; lowercase). Count drops in run_metadata.json and call them out in the deliverable header if non-zero.

  • Filter by result scope (anchor #6). Applied second.

    ScopeRow-keep logic
    verified-onlyemailVerification.result == "ok"
    verified-plus-catchallemailVerification.result in {"ok", "catch_all"}
    all-emailsemail is 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 URL if the same email appears from multiple sources. For with-phone rows 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 = "" and Email 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

Quality Rules (always enforce)

  • Guard rail: never submit a run with verifyLeadsEnrichmentEmails: false.
  • Provenance & traceability: populate the Source column on every row; carry Apify runId + datasetId in run_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 havelocation query
2 Inputdentists in Berlin
3 Departmentsmarketing, c_suite
4 Max contacts3
5 FormatCSV
6 Scopeverified-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

SourceBusinessFull NameJob TitleSeniorityEmailStatusQualityPhoneCity
MapsExample Dental ASample Contact 1Marketing Leadmanagercontact1@dental-a.example (mailto:contact1@dental-a.example)okgood+49 30 5550001Berlin
MapsExample Dental BSample Contact 2Ownerc_suitecontact2@dental-b.example (mailto:contact2@dental-b.example)okgood+49 30 5550002Berlin
MapsExample Dental CSample Contact 3CMOc_suitecontact3@dental-c.example (mailto:contact3@dental-c.example)okgood+49 30 5550003Berlin

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 haveSERP keyword
2 Inputbest CRM software
3 Departmentssales
4 Max contacts3
5 FormatJSON
6 Scopeverified-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 haveURL list
2 Inputhttps://acme-pay.example, https://demo-card.example, https://sample-bank.example, htp://broken-url, https://example-biz.example
3 Departmentsany (empty array)
4 Max contacts2
5 FormatCSV
6 Scopewith-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.

SourceBusinessFull NameJob TitleEmailStatusQualityPhone
URL listacme-pay.exampleSample Contact 1Head of BDcontact1@acme-pay.example (mailto:contact1@acme-pay.example)okgood+1 415 555 0101
URL listacme-pay.exampleSample Contact 2Director, Partnershipscontact2@acme-pay.example (mailto:contact2@acme-pay.example)catch_allrisky+1 415 555 0102
URL listdemo-card.exampleSample Contact 3VP Salescontact3@demo-card.example (mailto:contact3@demo-card.example)okgood+1 415 555 0201
URL listsample-bank.exampleSample Contact 4Growth Leadunknown+1 415 555 0301
URL listexample-biz.exampleSample Contact 5Account Managercontact5@example-biz.example (mailto:contact5@example-biz.example)okgood+1 415 555 0401
URL listhtp://broken-urlskipped — 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:

FieldValueNotes
maximumLeadsEnrichmentRecordsanchor #4 (default 3, min 1)0 disables enrichment — never use.
leadsEnrichmentDepartmentsanchor #3 as array, or [] for anyEnum: c_suite, product, engineering_technical, design, education, finance, human_resources, information_technology, legal, marketing, medical_health, operations, sales, consulting.
verifyLeadsEnrichmentEmailstrue alwaysGuard rail. Adds emailVerification per lead. Never false.

1. Google Maps — compass/crawler-google-places

Anchor #1 is a location + business type.

FieldTypeRequired?Notes
searchStringsArraystring[]yesBusiness type(s), e.g. ["dentists"].
locationQuerystringyesFree-form location, e.g. "Berlin, Germany".
maxCrawledPlacesPerSearchintoptional, default 20Places per search string.
languagestringoptionalUI language, e.g. "en".
countryCodestringoptionalISO 3166 alpha-2.
city, state, postalCodestringoptionalNarrower filters.
scrapePlaceDetailPagebooloptional, default falseSet true for address / hours / phone.
skipClosedPlacesbooloptional, default falseDrop 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.

FieldTypeRequired?Notes
queriesstringyesNewline-separated queries, each ≤ 32 words.
maxPagesPerQueryintoptional, default 1Each page ≈ 10 results.
countryCodestringoptional, default "us"Drives the google.xx domain.
languageCodestringoptionalUI language.
searchLanguagestringoptionallr filter — restricts result-page language.
mobileResultsbooloptional, default falseMobile 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.

FieldTypeRequired?Notes
startUrlsobject[]yes[{"url": "https://..."}, ...]. Pre-validate as http/https; emit a skipped — invalid URL row for each rejection.
proxyConfigobjectyesDefault {"useApifyProxy": true} works for most.
maxRequestsPerStartUrlintoptional, default 20Pages crawled per start URL.
maxDepthintoptional, default 2Link-depth from start.
mergeContactsbooloptional, default trueMerge per-domain contacts. Keep on.
sameDomainbooloptional, default trueStay inside the start URL's domain.
useBrowserbooloptional, default falseHeadless 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.

ColumnSource field
SourceLiteral: Maps, Search, or URL list (set by the route used)
Source Query or URLOriginal search string or input URL
Business / Domaintitle (Maps) / domain of url (SERP) / start URL host (URL list)
Full NameleadsEnrichment[].fullName
Job TitleleadsEnrichment[].jobTitle
DepartmentsleadsEnrichment[].departments joined with | (array of enum strings; often empty)
SeniorityleadsEnrichment[].seniority (entry / manager / director / c_suite / etc., blank if unavailable)
EmailleadsEnrichment[].email
Email Verification StatusleadsEnrichment[].emailVerification.result (ok / invalid / disposable / catch_all / unknown / error)
Email Verification QualityleadsEnrichment[].emailVerification.quality (good / risky / bad)
LinkedInleadsEnrichment[].linkedinProfile
Phoneplace phone if present (Maps) else leadsEnrichment[].companyPhoneNumber
CityleadsEnrichment[].city (or place city for Maps)
CountryleadsEnrichment[].country (or place countryCode for Maps)
Business Addressplace address (Maps only; blank for SERP / URL list)
Business Websiteplace website / url / leadsEnrichment[].companyWebsite
Date Scrapedrun finish time (ISO 8601)

Notes:

  • departments is a plural array on the lead (not a string field called department). 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

  1. 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.org attributed to 8 unrelated Polish zoos because the matcher latched onto the zoo substring.

    Row-keep logic: extract hostnames (strip https?://, leading www., anything after /; lowercase) from both the source URL and the lead's companyWebsite, and keep only if both non-empty and equal. The source URL is place.website (Maps), the SERP result url (Search), or the original startUrls[].url (URL list). If either hostname is empty, drop the lead.

    Count drops in run_metadata.json under stats.spuriousMatchesDropped and surface in the deliverable header if non-zero.

  2. Result-scope filter (anchor #6). Applied after spurious-match.

    ScopeRow-keep logic
    verified-only (default)emailVerification.result == "ok"
    verified-plus-catchallemailVerification.result in {"ok", "catch_all"}
    all-emailsemail is non-empty (any result, including missing verification)
    with-phoneplace phone or companyPhoneNumber is non-empty (regardless of email)
    everythingno filter
  3. Dedupe: for scopes that produce email rows, group by email.toLowerCase(). Keep the first occurrence. If the same email comes from multiple sources, concatenate Source Query or URL with |. For with-phone rows that have no email, dedupe by lowercased phone instead.

  4. Empty-result rows: if the department filter (anchor #3) produced zero leads for a given domain, include one row for that domain with blank Email and Email 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.

  5. 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:

  1. Flatten leadsEnrichment so each lead becomes its own row.
  2. Apply both filters (spurious-match, then scope).
  3. Dedupe.
  4. Project to the 16 columns above (CSV) or to a contacts array (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 .env with APIFY_TOKEN=...; get the token from https://console.apify.com/account/integrations. Not needed on the MCP path.
  • Actor not found — Confirm the ID is one of compass/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-OUT from Apify — Lower source breadth (maxCrawledPlacesPerSearch, maxRequestsPerStartUrl, fewer queries).

Empty / weak results

  • No verified rows after filter — Re-render under verified-plus-catchall or all-emails scope. Catch-all SMTP servers can't be proven but often deliver.
  • No leads at all — Try in order: bump maximumLeadsEnrichmentRecords; widen leadsEnrichmentDepartments to []; confirm sources have reachable websites (Maps places without a website can'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: true in vdrmota/contact-info-scraper input. Raises cost but unblocks most anti-bot sites.
  • Invalid URL — Pre-validate in Step 3; emit a skipped — invalid URL row. 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.

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.