Google Maps Email Cleaner & Lead Verifier avatar

Google Maps Email Cleaner & Lead Verifier

Pricing

from $20.00 / 1,000 clean contacts

Go to Apify Store
Google Maps Email Cleaner & Lead Verifier

Google Maps Email Cleaner & Lead Verifier

Clean, dedupe and verify Google Maps leads from any scraper. Merges duplicate businesses, classifies every email (personal, info@, free, disposable), checks MX records and ranks contacts SAFE/REVIEW/WEAK with a 0-100 priority. CRM-ready. No LLM, no login.

Pricing

from $20.00 / 1,000 clean contacts

Rating

0.0

(0)

Developer

Turgay NANTA

Turgay NANTA

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a month ago

Last modified

Share

Google Maps Email & Contact Cleaner: Dedupe, Verify and Prioritize Leads

Scraped Google Maps leads are messy. The same business appears three times, phone numbers come in five formats, half the "emails" are info@ inboxes, and a few are throwaway addresses that will bounce and hurt your sender reputation.

This Actor turns raw Google Maps scraper output into a clean, de-duplicated, CRM-ready contact list and tells you which contacts are safe to email first. Every record gets a best email, an email type (corporate, personal, role inbox, free provider, disposable), a SAFE / REVIEW / WEAK contact-confidence verdict and a 0-100 priority score.

  • Works with any Google Maps scraper: connect its dataset, paste JSON, or let the Actor fetch places for you from a search term.
  • No LLM, no API keys, no login: fully deterministic rules, so the same input always gives the same output.
  • Pay per result: no subscription. See Pricing for worked examples.

Not a scraper. This is the cleaning and qualification layer that runs after the scrape, so you stop paying (in money and in bounces) for rows you should never have emailed.


At a glance

InputRaw Google Maps / directory records (records), an Apify dataset ID (datasetId), or search terms (searchTerms)
OutputOne clean row per unique business + a run summary with a "reach these first" list
Key columnspriority, name, phone, best_email, best_email_type, contact_confidence, contact_confidence_reason, website, socials_summary
Email checksFormat, role inbox, disposable domain, free provider, personal-name pattern, optional live MX record
DedupMulti-key merge across sources (website, phone, email), so fragments of the same business become one record
SpeedThousands of records per minute (pure Python, no browser)
Runs onApify Console, API, schedules, n8n, Make, Zapier, AI agents via MCP

Quick start (2 minutes)

  1. Run any Google Maps scraper from Apify Store and copy the dataset ID from its Storage tab.
  2. Open this Actor, paste the ID into Dataset ID, and click Start.
  3. Open the Contact Table view in the Dataset tab and sort by priority.

No data yet? Put a search such as dentist in Austin into Search terms and the Actor fetches places through an upstream Google Maps scraper first, then cleans them.

What one output row looks like

{
"priority": 100,
"name": "Bay Dental Clinic Downtown",
"phone": "+14155550142",
"website": "baydental.com",
"address": "120 Market St, San Francisco, CA",
"emails": ["jane.doe@baydental.com", "info@baydental.com"],
"best_email": "jane.doe@baydental.com",
"best_email_type": "corporate",
"contact_confidence": "SAFE",
"contact_confidence_reason": "corporate email + complete contact fields",
"socials_summary": "facebook:facebook.com/baydental · instagram:instagram.com/baydental",
"email_classes": [
{"email": "jane.doe@baydental.com", "type": "corporate", "personal": true, "score": 95},
{"email": "info@baydental.com", "type": "role_account", "personal": false, "score": 40}
]
}

Why people use it

Most Google Maps scrapers are very good at extracting data and do nothing about quality. You get what the listing says, in whatever format it says it. That creates four expensive problems:

  1. Duplicates. The same clinic appears as "Bay Dental", "Bay Dental Clinic Downtown" and a website-only row from a second source. You email it three times.
  2. Role inboxes. info@, contact@, sales@ go to a shared desk. They are not useless, but they are not a person, and treating them as one lowers reply rates.
  3. Bad addresses. Disposable or malformed addresses bounce. Bounces damage your domain reputation for every future campaign.
  4. No priority. A list of 2,000 rows with no ranking means someone has to eyeball it, or you blast everything and hope.

