# Google Maps Email Extractor – Every Address Verified (`locomint/google-maps-email-extractor`) Actor

Google Maps leads billed per verified lead, not per business read. Search a category and a city: businesses with no address, or none a mail server confirms, cost you nothing, and every filter is free. Each address delivered was checked against its own mail server.

- **URL**: https://apify.com/locomint/google-maps-email-extractor.md
- **Developed by:** [Locomint](https://apify.com/locomint) (community)
- **Categories:** Lead generation, Marketing, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 result delivereds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Google Maps Email Extractor – Every Address Verified

Search a category and a city, and get back businesses whose email address has actually been
checked — not guessed, not scraped and hoped for.

For each business found on the map, this Actor opens the business's **own website**, reads the
email addresses published there, and then holds a real SMTP conversation with each mailbox's
mail server before the row is delivered. Addresses the server rejects never reach you.

### What you pay for — and why it is cheaper than it looks

**One charge per lead delivered, and a lead is only delivered when its address passed.**
Everything else is free:

| Outcome | Delivered? | Charged? |
|---|---|---|
| Address confirmed by the mail server | yes | yes |
| Business has no website | no | no |
| Website published no address | no | no |
| Every address found was rejected | no | no |
| Business asked us to remove it | no | no |

So a run over 500 businesses that yields 90 checked addresses costs you **90 rows, not 500**.

That is the whole difference. The usual model on this platform bills you per business *read* —
then adds a charge for finding the contact details, and another for verifying the address, and
in at least one popular case **a further charge for every filter you apply**. You pay for the
businesses that had no address, and for the ones whose address turned out to be dead.

Here you do not. Filters are free, misses are free, and unverifiable addresses are free. You are
charged once for a lead you can actually write to.

An unchecked list is also cheap to produce and expensive to send to: the bounces are charged
against **your** sending reputation, not ours.

### Why the checking is real

Most scrapers cannot do this. Asking a mail server whether a mailbox exists needs outbound port
25, which this platform blocks — so an Actor with no machine of its own can only check spelling
and whether the domain has a mail server at all. This one runs the mailbox conversation on
Locomint's own server, which is why a verdict here means the server answered.

If that server is ever unreachable, the run says so in the log in plain words and falls back to
domain-level checks — and with the default setting you then get **no rows**, rather than
unchecked addresses quietly sold as checked.

### Input

| Field | What it does |
|---|---|
| `searchStringsArray` | Categories to search, one per line, as you would type them into a map |
| `locationQuery` | City **and country** — "Porto, Portugal", not "Porto" |
| `maxCrawledPlacesPerSearch` | How many businesses to *read*, not how many rows you get |
| `deliver` | `verified` (default), `verified_and_risky`, or `any` |
| `maxEmailsPerPlace` | How many addresses to test per business (default 3) |
| `websiteFilter` | Skips businesses with no website by default — they cannot yield an address |
| `includeDetails` | Off by default; adds opening hours and photos at ~20% more run time |

`placeMinimumStars`, `skipClosedPlaces` and `categoryFilterWords` are free filters applied to the
search result **before** anything is fetched, so anything they exclude costs nothing.

#### What the verdicts mean

- **deliverable** — the mail server was asked about this exact mailbox and said yes. This is
  what `verified` delivers, and the only status that means the mailbox itself was tested.
- **risky** — a catch-all domain: the server accepts every address, so nobody can prove this one
  either way. Held back by default; ask for `verified_and_risky` if you want them.
- **valid** — syntax and a real mail server on the domain, and nothing more. It is **not** a
  mailbox check, so it is never delivered as verified.
- **undeliverable / invalid / disposable / unknown** — only delivered if you ask for `any`,
  and then charged.

### Output

One row per business: name, address, phone in E.164, website, rating, review count, category
and coordinates — plus `email`, `email_status`, `email_score`, `email_is_named` (true when the
delivered address names a member of staff, false for a company mailbox), the mail server that
answered, and `emails_verified`, which lists every address tested on that site with its own
verdict, so you can see what was rejected rather than wonder where it went. The row also
carries `named_emails` and `named_profiles`: every named address and personal profile link
found on the site.

Opening hours, photos and the description need a second fetch per business and are **off by
default**, because a lead row does not use them and they cost you run time for every business
looked at, not just the ones that become leads. Turn on `includeDetails` if you want them.

### Limits, stated plainly

- **Addresses the business publishes on its own site.** Company mailboxes such as `info@` and
  `bookings@` come first, often with a business's own generic mailbox on a free provider.
  Addresses that name a member of staff are tested too, and the lead's `email` is the address
  with the strongest verdict among those tested, which can be a named one; `email_is_named`
  says which it is. A named address on another company's domain, such as the web designer
  credited in the footer, is never returned.
- **Expect far fewer rows than businesses read, and budget for that.** Most small businesses
  publish no address on their site at all. Measured on a real run — dentists in Porto, 12
  businesses read — **one** produced a confirmed address. One in eight is a fair expectation
  for a tight local category; a category where businesses are more web-native does better.
  You are charged per lead precisely because of this ratio, so the misses cost you nothing.
- **Only pages the business publishes.** No logins, no accounts, no pages behind a challenge.
  A site that blocks us is skipped, not worked around.
- **Verification is a point in time.** A mailbox that answered today can be closed tomorrow.
  `email_checked_at` records when the server answered.
- **A catch-all cannot be resolved.** No verifier can, including the ones that claim otherwise.

### Sending to these addresses

These are addresses the business chose to publish on its own website. A named work address is
personal data in the EU and the UK. Whether you may write to any of them, and to a named person
in particular, is your responsibility and depends on where you and they are: GDPR, PECR,
CAN-SPAM and their local equivalents all apply to you, not to the data. Nothing here is consent
to be contacted.

### Data removal

A business that wants its details left out of future runs, or a person who wants their own
address or profile link left out, can write to **info@locomint.io**. A suppressed business is
dropped from every run before anything is delivered or charged, a suppressed address or profile
link is removed from every row, and removals are honoured in every Locomint listing.

Questions and bug reports belong in the Issues tab of this Actor.

# Actor input Schema

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

One category per line, the way you would type it into the map: dentist, roofing contractor, boutique hotel. Up to 25 per run.

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

A city and its country, so the answer is not ambiguous: "Porto, Portugal" rather than "Porto". A bare city name is resolved against the wrong country surprisingly often.

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

How many businesses to look at, not how many rows you get. Most businesses publish no address at all, so expect far fewer leads than this - and you are only charged for the ones that come back with a checked address.

## `deliver` (type: `string`):

"verified" means the mail server was asked about that exact mailbox and said yes - use this if the list is going into a mailing tool. "verified\_and\_risky" also keeps catch-all domains, where the server accepts every address and nobody can prove one way or the other; useful for research, risky for sending. "any" delivers every address found, checked or not, and charges for all of them.

## `maxEmailsPerPlace` (type: `integer`):

A contact page often lists several. Each check is a real conversation with someone's mail server, so this is deliberately small; the best verdict becomes the row's address and the rest are listed beside it.

## `smtpCheck` (type: `boolean`):

On by default, and it is the point of this Actor. Turned off, addresses are only checked for syntax, a real mail server on the domain, disposable providers and role mailboxes - and nothing is reported as verified, so with "verified" selected you will get no rows at all.

## `websiteFilter` (type: `string`):

Skipped by default: a business with no website has no page to read an address from, so reading it further would cost you money for a row that cannot exist. This filter is applied to the search result before anything else is fetched.

## `placeMinimumStars` (type: `string`):

Leave empty for all. Businesses with no rating at all do not pass this filter.

## `skipClosedPlaces` (type: `boolean`):

A closed business is not a lead.

## `categoryFilterWords` (type: `array`):

Leave empty for all. One word or phrase per line, matched against the business's own category.

## `includeDetails` (type: `boolean`):

Off by default. A lead row already has name, address, phone, website, rating and category from the search itself. Turning this on adds opening hours, photos and the description, at the cost of a second fetch per business - measured at about 20% more run time for the same leads.

## Actor input object example

```json
{
  "searchStringsArray": [
    "dentist"
  ],
  "locationQuery": "Porto, Portugal",
  "maxCrawledPlacesPerSearch": 20,
  "deliver": "verified",
  "maxEmailsPerPlace": 3,
  "smtpCheck": true,
  "websiteFilter": "with",
  "placeMinimumStars": "",
  "skipClosedPlaces": true,
  "includeDetails": false
}
```

# Actor output Schema

## `leads` (type: `string`):

One row per business that has an email address the mail server confirmed: the full place record - name, address, phone in E.164, website, rating, categories, opening hours - plus the address itself, the verdict behind it, and every other address found on that site with its own verdict. A business with no website, none published on its page, or none that passed the check is left out and costs nothing.

# 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": [
        "dentist"
    ],
    "locationQuery": "Porto, Portugal"
};

// Run the Actor and wait for it to finish
const run = await client.actor("locomint/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": ["dentist"],
    "locationQuery": "Porto, Portugal",
}

# Run the Actor and wait for it to finish
run = client.actor("locomint/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": [
    "dentist"
  ],
  "locationQuery": "Porto, Portugal"
}' |
apify call locomint/google-maps-email-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,locomint/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/kd6XPubU58qg8ShxI/builds/Oq2Av3zKMh2FQ8lCg/openapi.json
