The Mail.ru check answers whether an e-mail address has a Mail.ru account. It covers mail.ru and the provider's other domains: inbox.ru, list.ru, bk.ru and internet.ru. It runs in bulk jobs and returns registered: true, false or null (unknown) for each address, with the time we checked. Nothing is sent to the address and no mailbox is read.
What does the Mail.ru check tell you?
It tells you whether Mail.ru has a mailbox at the exact address you send. Mail.ru is one of the large Russian-language web mail services. Since 2021 it has been part of the VK group, after Mail.ru Group renamed itself VK. When people register, Mail.ru offers several domains. So a list with Russian-speaking customers typically contains @mail.ru, @inbox.ru, @list.ru, @bk.ru and @internet.ru.
Those domains work differently from Yandex's aliases. Each one is its own address space, so [email protected] and [email protected] can be two different people. That is one more reason we never rewrite or merge addresses.
registered: true: a Mail.ru mailbox exists at this address.registered: false: a conclusive "no account".registered: null: no conclusive answer, withstatusandreasonexplaining why. You are not charged.
Who uses it, and why?
The Mail.ru check matters for businesses whose sign-ups include users in Russia and other Russian-speaking markets, where Mail.ru and Yandex carry a large share of consumer e-mail.
- Bonus and trial abuse. Mass registrations often use invented addresses on any of the provider's domains. A batch run over new accounts flags addresses that never existed. See OTP and sign-up fraud.
- Deliverability. Dead Mail.ru addresses cause hard bounces. Removing them keeps receipts and security notices reaching the rest of your users.
- Lead and CRM quality. A form lead on
@bk.ruor@list.rulooks as plausible as one on@mail.ru. The check shows which of them are real mailboxes.
Run Mail.ru and Yandex together in one job to cover a Russian-language list. For phone-based checks in the same region, see the VK check.
What do you get back?
Each 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["mailru.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 | 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 |
Downloads (CSV or NDJSON) add mailru.email.status, mailru.email.registered and mailru.email.billed columns.
How is it billed?
You pay the bulk price per address with a conclusive answer. You're not charged for inconclusive results (unknown, unsupported country, timeout, invalid, duplicate). Run POST /v1/jobs/estimate first. It's free and shows the maximum cost, which you can then pass as max_cost. See pricing.
Repeat checks of the same address inside the freshness window can come from your account's cache. Cache hits are free.
What are the limits?
The Mail.ru check is bulk only. POST /v1/lookup refuses it with 403 service_disabled ("The check 'mailru.email' is available in bulk jobs only (POST /v1/jobs)."). A job takes up to 50,000 numbers and e-mails.
- Up to 20 checks per request. Identifiers × applicable checks are 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 on one domain whose local parts differ only by digits or separators are refused with
suspected_enumeration. 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 numbers (see
GET /v1/limits).
How do I use it responsibly?
Check addresses of people who gave them to you, or that you otherwise have a lawful reason to process. We never read mailboxes or send e-mail to the address. The answer is yes, no or unknown and contains no personal details.
Don't use the check to try the same name across mail.ru, bk.ru, list.ru and the other domains to find a person, and don't use it to prepare unsolicited mail. The acceptable use policy forbids both. Anyone can object through the opt-out form. Suppressed addresses are skipped and free.
Example request
Test keys return fixed answers for test.mobilevalidate.com addresses (see test mode).
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":["mailru"]}'// 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: ["mailru"],
});
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: ["mailru"],
}),
});
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=["mailru"],
)
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' => ['mailru'],
]),
]);
$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":["mailru"]}`)
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" => ["mailru"]
})
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": {
"mailru.email": {
"service": "mailru.email",
"status": "completed",
"registered": true,
"attributes": null,
"confidence": "high",
"confidence_score": 0.99,
"checked_at": "2026-09-25T14:25:50.198Z",
"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":["mailru.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: ["mailru.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: ["mailru.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=["mailru.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' => ['mailru.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":["mailru.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" => ["mailru.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 mailru.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": "mailru.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["mailru.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
Which domains belong to Mail.ru?
Mail.ru offers addresses on mail.ru and on its alternative domains inbox.ru, list.ru, bk.ru and internet.ru. Unlike aliases, these are separate addresses: [email protected] and [email protected] can belong to different people.
Does the check send an e-mail or read the mailbox?
No. Nothing is sent to the address and no mailbox is opened. The result is yes, no or unknown, never a name, avatar or profile.
Can I run the Mail.ru check in real time?
Not yet. It is offered in bulk jobs only, and POST /v1/lookup refuses it with service_disabled. Use POST /v1/jobs.
What happens with invalid or duplicate addresses in my file?
They are marked email_status invalid_email or duplicate, are never checked and are never charged.