This Actor fixes all four in one pass and gives you a ranked list you can hand to a sales rep or import into a CRM without cleanup.


What it does, step by step

  1. Reads any shape of input. Field names are auto-detected: name / title / businessName / company, website / url / domain, phone / phoneUnformatted / phoneNumber, emails / email, socials / social / socialLinks, address / fullAddress, countryCode. You do not need to rename columns.
  2. Normalizes every field.
    • Websites become a lowercase canonical domain (https://www.BayDental.com/contact?utm=x becomes baydental.com).
    • Phones become one consistent format. International fields such as phoneUnformatted are preferred. When a number has no country code, the record's countryCode is used; if the country is unknown, the number is kept in national format and no country code is invented.
    • Social links are canonicalized and tracking parameters are removed (Facebook, Instagram, X/Twitter, LinkedIn, YouTube, TikTok, Pinterest, Telegram).
    • Emails are lowercased, validated and de-duplicated.
  3. Merges duplicates across sources. Records that share any strong key (website, phone, or email) are merged into one business, and missing fields are filled from the other fragments. A website-only row and an email-only row that share a phone number become one complete record. Two different branches that only share a name are not merged.
  4. Classifies every email. Each address is labeled corporate, free_provider, role_account, disposable or invalid, and personal-name addresses (jane.doe@, j_smith@) are detected so a real person ranks above a shared inbox.
  5. Picks the best email per business and gives a contact-confidence verdict with a plain-English reason.
  6. Scores priority 0-100 from the verdict, email quality and how complete the contact details are.
  7. Optionally checks MX records for the best email's domain. If the domain has no mail server, a SAFE verdict is downgraded to REVIEW.
  8. Writes a run summary with verdict counts, email-type counts and a top-10 "reach these first" list.

The contact-confidence verdict

VerdictMeaningWhat to do
SAFECorporate email and at least two other contact fields (phone, name, socials)Email first
REVIEWRole inbox only, free-provider address, or corporate email with thin contact detailsEnrich or verify before a cold campaign
WEAKNo valid email, or disposable addressCall, or skip for email outreach

Every verdict comes with contact_confidence_reason, for example:

  • corporate email + complete contact fields
  • role account only (info@/sales@) — find the person
  • free provider (gmail/hotmail) — corporate confirmation advised
  • corporate email but contact fields incomplete
  • disposable (throwaway) email
  • no valid email
  • no MX record found (live check)

How the priority score works

The score is transparent and deterministic, so you can explain it to anyone:

ComponentPoints
Verdict: SAFE / REVIEW / WEAK50 / 25 / 5
Best email type: corporate / free provider / role inbox / disposable25 / 12 / 6 / 0
Contact completeness: phone, website, address, socials (5 each)up to 20
Business name present5
Maximum100

Sort by priority and work top-down. The summary's reach_first_list already does this for the top 10.


Use cases

Local lead generation agencies

You scrape "plumbers in Texas" for a client and deliver 3,000 rows. Run this Actor before delivery: duplicates disappear, each business has one best email, and the client receives a ranked list with a clear reason for every verdict. Deliverables look professional and bounce rates drop.

Cold email and SDR teams

Import only SAFE rows into your sequencer for the first wave. Send REVIEW rows to an enrichment step (for example a people-finder) to turn info@ into a named contact. Leave WEAK rows for phone outreach. Your domain reputation stays healthy because disposable and invalid addresses never enter the campaign.

CRM imports and data hygiene

Before importing into HubSpot, Salesforce, Pipedrive or a Google Sheet, normalize phones and domains so your CRM's own dedup rules actually work. Re-run on each new scrape and merge against your existing list by website.

Franchise and multi-location research

Chains appear once per branch with different names. Because merging uses strong keys rather than names, branches with separate websites and phones stay separate, while the same branch scraped twice collapses into one record.

Market mapping and territory planning

Clean data gives honest counts. "How many dental clinics in this city have a reachable corporate email?" is one number in the summary (sendable_SAFE) instead of an afternoon in a spreadsheet.

AI agents and automations

The output is flat, typed and stable, which makes it easy for an AI agent or an n8n / Make workflow to branch on contact_confidence without extra parsing.


Input

Provide one of the three sources. Everything else is optional.

FieldTypeDescription
recordsarrayRaw business records. Paste a Google Maps scraper's output or your own list. Field names are auto-detected.
datasetIdstringThe ID of an Apify dataset holding raw records, for example the output of a Google Maps scraper run. When set, it is used instead of records (the example records are ignored).
searchTermsarray of stringsIf you have no data yet: the Actor calls an upstream Google Maps scraper with these terms (for example dentist in Berlin), then cleans the result.
maxPlacesintegerIn search mode, the maximum number of places fetched per term. Default 50.
liveMxCheckbooleanCheck that the best email's domain has a live MX record. Default off.

Example: clean an existing dataset

{
"datasetId": "YOUR_GOOGLE_MAPS_DATASET_ID",
"liveMxCheck": true
}

Example: paste records from anywhere

{
"records": [
{"title": "Bay Dental Clinic", "url": "https://www.baydental.com?utm=x",
"phone": "(415) 555-0142", "phoneUnformatted": "+14155550142", "countryCode": "US",
"emails": ["info@baydental.com"],
"social": ["https://facebook.com/baydental?ref=page_internal"]},
{"name": "Bay Dental Clinic Downtown", "website": "baydental.com",
"phoneNumber": "+1 (415) 555-0142", "email": "jane.doe@baydental.com"}
]
}

These two rows are the same business. The output is one merged record with both emails, the personal address chosen as best_email, and a SAFE verdict.

Example: start from a search term

{
"searchTerms": ["dentist in Austin", "orthodontist in Austin"],
"maxPlaces": 100
}

Output

Dataset rows (one per unique business)

FieldDescription
priority0-100 score; higher means contact first
nameBusiness name (the most complete version across merged fragments)
websiteCanonical domain
phoneNormalized phone; international format when the country is known
addressCleaned address
emailsAll valid emails found, de-duplicated
email_classesPer-email label: type, personal, role_account, disposable, free_provider, score
best_emailThe single best address to contact
best_email_typecorporate, free_provider, role_account, disposable or empty
contact_confidenceSAFE, REVIEW or WEAK
contact_confidence_reasonPlain-English reason for the verdict
mx_verifiedPresent when liveMxCheck is on: true, false or null (could not check)
socialsCanonical social profiles as {platform, url}
socials_summaryOne-line readable version for spreadsheets

The Contact Table view shows the most useful columns in priority order, ready to export as CSV, Excel, JSON or HTML.

Run summary (last row)

{
"_summary": {
"total_records": 1742,
"with_email": 1210,
"verdict_distribution": {"SAFE": 486, "REVIEW": 701, "WEAK": 555},
"email_type_distribution": {"corporate": 598, "role_account": 402, "free_provider": 196, "disposable": 14, "none": 532},
"sendable_SAFE": 486,
"reach_first_list": [
{"rank": 1, "business": "Bay Dental Clinic Downtown", "priority": 100,
"best_email": "jane.doe@baydental.com", "contact_confidence": "SAFE"}
]
}
}

The numbers above are illustrative; your run reports its own.


Pricing

Pay per event, no subscription. Current prices are always shown on the Pricing tab; at the time of writing they are:

EventWhen it is chargedPrice
actor-startOnce per run$0.0001
place-processed-100Per 100 input records read$0.30
enrichedPer clean, de-duplicated output record$0.02

Duplicates are merged before the per-record charge, so you never pay twice for the same business.

Input recordsUnique businesses after merge (example)Approximate cost
10085$0.30 + $1.70 = $2.00
1,000850$3.00 + $17.00 = $20.00
5,0004,100$15.00 + $82.00 = $97.00

In search mode the upstream Google Maps scraper bills its own usage separately in your Apify account. If no records are found, nothing is charged by this Actor.

Apify's free plan includes monthly platform credit, which is enough to test this Actor on a real list.


Integrations

Chain it after your Google Maps scraper

In Apify Console open your Maps scraper, go to Integrations, add Run another Actor, pick this Actor and map the default dataset ID to datasetId. Every scrape is then cleaned automatically.

Run it on a schedule

Create a schedule (daily or weekly) for your Maps scraper with the integration above. New businesses appear, duplicates stay merged, and your CRM receives only clean rows.

API

curl -X POST "https://api.apify.com/v2/acts/enezli~maps-contact-cleaner/run-sync-get-dataset-items?token=YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"datasetId": "YOUR_DATASET_ID"}'

Python

from apify_client import ApifyClient
client = ApifyClient("YOUR_TOKEN")
run = client.actor("enezli/maps-contact-cleaner").call(run_input={"datasetId": "YOUR_DATASET_ID"})
safe = [r for r in client.dataset(run["defaultDatasetId"]).iterate_items()
if r.get("contact_confidence") == "SAFE"]
print(len(safe), "contacts ready to email")

JavaScript

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('enezli/maps-contact-cleaner').call({ datasetId: 'YOUR_DATASET_ID' });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.filter((r) => r.contact_confidence === 'SAFE').length);

