# Google Maps Email Extractor (`sandy_yclept/google-maps-email-extractor`) Actor

Find local businesses on Google Maps and get business emails from each one's own website: Google Maps leads with an MX-checked email, phone and socials. This Actor charges only for places where an email is found; the search is billed per place by the Google Maps scraper it runs, $0.50 per 1,000.

- **URL**: https://apify.com/sandy\_yclept/google-maps-email-extractor.md
- **Developed by:** [Rafal Sav](https://apify.com/sandy_yclept) (community)
- **Categories:** Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 place with emails

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### What does Google Maps Email Extractor do?

**Google Maps Email Extractor** finds businesses on **Google Maps** and gets **the email address from each business's own website**. Type what you would search for — `dentist`, `plumber`, `Steuerberater` — and a location, and you get one record per place: the name, address, phone and website Google Maps shows, plus the **emails, phone numbers and social profiles** published on that website, each email **checked for a working mail server**.

Google Maps does not show email addresses; the website does. This Actor reads every place's website the way a person would — the contact page, the imprint, the privacy policy — and returns the address the business actually answers.

You **pay only for places where an email is found**. A place without a website, a website that has no email, or an email that belongs to someone else (the web agency, a booking platform) costs nothing.

It runs on the [Apify platform](https://apify.com): API access, scheduling, integrations (Make, Zapier, n8n, Google Sheets) and run monitoring included.

### Why use it?

- **More places get an email.** On 130 real businesses with a website — dentists, plumbers, lawyers, estate agents and accountants in Chicago, Manchester, Cologne and Kraków — it found a usable email for **80%**, against **71%** for the contact add-on built into Google Maps Scraper, on the same places (September 2026, details below).
- **The email is the business's own.** An address counts only when it is on the business's own domain, on a second domain carrying the business's name (`koeln@dahler.com` for DAHLER at dahlercompany.com), or a personal mailbox the business uses (Gmail, Outlook, T-Online, WP.pl…). The web agency that built the site — even one sharing the owner's surname — the booking platform named in the privacy policy, and the regulator or professional chamber a German Impressum must name are returned for reference but **never billed and never picked as `best_email`**.
- **Emails are checked for a working mail server.** A domain that accepts no mail (a typo, an expired domain) is flagged `valid_mx: false` and never billed.
- **Phones are normalized.** Numbers found on the website come back in E.164 (`+17735550123`), parsed for the place's country; they matched the Google Maps phone for **85%** of the places above, against 61% for the add-on.
- **Obfuscated addresses are decoded:** Cloudflare email protection, HTML entities, `name [at] domain [dot] com`.
- **Chains are not billed twice.** Branches of one business that share an inbox are charged once per run; a website shared by several branches is read once.

### Use cases

- **Local lead generation** — every dentist, plumber or law firm in a city, with an inbox to write to.
- **Agency prospecting** — find businesses in a niche and region, with their website and socials.
- **Sales territory mapping** — a CSV of reachable businesses per category and city.
- **Enriching a list you already have** — give it an earlier Google Maps dataset and only the websites are read.

### How to use it

1. Enter one or more **Search terms** (one per line) and a **Location**, e.g. `dentist` in `Chicago, USA`.
2. Set **Places per search term** — it also caps what the Google Maps search costs.
3. Click **Start**. Places are read as soon as Google Maps returns them; download the results as JSON, CSV, Excel or HTML.

Already scraped Google Maps? Pick that dataset under **Or: places you already have** instead of entering search terms: its places are enriched and nothing is paid for finding them again. A Google Maps link pasted into the search terms works too.

### Input

| Field | What it does |
|---|---|
| Search terms | What you would type into Google Maps. Each term is searched in the location. |
| Location | A city, region or country, as free text. One per run. |
| Places per search term | How many places to take per term (default 20). |
| Language | Language of place names and addresses returned by Google Maps. |
| Where the places come from | `scraperlink` (default, $0.50 per 1,000 places on the Free and Starter plans, compass takes over when it finds nothing) or `compass` (closest to Google Maps' own ranking). |
| Places you already have | An existing Google Maps dataset to enrich instead of searching. |
| Max contact subpages | How many contact-like pages to read per website (default 5). |
| Validate emails | Check each email's domain for a mail server (on by default). |
| JavaScript rendering | `auto` re-reads a website in a browser when no usable email was found without one; needs 2048 MB of memory. |

### Output

One record per place:

```json
{
  "name": "Northside Dental Care",
  "category": "Dentist",
  "address": "1200 N Example Ave, Chicago, IL 60622",
  "city": "Chicago",
  "country_code": "US",
  "maps_phone": "+17735550123",
  "website": "https://www.northsidedental.example/",
  "status": "ok",
  "best_email": "info@northsidedental.example",
  "emails": [
    { "email": "info@northsidedental.example", "valid_mx": true, "role": true, "confidence": 0.85, "kind": "own" },
    { "email": "hello@webagency.example", "valid_mx": true, "role": true, "confidence": 0.85, "kind": "other" }
  ],
  "phones": ["+17735550123"],
  "faxes": [],
  "socials": { "facebook": ["https://facebook.com/northsidedental"], "instagram": ["https://instagram.com/northsidedental"] },
  "billed": true,
  "rating": 4.8,
  "reviews_count": 311,
  "google_maps_url": "https://www.google.com/maps/search/?api=1&query=...",
  "place_id": "ChIJ...",
  "search_string": "dentist",
  "rank": 4
}
```

| Field | Description |
|---|---|
| `name`, `category`, `address`, `city`, `country_code` | The place as Google Maps lists it. |
| `maps_phone` | The phone number Google Maps shows. |
| `website` | The website Google Maps lists, without click-tracking parameters. |
| `status` | `ok` (contacts found), `no_contacts` (website read, nothing published), `unreachable` (the website refused or did not answer, or forwards to a platform's front page — an expired domain sent to Facebook or to its registrar), `no_website` (none listed, or only a Facebook, Instagram or booking-platform page — kept in `socials` or `website`; a Google Site is read as the business's own), `closed` (permanently closed, not read). |
| `best_email` | The address to write to: the business's own domain first, then a domain with its name, then a personal mailbox it uses. Empty when there is none. |
| `emails` | Every address found on the website, each with `valid_mx`, `role`, `confidence` and `kind`: `own`, `related` (a domain carrying the business's name), `free_mail` or `other` (someone else's — never billed). |
| `phones`, `faxes` | Numbers found on the website, in E.164. Faxes are kept apart. |
| `socials` | Profile links from the website, grouped by platform, de-duplicated. |
| `billed` | Whether this place was charged. |
| `rating`, `reviews_count`, `google_maps_url`, `place_id`, `search_string`, `rank` | From Google Maps. |

### Accuracy

On **September 24, 2026** we took the first 8 Google Maps results for five lead-generation categories in four cities — dentists, plumbers, lawyers, estate agents and accountants in Chicago, Manchester, Cologne and Kraków, searched in the local language — 136 places, 130 with a website. The same places went through this Actor and through Google Maps Scraper's own **Company contacts enrichment** add-on:

| Of 130 places with a website | This Actor | Contact add-on |
|---|---|---|
| Usable email (own domain or personal mailbox) | **104 (80%)** | 92 (71%) |
| Email on the business's own domain | **94 (72%)** | 82 (63%) |
| Only someone else's address found | 12 (9%) | 11 (8%) |
| Phone matching the one on Google Maps | **110 (85%)** | 79 (61%) |

Only this Actor found a usable email for 17 of the places, only the add-on for 5. The top of Google Maps is not a random draw of businesses — firms that invest in their listing are over-represented — so read these as a fair comparison on the same places, not as a yield estimate for every list.

**Where the places come from** (September 25, 2026). The same 20 searches through [scraperlink's Google Maps Scraper](https://apify.com/scraperlink/google-maps-scraper), the default source, returned 63 of those places among their first 10 results, with the same website and phone for all 63 and the same share of usable emails, 81%. Its other places — more trades that serve an area without a street address, and more lead-generation sites — gave fewer: 74% of all 199 places with a website. The search costs an eighth as much, so a usable email costs about half. Pick `compass` as the place source for the ranking Google Maps shows.

### How much does it cost?

Two things are paid, both only for what is actually delivered:

- **Place with email — $0.003** (~$3 per 1,000), charged by this Actor for each place whose website gave a usable email: on its own domain, on a domain carrying its name, or a personal mailbox it uses. Places without a website or without an email, closed, unreachable, or with only someone else's address: **$0**. Phones and social profiles are included. A place is written to the dataset before it is charged, so you never pay for a row you cannot see.
- **Finding the places.** The Google Maps search runs in a separate run on your account, which bills its own places. By default that is [scraperlink's Google Maps Scraper](https://apify.com/scraperlink/google-maps-scraper) — **$0.50 per 1,000 places** on the Free and Starter plans, $0.40 on higher ones, at least 10 per search term — and when it finds nothing, [compass's Google Maps Scraper](https://apify.com/compass/crawler-google-places) takes over — $4 per 1,000 places on the Free plan, less on paid plans. Paid add-ons are never switched on. Enriching a dataset you already have skips this part entirely.

100 dentists in Chicago cost about **$0.05** for the search plus **about $0.22** for the emails (about 75 places with an email). The Apify Free plan's **$5 in monthly credits** covers roughly 1,800 places; with `compass` as the source, about 700.

The **maximum cost per run** you set applies to this Actor's charges. The Google Maps search run gets its own limit: the places you asked for at its price plus a quarter — for compass never less than the $0.50 it requires.

### Tips

- **Keep "Places per search term" as low as you need** — the Google Maps search bills every place it returns, with or without an email. The default source returns at least 10 per term; only the number you asked for is read here.
- **Some trades rarely list a website.** Searching hair salons in Kraków, 12 of 15 places had none, or only a Facebook or booking page; dentists, lawyers and accountants usually do. Places without a website cost nothing here, but the search still bills them.
- **Re-use a dataset** rather than searching again for places you already have.
- **Use `renderJs: auto`** (with 2048 MB of memory) for websites built entirely in JavaScript; it only starts a browser for sites where no usable email was found without one.
- **Stopping the run stops the search too** — whether you abort it (any way) or it reaches its maximum cost — so nothing keeps billing in the background. Measured: a run aborted half a minute into a 300-place search stopped the search six seconds later, before a single place was billed. Small searches are often over before you could stop them: 100 places came back in 27 seconds.
- **Reading websites takes longer than finding places.** About 3 seconds per place at the default 1024 MB of memory (136 places in 6.5 minutes); results appear in the dataset as each place is done. The default run timeout of 4 hours covers roughly 5,000 places: a run that gets close to its timeout stops taking new places a few minutes early, finishes the ones in progress and says in its status how many were left — raise the timeout in the run options for bigger lists.
- **A run the platform moves to another server picks up where it was.** The search is not started again, and no place is read or charged twice.

### FAQ

#### Why do some places have no email?

Check `status`. `no_website`: Google Maps lists no website, or only a Facebook or booking page. `no_contacts`: the website publishes no email (only a contact form) — or it is built entirely in JavaScript; `renderJs: auto` re-reads such a site in a browser when no usable email was found without one. `unreachable`: the website refused the request — the Actor then retries through Apify's datacenter proxy by itself — or the domain forwards to a platform's front page. None of these are billed.

#### Why is an address in `emails` but not `best_email`?

It is on someone else's domain (`kind: other`): the agency that built the site, a booking or review platform, a supplier. It is returned so you can see it, but it is never billed and never chosen as the address to write to. A second domain of the business's own — one carrying its name — is `kind: related` and is treated like its own.

#### Can I use it for a list of websites instead of Google Maps?

Use the [Website Contact & Email Scraper](https://apify.com/sandy_yclept/website-contact-email-scraper) — same engine, one record per website, priced per contact.

#### Is scraping this data legal?

Google Maps listings and business websites are public, and this Actor respects `robots.txt` and rate-limits its requests. What you do with the data is your responsibility: under GDPR you are the data controller, you need a lawful basis for outreach, some countries restrict unsolicited commercial email (for example § 7 UWG in Germany), and you must honour opt-outs.

### Disclaimer

This Actor is a **tool**. You are the data controller for any personal data you collect and are responsible for complying with GDPR and local marketing law, the target sites' Terms of Service and robots.txt. It does not build or sell contact databases.

### Support

Found a place where an email was missed or the wrong one was picked? Open an issue on the **Issues** tab — accuracy reports shape the roadmap directly.

# Actor input Schema

## `searchStringsArray` (type: `array`):

What you would type into Google Maps, one term per line: "dentist", "plumber", "Steuerberater". Every term is searched in the location below. The places are found by a Google Maps scraper that bills its own places to your account in a separate run - see "Where the places come from" and the README for prices.

## `locationQuery` (type: `string`):

Where to search, as free text: a city and country work best ("Chicago, USA", "Köln, Deutschland"). One location per run. Leave it empty when the search terms already say where.

## `maxCrawledPlacesPerSearch` (type: `integer`):

How many Google Maps places to take for each search term. The Google Maps search bills every place it returns - at least 10 per term with the default source - so this also caps what the search costs. Reading the websites takes about 3 seconds per place; the run stops taking new places shortly before its timeout and says how many were left.

## `language` (type: `string`):

Language of the place names, categories and addresses Google Maps returns. Emails and websites are the same in any language.

## `placeSource` (type: `string`):

Which Google Maps scraper finds the places, in a run of its own on your account. scraperlink/google-maps-scraper: $0.50 per 1,000 places on the Free and Starter plans ($0.40 on higher ones), at least 10 per search term; when it finds nothing, compass/crawler-google-places takes over. compass/crawler-google-places: $4 per 1,000 places on the Free plan, closest to Google Maps' own ranking. The emails are read the same way from either.

## `mapsDatasetId` (type: `string`):

A dataset from an earlier Google Maps scrape - Google Maps Scraper's, or any export with a website per place. Its places are enriched instead of a new search, and nothing is paid for finding them again. Given together with search terms, both are read and duplicates are merged.

## `maxContactPages` (type: `integer`):

How many contact-like subpages (/contact, /about, /impressum, the privacy policy) to read on each place's website, on top of the page Google Maps links to. Set to 0 to read that page only.

## `validateEmails` (type: `boolean`):

Check each email's domain for MX records and attach a confidence score. An address whose domain accepts no mail is returned but never billed. Turn off for a faster run without the check.

## `includeSocials` (type: `boolean`):

Extract LinkedIn, X/Twitter, Facebook, Instagram, YouTube and TikTok profile links from each website (share/intent URLs are filtered out). Included in the price of a place; a place with only socials is not billed.

## `renderJs` (type: `string`):

Load pages in a real browser to reach contacts that are rendered by JavaScript. Off (default) is the fastest and cheapest. Auto reads a website without a browser first and only re-reads it in the browser when no email the place could be billed for was found - the phone Google Maps already shows does not count. On always uses the browser. Auto and On require the run memory to be at least 2048 MB. Below 1024 MB the browser is not started and the run says so.

## `proxyConfiguration` (type: `object`):

Optional proxy for fetching pages. Apify Proxy's datacenter IPs are included in every Apify plan, the Free plan too; residential proxies are billed separately. Left disabled, the Actor connects directly, and retries sites that refuse it through Apify Proxy (see below).

## `proxyFallback` (type: `boolean`):

Every cloud run has a datacenter IP address, and some sites turn those away. A site that refuses or drops the direct connection is retried once through Apify Proxy's datacenter IPs (included in every Apify plan, the Free plan too), then once more directly. Sites that answer directly never go through the proxy, and robots.txt is respected either way. Has no effect when a proxy is configured above.

## Actor input object example

```json
{
  "searchStringsArray": [
    "dentist",
    "orthodontist"
  ],
  "locationQuery": "Chicago, USA",
  "maxCrawledPlacesPerSearch": 10,
  "language": "en",
  "placeSource": "scraperlink",
  "maxContactPages": 5,
  "validateEmails": true,
  "includeSocials": true,
  "renderJs": "off",
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "proxyFallback": true
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchStringsArray": [
        "accountant"
    ],
    "locationQuery": "Manchester, United Kingdom",
    "maxCrawledPlacesPerSearch": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("sandy_yclept/google-maps-email-extractor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchStringsArray": ["accountant"],
    "locationQuery": "Manchester, United Kingdom",
    "maxCrawledPlacesPerSearch": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("sandy_yclept/google-maps-email-extractor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchStringsArray": [
    "accountant"
  ],
  "locationQuery": "Manchester, United Kingdom",
  "maxCrawledPlacesPerSearch": 10
}' |
apify call sandy_yclept/google-maps-email-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,sandy_yclept/google-maps-email-extractor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/uQhr2ws5RdSBp0aNd/builds/HjIXYjLR9nYrfRdKE/openapi.json
