Get an API key
Sign in and create a key in your API keys settings. The key is shown once and cannot be recovered, so copy it straight away. There are two key types: a sandbox key (prefix es_test_) returns deterministic fixtures and spends nothing, so you can build against it first; a live key (prefix es_live_) verifies real addresses. The free tier gives you 100 live verifies a month. See sandbox testing for the test addresses.
Export it as an environment variable so it never touches your code. Every example below reads ES_KEY from the environment and sends it in the X-API-Key header.
export ES_KEY="4f8c92a17b3d…"
Install the SDK
Pick your language. Each SDK is a thin wrapper over one HTTPS endpoint, all open source. If you would rather not add a dependency, curl works everywhere and needs nothing.
# node 18+ · ESM or CommonJS npm install @emailsherlock/node
# python 3.9+ pip install emailsherlock-sdk
# go 1.21+ go get github.com/Emailsherlock1/go
# php 8.1+ composer require emailsherlock/client
# no SDK needed, curl is enough curl --version
Run your first check
One address in, one result out, typically in under a second. The call on the right is complete and copy-paste ready. Swap the address for one of your own.
The body is a single field, email. The response is one result object with the verdict and every signal behind it. The same shape comes back from the batch endpoint, one per address.
curl https://api.emailsherlock.com/v1/credits \ -H "X-API-Key: $ES_KEY" \ -H "Content-Type: application/json" \ -d '[]'
import { Emailsherlock } from '@emailsherlock/node';
const es = new Emailsherlock(process.env.ES_KEY);
const result = await es.credits({ });
from emailsherlock import Emailsherlock es = Emailsherlock(api_key=os.environ["ES_KEY"]) result = es.credits()
import emailsherlock "github.com/Emailsherlock1/go"
es := emailsherlock.New(os.Getenv("ES_KEY"))
result, err := es.Credits(ctx, )
<?php
use Emailsherlock\Client;
$es = new Client(getenv('ES_KEY'));
$result = $es->credits([]);
{
"plan": "Free",
"sandbox": false
}
Read the result
The result field is the verdict. The booleans and the score are the signals behind it, so you can branch on whichever matters to your flow.
|
emailstring
The address you sent.
|
|
resultstring
The verdict for this address. One of valid invalid catch_all disposable role unknown.
|
|
deliverableboolean · nullable
Proven deliverability: true only after the mailbox accepted RCPT TO, false only when the address provably fails (bad syntax, no MX, hard reject). Null means unproven, not bad: disposable, role and catch_all short-circuit before the SMTP probe.
|
|
reasonstring · nullable
Why the pipeline decided. greylisted, smtp_timeout, smtp_unreachable and verification_pending are transient: retry the address later. Null on results cached before reasons existed. One of bad_syntax no_mx mailbox_accepts mailbox_not_found disposable_provider role_address catch_all_domain accept_all_provider greylisted smtp_timeout smtp_unreachable verification_pending blocked_domain spam_trap .
|
|
mxboolean
The domain has reachable MX records.
|
|
mx_recordstring · nullable
The primary MX host, when one was resolved during this check.
|
|
disposableboolean
Throwaway / temporary-mail provider.
|
|
roleboolean
Role address such as info@ or sales@.
|
|
catch_allboolean
Host accepts mail for any local part.
|
|
free_emailboolean · nullable
The domain is a freemail provider (gmail.com, web.de, ...). Null when the domain is not classified yet.
|
|
relayboolean
The address sits on a relay / forwarding / alias-masking service (SimpleLogin, Apple Hide My Email, Cloudflare Email Routing, ...). The address is deliverable and reaches a real person, it is just masked, so this never changes the verdict or decision. Use it to apply your own policy on masked senders.
|
|
relay_providerstring · nullable
Name of the relay provider when relay is true, null otherwise.
|
|
parkedboolean
The address is on a parked or for-sale domain. Informational only: it never changes the verdict, but a parked domain rarely runs a real mailbox.
|
|
spam_trapboolean
The mail host is a known or suspected spam trap. Informational only: it never changes the verdict, and it is never a hard block. But sending to a trap hurts your sender reputation, so the recommendation drops to review. See spam_trap_type for how it was identified.
|
|
spam_trap_typestring · nullable
How the trap was identified: known_trap_domain (the mail host is on our curated trap registry) or reputation_heuristic (it matched a conservative parked + spam-list heuristic). Null when spam_trap is false. One of known_trap_domain reputation_heuristic .
|
|
suggested_correctionstring · nullable
A corrected address when the domain looks like a typo of a high-volume domain (gmial.com -> [email protected]). Null when nothing looks mistyped. Advisory only: it never changes the verdict, so show it as a "did you mean" hint and let the sender decide.
|
|
smtp_diagnosisstring · nullable
Enhanced scan only: the granular mailbox state read from the SMTP reply (mailbox_full, mailbox_disabled, mailbox_not_found, greylist_deferred, policy_block, accepted). Null on Quick / Standard scans, which do not run the deep probe. Explains the verdict, never overrides it. One of accepted mailbox_full mailbox_disabled mailbox_not_found greylist_deferred policy_block .
|
|
scorenumber · nullable
0-1 confidence, higher is safer to send to.
|
|
freshnessstring
How recent the underlying data is. One of fresh cached_recent cached_stale_refreshed.
|
|
checked_atstring · nullable
When the underlying verification ran (ISO 8601). On cached results this is the original check, not the request time.
|
|
domainobject · nullable
Domain-level intelligence. Null when the domain has not been crawled yet.
|
|
decisionobject
|
|
degradedboolean
Email-Guard only: true when your monthly remote-decision quota is used up and the Verify API declined to run a remote check. The SDK falls back to its local checks (syntax + the bundled disposable list) until the quota resets at the start of next month. false on every normal response. See docs/decisions/email-guard.md.
|
Move to production
Your first call already hit the live API, so there is no key or endpoint to swap. Four things separate a demo from a production integration.
/v1/verify/jobs/{id}429