The Mine Works
← All posts
tutorial July 11, 2026 · 5 min read

Maps Leads: Verified Email Extraction from Google Maps Business Listings

How to pull B2B leads from Google Maps with MX-verified emails, past the official API's 120-result cap, and pay only for contactable businesses.

Try the scraper

The actor referenced in this article. Pay only for results delivered.

View the scraper →

Every “Google Maps scraper” tutorial hits the same wall eventually: the official Google Places API caps results at 120 places per search area, and it does not verify emails — you get a business name, an address, and if you’re lucky a website URL. Getting from “website URL” to “an inbox you can actually send a cold email to” is a separate job entirely, one the API doesn’t touch at all.

This guide covers both problems: pulling more results than the API will give you, and getting back emails that have already been checked against live DNS MX records, not just harvested and hoped for.

What Maps Leads Actually Returns

The Google Maps Leads Scraper takes a search query — "plumbers in Chicago", "dentists in Mumbai", "coffee shops in Austin TX" — and returns business name, category, full address, phone, website, Google Maps rating, review count, opening hours, coordinates, business status, and a verified email.

The email is the part worth dwelling on. For each business, the actor crawls its website looking for contact emails, then checks every candidate email’s domain for a live DNS MX record before it’s ever counted as a result. Emails are also scored by relevance to the business (an info@ or sales@ address on the business’s own domain ranks above a personal Gmail address picked up incidentally), and the best one is surfaced in the email field with the full ranked list in all_emails.

{
  "name": "Apex Plumbing Co.",
  "category": "Plumber",
  "address": "412 W Monroe St, Chicago, IL 60606",
  "phone": "+1 312-555-0182",
  "website": "https://apexplumbingchicago.com",
  "email": "service@apexplumbingchicago.com",
  "email_status": "valid",
  "rating": 4.7,
  "review_count": 312,
  "business_status": "OPERATIONAL"
}

Getting Past the 120-Result Ceiling

Google Maps itself only surfaces about 120 places per search area before the scroll list stops loading more, and the official Places API enforces the same limit. This isn’t a bug you can work around with a different API parameter — it’s a hard ceiling on how many results any single search area returns, official API or otherwise.

Maps Leads gets around this by driving Google Maps the way a person would: searching, scrolling, and opening each place individually, across each query you give it. That means the practical way to cover a market thoroughly isn’t one broad query — it’s several narrower ones. "plumbers in Chicago" alone caps out; "plumbers in Chicago Loop", "plumbers in Chicago North Side", "plumbers in Chicago South Side" run as three independent searches and together cover far more ground than the single citywide query ever could.

Running It in Python

import os
from apify_client import ApifyClient

apify = ApifyClient(os.environ["APIFY_TOKEN"])

run = apify.actor("themineworks/maps-leads").call(run_input={
    "searchQueries": [
        "plumbers in Chicago Loop",
        "plumbers in Chicago North Side",
        "plumbers in Chicago South Side",
    ],
    "maxLeadsPerQuery": 25,
    "language": "en",
    "verifyEmails": True,
    "skipClosedBusinesses": True,
    "deduplicateAcrossRuns": True,
    "proxyConfig": {"useApifyProxy": True, "apifyProxyGroups": ["RESIDENTIAL"]},
})

leads = [item for item in apify.dataset(run["defaultDatasetId"]).iterate_items()
         if item.get("enrichment_status") == "email_verified"]

for lead in leads:
    print(f"{lead['name']}{lead['email']} ({lead['email_status']}) — {lead['rating']}★, {lead['review_count']} reviews")

Filtering on enrichment_status == "email_verified" after the fact matters because every result — charged or not — still appears in the dataset. Rows that weren’t charged carry a reason in enrichment_status: closed_skipped for permanently closed businesses, no_email for sites with no extractable contact, extraction_failed for sites the crawler couldn’t reach. You get full visibility into why a row is free, rather than silently missing leads.

Only Paying for What You Can Use

The billing model is built around the actual thing you want: a contactable business. The charge event is lead-verified, fired only when a business has at least one email that passes DNS MX verification, at $0.01 per verified lead ($10 per 1,000). Permanently closed businesses, businesses with no website, businesses whose sites yield no email, and emails that fail MX are all free — you never pay for a dead end.

Cross-run deduplication (on by default via deduplicateAcrossRuns) is worth calling out too: if you re-run the same query next month to catch new listings, businesses you’ve already paid for and pulled in a previous run are skipped and not billed again. That makes scheduled re-runs for fresh-lead discovery cheap, since you’re only paying for what’s actually new.

From Search Query to CRM Row

A realistic pipeline for a sales team running geo-targeted outbound looks like this: define your target verticals and cities, run several sub-queries per city to get past the 120-result ceiling, filter to email_verified rows, and push the result straight into your CRM or sequencer.

import csv

with open("chicago_plumbers_leads.csv", "w", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=[
        "name", "category", "address", "phone", "website", "email", "rating", "review_count"
    ])
    writer.writeheader()
    for lead in leads:
        writer.writerow({k: lead.get(k) for k in writer.fieldnames})

That CSV is import-ready for most CRMs and cold-email tools without further cleanup — the emails are already MX-verified, so you’re not going to tank your sender reputation on a list full of dead domains.

Where to Go From Here

Maps Leads gets you verified emails for businesses that already expose a contact address on their website. Some businesses won’t — a Squarespace site with a contact form and no visible mailto: link, for example. For those, you’ll want a follow-up enrichment step; see our guide on building the full B2B lead-generation stack for chaining Maps Leads into Website Contact Finder and Email Verifier for full coverage.

If you also need review data on the businesses you’ve found — for sales conversations, competitive benchmarking, or just prioritizing outreach by reputation — see our Google Maps Reviews Scraper guide, which pulls review text and ratings for the same listings.

Frequently Asked Questions

Does this need a Google Cloud account or Places API key?

No. It reads public Google Maps listings through a residential proxy, no Google account, API key, or billing setup required on your side.

What does email_status actually verify?

Each candidate email’s domain is checked for a live DNS MX record before it counts. valid means the MX check passed cleanly; catch_all_likely, role_based, and unverified are lower-confidence variants worth a second look before a high-stakes send — see our Email Verifier & Validator guide if you want a deeper SMTP-level check on top.

How many leads can one query return?

maxLeadsPerQuery caps it, but the practical ceiling on any single query is Google Maps’ own display limit for that search area. Splitting a broad geography into several narrower queries is how you scale past it.

What happens to businesses with no website?

They still appear in the dataset (useful for market-mapping and density analysis) but are never charged, since there’s no email to verify.

Related Actor

Explore the scraper referenced in this article — see inputs, outputs, and pricing, then run it on Apify.