The Gmail check answers whether an e-mail address has a Gmail account. It is meant for gmail.com and googlemail.com addresses and runs in bulk jobs. Each address gets registered: true, false or null (unknown), with the time we checked. We never send mail to the address, open the mailbox or return anything about the account holder.
What does the Gmail check tell you?
It tells you whether Google has a Gmail account at the address, exactly as written. That is the right question for lists and sign-up data where Gmail makes up a large share of consumer addresses.
registered: true: a Gmail account exists at this address.registered: false: a conclusive "no account".registered: null: no conclusive answer (statusandreasonsay why), and you are not charged.
Two Gmail behaviours matter here. Google's help center explains that Gmail ignores dots in the part before the @ (j.doe and jdoe reach the same inbox) and that [email protected] delivers to [email protected]. We deliberately do not rewrite addresses. We only trim and lowercase them. So [email protected] and [email protected] are separate rows and are not treated as duplicates. If you want one row per inbox, collapse the spellings before you send them.
Who uses it, and why?
Gmail is used by consumers worldwide, so fake and mistyped Gmail addresses show up in almost every sign-up funnel.
- Cleaning sign-up and CRM data. A batch run over last month's sign-ups shows which Gmail addresses never existed. Those are usually typos, or accounts created with invented addresses. See OTP and sign-up fraud.
- Deliverability before a transactional send. Removing Gmail addresses without an account prevents hard bounces, which count against your sender reputation with large mailbox providers.
- Lead scoring. A lead with a real Gmail address is more likely to be reachable. A non-existent one is usually not worth a follow-up. See lead verification.
googlemail.com still appears in older records. Google used it in some countries, notably Germany and the UK, while it could not use the Gmail name there. Both domains reach Gmail, so both belong in the same check. For a real-time answer at sign-up, use the mailbox check.
What do you get back?
Every address becomes a row with kind: "email" and one entry per check in checks.
| Field | Type | Meaning |
|---|---|---|
email | string or null | Normalized address (trimmed, lowercased); null when invalid |
email_status | enum | valid, invalid_email, duplicate or suppressed |
checks["gmail.email"].registered | boolean or null | true account exists, false none, null unknown |
…status | enum | completed, pending, unknown, unsupported_country or failed |
…reason | string or null | Why an answer is not conclusive, e.g. UPSTREAM_TIMEOUT |
…confidence, …checked_at | enum, timestamp | How sure the answer is, and when it was obtained |
…cached, …billed | boolean | Cache hit, and whether it was charged |
In job results and downloads, the input is masked (re•••@test.mobilevalidate.com). CSV and NDJSON downloads add gmail.email.status, gmail.email.registered and gmail.email.billed columns.
How is it billed?
You pay the bulk price per address, only for conclusive answers. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). POST /v1/jobs/estimate is free. It shows how many rows are valid, invalid, duplicate or already cached, and the most the job could cost. Pass that figure as max_cost to cap the job. See pricing.
Repeat checks of the same address inside the freshness window can come from your account's cache. Cache hits are free (cached: true, billed: false).
What are the limits?
The Gmail check is bulk only. POST /v1/lookup refuses it with 403 service_disabled ("The check 'gmail.email' is available in bulk jobs only (POST /v1/jobs)."). A job holds up to 50,000 numbers and e-mails. You can send them as JSON emails, or as a CSV upload with an email column.
- Up to 20 checks per request. The total of identifiers × applicable checks is capped at 100,000 per job.
- Requests that look like sequential number ranges or generated e-mail lists are rejected. Twenty or more addresses in one request whose local parts differ only by digits or separators are refused with
suspected_enumeration.googlemail.comcounts asgmail.comfor this rule. Live keys are also limited to 50 addresses of one such pattern per account per UTC day. - The daily cap counts e-mail addresses like phone numbers (see
GET /v1/limits). - Job data is kept for 30 days by default, and you can purge a job earlier with
DELETE /v1/jobs/{id}.
How do I use it responsibly?
Check addresses people gave you: customers, sign-ups and leads who asked to be contacted. We never read mailboxes or send e-mail to the address. The answer is yes, no or unknown, and never includes a name, avatar or profile.
Don't generate address variations to find out whether someone has a Gmail account, and don't build lists for unsolicited mail. The acceptable use policy forbids both. Anyone can object to having their address checked through the opt-out form. Suppressed addresses are skipped and never charged.
Example request
Test keys return fixed answers for addresses on test.mobilevalidate.com (see test mode). This job checks one registered address, one unregistered address and one address that answers unknown.
curl https://api.mobilevalidate.com/v1/jobs \
-H "Authorization: Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" \
-H "Content-Type: application/json" \
-d '{"emails":["[email protected]","[email protected]","[email protected]"],"checks":["gmail"]}'// npm install mobilevalidate · Node.js 20+ · save as check.mjs and run: node check.mjs
import { MobileValidate } from "mobilevalidate";
// Omit apiKey to read MOBILEVALIDATE_API_KEY from the environment.
const mv = new MobileValidate({ apiKey: "mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" });
const { data, error } = await mv.jobs.create({
emails: [
"[email protected]",
"[email protected]",
"[email protected]",
],
checks: ["gmail"],
});
if (error) console.error(error.code, error.message);
else console.dir(data, { depth: null });// Node.js 18+, Deno, Bun or the browser console (ES module: save as .mjs or use "type": "module").
const res = await fetch("https://api.mobilevalidate.com/v1/jobs", {
method: "POST",
headers: {
Authorization: "Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym",
"Content-Type": "application/json",
},
body: JSON.stringify({
emails: [
"[email protected]",
"[email protected]",
"[email protected]",
],
checks: ["gmail"],
}),
});
console.log(res.status, res.headers.get("x-request-id"));
console.dir(await res.json(), { depth: null });# pip install mobilevalidate-sdk
from mobilevalidate import MobileValidate
# Omit api_key to read MOBILEVALIDATE_API_KEY from the environment.
mv = MobileValidate(api_key="mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym")
result = mv.jobs.create(
emails=[
"[email protected]",
"[email protected]",
"[email protected]",
],
checks=["gmail"],
)
print(result)<?php
$ch = curl_init('https://api.mobilevalidate.com/v1/jobs');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'emails' => [
'[email protected]',
'[email protected]',
'[email protected]',
],
'checks' => ['gmail'],
]),
]);
$response = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_RESPONSE_CODE), PHP_EOL, $response, PHP_EOL;package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{"emails":["[email protected]","[email protected]","[email protected]"],"checks":["gmail"]}`)
req, err := http.NewRequest("POST", "https://api.mobilevalidate.com/v1/jobs", body)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}require "net/http"
require "json"
uri = URI("https://api.mobilevalidate.com/v1/jobs")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym"
req["Content-Type"] = "application/json"
req.body = JSON.generate({
"emails" => [
"[email protected]",
"[email protected]",
"[email protected]"
],
"checks" => ["gmail"]
})
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.code, res.bodyRuns as pasted with the public sandbox key, which answers the test values only. In the SDKs, omit the key to use MOBILEVALIDATE_API_KEY.
Response of GET /v1/jobs/{id}/results (excerpt, test mode: the first item of data):
{
"kind": "email",
"input": "re•••@test.mobilevalidate.com",
"email": "[email protected]",
"email_status": "valid",
"e164": null,
"country": null,
"checks": {
"gmail.email": {
"service": "gmail.email",
"status": "completed",
"registered": true,
"attributes": null,
"confidence": "high",
"confidence_score": 0.99,
"checked_at": "2026-09-25T14:25:31.513Z",
"cached": false,
"age_seconds": 0,
"billed": false,
"reason": null,
"poll_after_ms": null
}
},
"test": true
}Try it now
Bulk-only service: create a job via POST /v1/jobs, then read GET /v1/jobs/{id}/results. Every example runs as pasted: it uses the public sandbox key, which answers the test values for free. In the SDKs, omit the key to use MOBILEVALIDATE_API_KEY.
curl https://api.mobilevalidate.com/v1/jobs \
-H "Authorization: Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" \
-H "Content-Type: application/json" \
-d '{"emails":["[email protected]"],"checks":["gmail.email"]}'// npm install mobilevalidate · Node.js 20+ · save as check.mjs and run: node check.mjs
import { MobileValidate } from "mobilevalidate";
// Omit apiKey to read MOBILEVALIDATE_API_KEY from the environment.
const mv = new MobileValidate({ apiKey: "mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" });
const { data, error } = await mv.jobs.create({
emails: ["[email protected]"],
checks: ["gmail.email"],
});
if (error) console.error(error.code, error.message);
else console.dir(data, { depth: null });// Node.js 18+, Deno, Bun or the browser console (ES module: save as .mjs or use "type": "module").
const res = await fetch("https://api.mobilevalidate.com/v1/jobs", {
method: "POST",
headers: {
Authorization: "Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym",
"Content-Type": "application/json",
},
body: JSON.stringify({
emails: ["[email protected]"],
checks: ["gmail.email"],
}),
});
console.log(res.status, res.headers.get("x-request-id"));
console.dir(await res.json(), { depth: null });# pip install mobilevalidate-sdk
from mobilevalidate import MobileValidate
# Omit api_key to read MOBILEVALIDATE_API_KEY from the environment.
mv = MobileValidate(api_key="mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym")
result = mv.jobs.create(
emails=["[email protected]"],
checks=["gmail.email"],
)
print(result)<?php
$ch = curl_init('https://api.mobilevalidate.com/v1/jobs');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'emails' => ['[email protected]'],
'checks' => ['gmail.email'],
]),
]);
$response = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_RESPONSE_CODE), PHP_EOL, $response, PHP_EOL;package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{"emails":["[email protected]"],"checks":["gmail.email"]}`)
req, err := http.NewRequest("POST", "https://api.mobilevalidate.com/v1/jobs", body)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}require "net/http"
require "json"
uri = URI("https://api.mobilevalidate.com/v1/jobs")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym"
req["Content-Type"] = "application/json"
req.body = JSON.generate({
"emails" => ["[email protected]"],
"checks" => ["gmail.email"]
})
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.code, res.body// .mcp.json (Claude Code), ~/.cursor/mcp.json (Cursor) or .vscode/mcp.json ("servers" instead of "mcpServers")
{
"mcpServers": {
"mobilevalidate": {
"type": "http",
"url": "https://mcp.mobilevalidate.com/mcp",
"headers": { "Authorization": "Bearer ${MOBILEVALIDATE_API_KEY}" }
}
}
}
// Then ask your agent, e.g.:
// "Check [email protected] with gmail.email" → tool: create_lookup_jobThe hosted MCP server is live for customers with an agent key (mv_agent_…) or a test key. Or run it locally with npx -y @mobilevalidate/mcp.
Real test-mode result for this check
{
"service": "gmail.email",
"status": "completed",
"registered": true,
"attributes": null,
"confidence": "high",
"confidence_score": 0.99,
"checked_at": "2026-09-25T14:25:29.021Z",
"cached": false,
"age_seconds": 0,
"billed": false,
"reason": null,
"poll_after_ms": null
}What you get
Fields of checks["gmail.email"], generated from the public service catalog.
| Field | Type | Meaning |
|---|---|---|
| registered | boolean | null | true = found, false = not found, null = unknown (not charged). |
| status | enum | completed, pending, unknown, unsupported_country or failed. |
| checked_at | timestamp | When the answer was obtained. |
Frequently asked questions
Does the check send an e-mail or sign in to the account?
No. Nothing is sent to the address and no mailbox is opened. The check only answers whether a Gmail account exists for the address: yes, no or unknown.
Gmail ignores dots in addresses. Do you merge [email protected] and [email protected]?
No. We only trim spaces and lowercase the address. Each address is checked as you sent it, so two spellings are two rows. If you want to merge them, normalize them yourself before sending.
Why is the Gmail check only available in bulk jobs?
The Gmail check is offered in bulk jobs only for now. Send it with POST /v1/jobs. For a real-time answer at sign-up, use the mailbox check (email). GET /v1/services always shows the current modes.
Does googlemail.com count as Gmail?
Yes, both domains are Gmail. For anti-enumeration, googlemail.com addresses are grouped with gmail.com addresses, so splitting a generated list across the two domains does not get around the limit.
What happens to addresses that are not on Gmail?
The check is meant for gmail.com and googlemail.com addresses. Answers for other domains may be unknown, and unknown answers are not charged.