n8n, Make, Zapier

Use the official Apify node or app: Run Actor with this Actor, then Get dataset items, then route rows by contact_confidence (for example SAFE to your email tool, REVIEW to an enrichment step, WEAK to a call list).

AI agents (MCP)

This Actor is available through the Apify MCP server, so assistants such as Claude or Cursor can call it with a dataset ID and read back the ranked list.


Tips for best results

  • Keep countryCode in your data. Most Google Maps scrapers include it. It lets the Actor put every phone number in international format.
  • Turn on liveMxCheck before a cold campaign. It adds a DNS lookup per business and catches dead domains.
  • Merge multiple sources in one run. Put the output of two different scrapers into records together; cross-source merging is where the biggest cleanup happens.
  • Use REVIEW rows wisely. A role inbox at a small business often reaches the owner. For larger companies, find a named person first.
  • Re-run instead of editing by hand. The output is deterministic, so re-running on an updated scrape gives consistent results you can diff.

Limitations

  • This Actor does not visit websites to discover new emails. It cleans and qualifies the emails your scraper already found. If your scraper has a "contact details" or "website enrichment" option, turn it on for more emails to work with.
  • Email checks are rule-based plus optional MX lookup. There is no SMTP mailbox probe, so a corporate address on a valid domain can still bounce if that specific mailbox does not exist.
  • The disposable-domain list covers the most common providers, not every one in existence.
  • Merging uses website, phone and email. Two records with no shared strong key and different names are kept separate by design, to avoid merging different branches.

