Updated 2026-09-29
An email age check estimates how long an address has existed, because a freshly created address behind an otherwise clean application is one of the strongest fraud signals there is. No API can return a true creation date - mailbox providers do not publish one - so this guide covers what can actually be established - and what the email age field in the API returns - how to establish it from breach dates and registration breadth, and how much weight the result will bear.
The question people actually want answered is “was this address made yesterday to apply to us?” The honest answer is that the only party who knows is the mailbox provider, and no provider exposes it. An age expressed in days is therefore always an estimate, however precise it looks - and precision is exactly what makes an estimate hard to argue with later.
This API returns no age field for that reason. Inventing one would break the rule the whole product rests on: a value you cannot audit is indistinguishable from a value that was verified, once it reaches a risk model.
A data breach is a dated snapshot of a service's user table. If an address is in it, the address existed on that date. That is not an inference - it is a record, and it is the strongest age evidence available from open sources.
{
"email": {
"address": "jane@example.com",
"age": {
"firstSeenAt": "2019-01",
"ageDays": 2464,
"source": "breach_records",
"estimate": true
},
"breaches": {
"listed": true,
"count": 2,
"firstSeenAt": "2019-01",
"lastSeenAt": "2024-01",
"records": [
{ "name": "Collection1", "date": "2019-01" },
{ "name": "Naz.API", "date": "2024-01" }
]
}
}
}The email module does this for you: email.age is the earliest dated breach record, with ageDays counted from the end of that period and estimate: true on every value. It adds nothing to the response time - the records are already fetched. Note what it is not: a creation date. An address first breached in 2019 may well have been created in 2009 and simply never appeared in an earlier corpus. The bound only ever runs one way.
Handle the null case explicitly. When the breach source is not configured, breaches is null rather than an empty array - “we did not look” is a different fact from “we looked and found nothing”, and collapsing them turns a missing check into a false conclusion about age. See the breach check guide.
The second signal is how many platforms the address is registered on. It carries no date, so it cannot produce a bound - but accounts accumulate over time, and an address registered across a dozen independent services has usually been in use for a while. Read it as corroboration for a breach-derived floor, never as a substitute for one.
Where breadth genuinely earns its place is the case breach data misses entirely: an address with no breach history and no registrations anywhere. That combination is the thin footprint described in the thin footprint guide, and it is more actionable than any single estimated age would be.
Derive the floor in your own code, from the raw breach dates, and keep it beside the facts it came from rather than replacing them. A reviewer who can see “earliest breach 2012-05-05, LinkedIn” can evaluate the reasoning; a reviewer who sees “email age: 14 years” can only trust it.
Then route on it rather than reject on it. Combine an unknown-or-recent floor with a thin registration result and low corroboration, and you have a case worth a human - which is exactly the outcome a signup risk check should produce.