Know an address exists before you send a thing.
VerifyGenius tells you which of the contact data you already hold is safe to use, starting with email. It asks the company’s own mail server whether a business address is real, then hangs up. No email is ever sent, and only addresses we can prove are called safe to send.
Built for UK business domains. Email today; phone numbers and postal addresses are next. Private for now, running for our own products.
- 1
EHLO api.verifygenius.com250 mx.acme-dental.example - 2
MAIL FROM:<verify@verifygenius.com>250 2.1.0 OK - 3
RCPT TO:<zz4c19e07a2b91d3f6e0@acme-dental.example>550 5.1.1 User unknownMade-up address first. The server says no, so its yes will mean something. - 4
RCPT TO:<jane.cole@acme-dental.example>250 2.1.5 OK DATANever sent. No message, ever.- 5
QUIT221 Bye
Six answers. Only one of them means send.
Most checkers round “probably” up to “valid”. We don’t. If the server can’t prove it, we say so, and you keep your bounce rate.
validThe server refused a made-up address and accepted this one, on the same machine.Yes, for 30 daysinvalidThe server said there is no such user, or the domain has no mail server at all.Nocatch_allThe server accepts any address, real or not, so its “yes” proves nothing.NounknownNo clear answer: greylisting, a policy reply, an unreachable server, or a reply we haven’t calibrated.NosuppressedThe address, company or domain has opted out. We didn’t check it and won’t.Nobad_syntaxNot an email address, so nothing was checked.NoWhat happens in one check
The order matters. Each step exists because a real mail server taught us to add it.
- 1
Opt-outs first
If the address, its company or its domain (parent domains included) has opted out, we stop here and return
suppressed. No connection is made. - 2
Find the mail server
We look up the domain’s MX records. No mail server means
invalid; a DNS failure meansunknown, never a guess. - 3
Ask about someone who doesn’t exist
A made-up address goes first, the same one for that domain all month, so greylisting can clear on the retry. If the server accepts it, the domain is
catch_alland we stop. - 4
Ask about the real address
The answer only counts if it comes from the same server that refused the made-up one. A different server answering is
unknown. - 5
Read the reply with calibrated rules
Only a clear “no such user” is
invalid. Anything naming a policy, our IP or the sender staysunknown. Microsoft 365’s550 5.4.1counts only when another address at the same company has been accepted, because Microsoft also uses it for faults that affect a whole tenant. - 6
Hang up
QUITbeforeDATA, every time. A greylisted server gets one more try, 20 minutes later. Then we move on.
We tried to fool it 530 times. It never said yes.
One square per made-up address. A square would be filled if we had called it valid. None are.
- 0 of 152
- addresses gave a different answer when asked again hours later. A day later, 100 of 100 matched.
- 149 of 207
- addresses printed on companies’ own websites came back valid. 3 had gone stale, and those are the ones that bounce.
- 0 refusals
- because of our IP address. It isn’t on Spamhaus, SpamCop, Barracuda, PSBL, Mailspike or UCEPROTECT.
- 22% undecided
- of domains, on purpose. It was 67% before the reply rules, and 16% outside Microsoft 365. We’d rather tell you “unknown” than guess.
Method: 200 UK companies with a website proven by their company number, about 1,030 checks from the production IP address across four passes, 27 September 2026, plus a repeat the next day. Each company got a made-up address and up to three addresses published on its own site.
API
JSON over HTTPS, one bearer key per product. The same API runs our own products today. Early-access keys come with your base URL; examples use $VG_BASE.
Quick start
curl -s "$VG_BASE/v1/check" \
-H "Authorization: Bearer $VG_KEY" \
-H "Content-Type: application/json" \
-d '{"email": "jane.cole@acme-dental.example"}'import os, requests
r = requests.post(
f"{os.environ['VG_BASE']}/v1/check",
headers={"Authorization": f"Bearer {os.environ['VG_KEY']}"},
json={"email": "jane.cole@acme-dental.example"},
timeout=70,
)
result = r.json()
if r.status_code == 200 and result["status"] == "valid":
send_later(result["email"]) # sendable for 30 days from checked_atconst r = await fetch(`${process.env.VG_BASE}/v1/check`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VG_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ email: "jane.cole@acme-dental.example" }),
});
const result = await r.json(); // 202 = still checking: poll /v1/jobs/{job_id}Response, 200:
{
"email": "jane.cole@acme-dental.example",
"status": "valid",
"reason": null,
"smtp_code": 250,
"smtp_text": "2.1.5 OK",
"mx_host": "mx.acme-dental.example",
"checked_at": "2026-09-28 11:02:13.482117+00:00",
"job_id": "5b0e7d0c-3f6a-4c55-9a1e-0f3c2d8b7a14"
}Keys
Send Authorization: Bearer vg_… on every request except health and the OpenAPI document. Each product gets its own key. We store only a SHA-256 hash, so a key is shown once, when it is issued; if you lose it, we revoke it and issue another. Jobs are private to the key that created them.
POST /v1/check
Checks one address and waits up to 60 seconds for the answer. Most answers take a few seconds. A slow server or greylisting returns 202 with the item still pending or checking; keep the job_id and read it later.
{ "email": "jane.cole@acme-dental.example" }POST /v1/jobs
Queues up to 5,000 addresses and returns at once. Addresses that aren’t addresses come back as bad_syntax without a check.
{ "emails": ["jane.cole@acme-dental.example", "accounts@acme-dental.example"] }{ "job_id": "9d2f4b61-8c0e-4e7a-b3d5-2a6f1c9e0b87", "total": 2 }GET /v1/jobs/{id}
The job and its results so far, up to 1,000 per page: pass offset and limit, and follow next_offset (null on the last page). done counts finished items across the whole job; poll until it equals total. A job created with a different key returns 404.
{
"id": "9d2f4b61-8c0e-4e7a-b3d5-2a6f1c9e0b87",
"requested_by": "your-product",
"total": 2,
"done": 2,
"created_at": "2026-09-28 11:04:50.017342+00:00",
"items": [
{ "email": "jane.cole@acme-dental.example", "status": "valid", "reason": null,
"smtp_code": 250, "smtp_text": "2.1.5 OK", "mx_host": "mx.acme-dental.example",
"checked_at": "2026-09-28 11:05:02.311904+00:00" },
{ "email": "accounts@acme-dental.example", "status": "invalid", "reason": null,
"smtp_code": 550, "smtp_text": "5.1.1 User unknown", "mx_host": "mx.acme-dental.example",
"checked_at": "2026-09-28 11:05:06.908215+00:00" }
]
}Result fields
statuspending·checking·valid·invalid·catch_all·unknown·suppressed·bad_syntax. Onlyvalidis sendable, for 30 days fromchecked_at.reason- Why, when the status alone doesn’t say:
domain accepts any address,no_mx,mail server unreachable,answered by a different MX than the probe,microsoft 5.4.1 on every address: not trusted,catch-all undecided (451). Otherwisenull. smtp_code,smtp_text- The server’s own reply to
RCPT TO, so you can see the evidence rather than take our word for it. mx_host- The mail server that answered.
checked_at- When the answer came back (UTC). Empty while pending.
flagsrole(info@, sales@…),free_mail(Gmail, Outlook…),disposable(throwaway domains, from a public list) andplus_address(name+tag@). Worked out from the address alone; a flag never changes the verdict.suggestion- A likely intended address when the domain looks like a typo of a common provider, such as
jane@gmial.com→jane@gmail.com. Otherwisenull. score,reasons- 0–100 for sorting, never a second opinion:
valid95,catch_all45,unknown25, everything else 0; a role address costs 5, a disposable domain caps it at 10.reasonslists every rule that applied, e.g.["status_valid", "role_address"]. Empty while pending.
Errors
400Bad JSON, no addresses, or more than 5,000 in one job, or a bad offset/limit. The body says which.401Missing or revoked key: {"error": "a valid key is required (Authorization: Bearer vg_...)"}404No such job for your key, or no such route.500Our fault. Safe to retry.Limits, and why they’re low
Checks leave from one IP address with a published name, at up to 20 a minute, one conversation per domain at a time. A circuit breaker pauses everything if servers start refusing us. That pace keeps us welcome on the servers you want answers from. Hand-started checks jump the queue ahead of background work.
GET /v1/status · /v1/health · /v1/openapi.json
/v1/status returns queue counts, checks in the last hour, and whether the sending IP appears on any of eight blocklists. /v1/health needs no key. /v1/openapi.json is the machine-readable version of this page.
Saw us in your mail logs?
If api.verifygenius.com (169.58.33.41) connected to your server, that was a mailbox check, not an email. It asked whether one business address exists, then disconnected.
It never sends a message
EHLO, MAIL FROM, RCPT TO, QUIT. The conversation ends before
DATA.It is slow on purpose
One conversation per domain at a time, 20 checks a minute in total, and a pause whenever servers push back.
It says who it is
Reverse DNS, HELO and SPF all name
api.verifygenius.com. The sender isverify@verifygenius.com.It checks business addresses
Addresses published on UK companies’ own websites, or built from the names of a company’s registered directors, so later business emails don’t bounce.
Stop checks for your domain
Opt out here or email postmaster@verifygenius.com. Opt-outs are applied before any further check.
Questions
Can I use it today?
Not yet. VerifyGenius runs privately for Rajoka’s own products, including InsightBase and Certivus. We’re gauging interest before opening it up. Tell us your volume and use below and we’ll get in touch.
Is it only for email?
Email is first. VerifyGenius is a data sanitisation service: it checks contact data you already have, without contacting anyone. We plan to add phone numbers and postal addresses, with the same rule of showing the evidence behind every answer. Tell us in the form which you need.
What will it cost?
We haven’t set prices. Your answer on monthly volume helps us set them. Nobody is charged for joining the early-access list.
Why is so much “unknown”?
Because some servers won’t give a straight answer, and guessing is how you end up bouncing. Microsoft 365 is the main one: when a company’s tenant rejects every address, we can’t tell a missing mailbox from a tenant-wide block, so we say unknown. Outside Microsoft 365, 16% of domains stayed undecided in our tests.
Why isn’t catch-all “valid”?
A catch-all server says yes to anything, including addresses we made up a second earlier. That yes carries no information about the address you asked for, so we won’t call it safe to send.
Does it work on Gmail or personal addresses?
It is built and calibrated for business domains, mostly in the UK. Big free-mail providers often don’t answer mailbox questions honestly, and we don’t claim results we haven’t tested.
Where does it run?
On servers we run ourselves. Checks leave from a single IP address, 169.58.33.41, with its own reverse DNS, and nothing about a check is shared with anyone else.
Ask for early access
Tell us roughly how many addresses you check each month and what you’d use it for. We read every answer and reply by email.
We use these details only to reply to you about VerifyGenius. See how we handle them.
Your details, if you use the form
Rajoka Limited collects your name, work email, company and answers so it can reply about early access and gauge demand for VerifyGenius. We rely on our legitimate interest in responding to your request. Your details are stored on our own servers, and each request is emailed to our team through Resend, our email provider. They are never sold, and are deleted within 24 months, or sooner if you ask. To see, correct or delete them, email postmaster@verifygenius.com. You can complain to the Information Commissioner’s Office at ico.org.uk. This site is served through Cloudflare, and the form uses Cloudflare Turnstile to block spam. We count visits with Plausible analytics, self-hosted on Rajoka infrastructure in the United Kingdom, which uses no cookies.