E-mail checks
Check e-mail addresses with the MobileValidate API: which services exist, how addresses are normalized, result rows, and anti-enumeration rules.
Last updated
View as MarkdownE-mail checks answer whether a mailbox exists, or whether an account on a platform is registered with an address. Send the addresses in emails (next to or instead of numbers) and request at least one e-mail check. Answers are registered: true, false or null only. They never include names, avatars or profile data.
Which e-mail services are there?
| Code | Alias | Answers | Modes |
|---|---|---|---|
email.valid | email | The mailbox exists (major webmail providers) | real time + bulk |
gmail.email | gmail | A Gmail account exists | bulk only |
outlook.email | outlook | An Outlook account exists | bulk only |
yahoo.email | yahoo | A Yahoo account exists | bulk only |
yandex.email | yandex | A Yandex account exists | bulk only |
mailru.email | mailru | A Mail.ru account exists | bulk only |
apple.email | apple.email | An Apple Account uses this address | real time + bulk |
amazon.email, facebook.email, instagram.email, netflix.email, spotify.email | — | An account on that platform uses this address | real time + bulk |
linkedin.email, x.email | — | An account on that platform uses this address | bulk only |
Bulk-only services work in bulk jobs. On POST /v1/lookup they return 403 service_disabled. GET /v1/services is the live list for your key (input_type: "email").
How do I send e-mails in a request?
curl https://api.mobilevalidate.com/v1/lookup \
-H "Authorization: Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym" \
-H "Content-Type: application/json" \
-d '{"emails":["[email protected]","[email protected]"],"checks":["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.lookup({
emails: [
"[email protected]",
"[email protected]",
],
checks: ["email"],
});
if (error) console.error(error.code, error.message);
else console.dir(data.results, { 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/lookup", {
method: "POST",
headers: {
Authorization: "Bearer mv_test_publicSandboxn9ZgneuhR1B9CRfKG3fulym",
"Content-Type": "application/json",
},
body: JSON.stringify({
emails: [
"[email protected]",
"[email protected]",
],
checks: ["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.lookup(
emails=[
"[email protected]",
"[email protected]",
],
checks=["email"],
)
print(result)<?php
$ch = curl_init('https://api.mobilevalidate.com/v1/lookup');
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]',
],
'checks' => ['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]","[email protected]"],"checks":["email"]}`)
req, err := http.NewRequest("POST", "https://api.mobilevalidate.com/v1/lookup", 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/lookup")
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]"
],
"checks" => ["email"]
})
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.
Phone checks run on numbers and e-mail checks on emails, so one request can mix both, up to 100 identifiers in total. If you send e-mails without an e-mail check, the request is refused before anything is stored: 400 invalid_request, param: "checks", "No e-mail check was requested: add an e-mail check (e.g. email, gmail…) for the emails. See GET /v1/services."
What does an e-mail result row look like?
Real test-mode output, first row:
{
"kind": "email",
"input": "[email protected]",
"email": "[email protected]",
"email_status": "valid",
"e164": null,
"country": null,
"checks": {
"email.valid": {
"service": "email.valid",
"status": "completed",
"registered": true,
"attributes": null,
"confidence": "high",
"confidence_score": 0.99,
"checked_at": "2026-09-25T14:23:15.781Z",
"cached": false,
"age_seconds": 0,
"billed": false,
"reason": null,
"poll_after_ms": null
}
},
"test": true
}E-mail rows have kind: "email", the normalized email (null if invalid), and email_status: valid, invalid_email, duplicate or suppressed. e164 and country are always null, and there is no number_status or whatsapp copy. Rows are ordered numbers first, then e-mails, in input order. The second test address above returns status: "unknown", reason: "UNSUPPORTED_PROVIDER", billed: false.
How are addresses normalized?
Addresses are trimmed and lowercased, and nothing else. We do not remove dots or +tags, and we do not map one provider domain to another, because such rewriting could merge addresses that belong to different people. An address that isn't syntactically valid gets email_status: "invalid_email" and no checks. A repeat of an earlier address in the same request is duplicate. An address on the suppression list is suppressed. None of these are billed. When you read results back later, input is masked (re•••@test.mobilevalidate.com) and email holds the normalized address.
What are the anti-enumeration rules for e-mails?
Requests that look like generated e-mail lists are rejected with 403 suspected_enumeration, param: "emails":
- Per request: 20 or more distinct addresses on one domain whose local parts differ only by digits and/or separators. Every run of non-letters counts as one class, so
john1@,john.2@,john_3@,john-4@andjohn+5@fall into one group, andgooglemail.comis grouped withgmail.com. - Per day (live keys): 50 or more distinct addresses of one such pattern from the same account in one UTC day, across all requests. Splitting a generated list into small requests doesn't get around this.
The account's daily cap counts e-mails the same way as numbers. E-mail checks are for fraud prevention and deliverability, such as screening sign-ups and keeping lists clean. They are not for discovering who owns an address.
Frequently asked questions
Do you send an e-mail to the address or log in to the mailbox?
No. Nothing is sent to the address and no mailbox is read. The answer is only yes, no or unknown.
Why is my company domain unknown for the email check?
The email check covers major webmail providers. Addresses on other domains return unknown with reason UNSUPPORTED_PROVIDER, which is not billed.
Do you treat john.smith@ and johnsmith@ as the same address?
No. We only trim spaces and lowercase the address. Dots and plus tags are kept, because rewriting them could merge different people's addresses.