Troubleshooting

SymptomLikely causeFix
Run fails with "No input"None of records, datasetId, searchTerms was providedProvide one of the three
Few emails in the outputThe upstream scraper did not collect emailsEnable the scraper's contact or website enrichment option, then re-run this Actor on the new dataset
Phones without +The records have no countryCode and the numbers are in national formatKeep countryCode in the scraper output, or add it to your records
Two branches merged into oneThey share a website, phone or email in the source dataCheck the source; shared central phone lines are a common cause
mx_verified is nullThe DNS lookup timed outRe-run later; null never downgrades a verdict

FAQ

Is this a Google Maps scraper? No. It cleans and prioritizes the output of any Google Maps scraper. In search mode it calls an upstream scraper for you, but the value it adds is the cleaning, merging and verdicts.

Which scrapers does it work with? Any scraper or file that produces business records with names, websites, phones or emails. Field names are detected automatically, including the common Google Maps scraper formats.

Does it use AI or send my data to an LLM? No. All logic is deterministic Python. The only optional external lookup is a DNS MX query for email domains.

Will it invent country codes for phone numbers? No. If a number has no country code and the record has no countryCode, it is kept in national format.

Why is my row marked REVIEW even though it has an email? Because the best email is a shared inbox (info@), a free provider (gmail.com), or the contact details are thin. The contact_confidence_reason column says which.

Can I export to Excel or Google Sheets? Yes. Use the Dataset tab's export (CSV, Excel, JSON, HTML), or the Google Sheets integration.

Do I pay for duplicates? No. Duplicates are merged first; the per-record charge applies to unique businesses only.

Is the data I send stored? Only in your own Apify storage (dataset and run logs), under your account's retention settings.

Can I use it for GDPR-sensitive outreach? The Actor only processes the data you provide or that a public listing already shows. You remain responsible for having a lawful basis for contacting people in your jurisdiction. Business role inboxes and corporate addresses are flagged separately to help you apply your own policy.

I found a bug or need a field. Open an issue on the Issues tab. Requests with a sample input are fixed fastest.


Trademarks

This Actor is not affiliated with, endorsed by, or connected to Google or Google Maps. All product names are trademarks of their respective owners.