Bulk Email Verifier: syntax, MX, disposable, role and typo avatar

Bulk Email Verifier: syntax, MX, disposable, role and typo

Pricing

from $0.60 / 1,000 email verifieds

Go to Apify Store
Bulk Email Verifier: syntax, MX, disposable, role and typo

Bulk Email Verifier: syntax, MX, disposable, role and typo

Check email lists without sending anything: syntax, mail server (MX) lookup, disposable domains, role accounts, free providers and typo suggestions. One row per address with a status and reasons. No login, no API key. Charged per address that gets a verdict.

Pricing

from $0.60 / 1,000 email verifieds

Rating

0.0

(0)

Developer

Edge

Edge

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

Security

Limited permissions

Share

Bulk Email Verifier: syntax, MX, disposable, role and typo checks

Paste a list of email addresses and get one row per address with a status (valid, invalid, risky or unknown), the reasons behind it, and flags for disposable domains, role accounts, free providers and likely typos. Nothing is sent to any address. No login, no API key, no website is scraped: the Actor asks DNS which mail server takes mail for each domain and compares the address with built-in lists.

By default this Actor does not check that the mailbox exists. It tells you whether the address is well formed, whether its domain exists and accepts mail, and whether it looks risky. An optional mailbox probe over SMTP (checkSmtp) is described below, with its limits.

What a run does, for each address

  1. Cleans the entry: removes a display name (Name <a@b.com>), mailto:, brackets and surrounding punctuation, splits entries that hold several addresses, removes duplicates ignoring upper and lower case, keeps the input order.
  2. Checks the syntax (a practical subset of the email address standard: local part up to 64 characters, whole address up to 254, valid domain labels, a top level domain of letters (whether it exists is settled by the DNS lookup); quoted local parts and IP-literal domains are rejected).
  3. Looks up the MX records of the domain over DNS. If there are none, it falls back to the domain's own A or AAAA record, as mail systems do. It checks that at least one mail host (of the first five by priority) resolves to an address. A domain with a "null MX" (a published statement that it takes no mail) is invalid.
  4. Compares with the built-in lists: 277 disposable domains (and their subdomains), 92 free provider domains, 68 role names, 116 known typo domains.
  5. Optionally probes the mailbox (see below).

The lists are written by hand and are not complete: new disposable services appear every week. A domain missing from the list is not a guarantee that it is a real mailbox provider.

Input

FieldTypeDefaultWhat it does
emailslist of textthree sample addressesOne address per line. Also accepted: Name <address>, mailto: links, several addresses in one line separated by commas or semicolons. Up to 100,000 entries; needed unless datasetId is set. An entry that is not an address becomes a row with status invalid.
checkSmtptrue or falsefalseOptional mailbox probe, see below.
timeoutSecondswhole number, 2 to 6010How long to wait for one DNS answer or one mail server reply. DNS lookups are tried twice.
maxEmailswhole number, 1 to 100,00010,000Stops reading the list after this many different addresses. Duplicates do not count.
datasetIddataset IDnoneA dataset to read the addresses from, such as a scraper's results. See below.
emailFieldtextemailThe field of each dataset row that holds the address.

Run it after a scraper

Ran a lead scraper? Pass its results instead of pasting a list.

  1. Open the scraper's page in the Console, select the Integrations tab and add this Actor, to run when the scraper's run succeeds.
  2. In this Actor's input there, put this line in the Dataset field (datasetId): {{resource.defaultDatasetId}}
  3. In Email field (emailField), name the field of the scraper's rows that holds the address. The default is email.

Each run then reads up to 100,000 rows of that dataset. A field that holds a list of addresses gives one address per item. A row with no address in that field is skipped and not charged, and the dataset part of RUN_SUMMARY counts these rows. Addresses pasted in emails are checked too, except the three sample ones of the form. Duplicates are removed and maxEmails caps the total, as for a pasted list. For a single run you can also put the ID of a dataset you own in datasetId. The Actor gets read access to that one dataset only.

Output

One dataset row per unique address, in input order. Every row has every field.

FieldMeaning
emailThe address as read from the input, after the cleanup in step 1.
normalizedLower case, domain in ASCII (punycode). Null when the syntax is invalid.
domainThe domain in lower case ASCII. Null when the syntax is invalid.
statusSee the table below.
reasonsList of short codes, in the order found. See the table below.
mxHostThe mail server that would receive mail (the first one by priority that resolves). The domain itself when it has no MX record but an address record. Null otherwise.
isDisposableThe domain, or a parent of it, is on the disposable list.
isRoleThe part before the @ (without a +tag) is a role name such as info, support or sales.
isFreeProviderThe domain is a free mailbox provider such as gmail.com. Information only: it does not make a row risky.
didYouMeanThe full address with the domain a known typo most likely stands for (john@gmial.com gives john@gmail.com), or null.
smtpResult, smtpDetailOutcome of the mailbox probe and the server's reply, or null when the probe is off or not tried for the row.

Status

statusMeaningCharged
validSyntax fine, the domain accepts mail, nothing risky found.yes
invalidBad syntax; the domain does not exist or accepts no mail; or (probe on) the mail server refused the mailbox.yes
riskyDeliverable in principle, but the domain is disposable, the address is a role account, the domain looks like a typo of a big provider, the local part is not plain ASCII, or (probe on) the server accepts every address.yes
unknownDNS did not answer in time, so no verdict is given. A temporary DNS failure is never reported as invalid.no

Reasons

CodeMeaning
bad_syntaxThe address is malformed; the next code says how: empty, missing_at, multiple_at, empty_local_part, empty_domain, bad_local_part, quoted_local_part, local_part_too_long, address_too_long, bad_domain, domain_without_dot, bad_tld, ip_literal_domain.
domain_not_foundThe domain does not exist (DNS answered "no such domain").
domain_accepts_no_mailThe domain has a null MX, or no MX and no address record at all.
mail_host_has_no_addressThe domain names mail hosts but none of the first five has an address.
dns_temporary_failureDNS did not answer after the retries (status unknown).
mx_foundA mail host for the domain was found.
no_mx_uses_address_recordNo MX record, but the domain has an address record, so mail would go there.
disposable_domainOn the disposable list.
role_accountA role name such as info or admin.
possible_typoThe domain is on the typo list; see didYouMean.
non_ascii_local_partThe part before the @ has non-ASCII characters, which many mail servers refuse.
check_timeoutOne address took longer than 12 times timeoutSeconds (status unknown).
smtp_mailbox_acceptedProbe on: the server accepted this address and refused a random one on the same domain.
smtp_mailbox_rejectedProbe on: the server said this mailbox does not exist (status invalid).
smtp_catch_allProbe on: the server accepts any address on the domain (status risky).
smtp_blockedProbe on: the server refused for a reason about us (blocklist, policy), not about the mailbox. The status is not changed.
smtp_temporaryProbe on: a temporary reply such as greylisting. The status is not changed.
smtp_unreachableProbe on: no connection could be made. The status is not changed.
smtp_protocol_errorProbe on: the server sent something unexpected or hung up. The status is not changed.

Example row (shape only, probe off; the values are made up):

{"email": "john@gmial.com", "normalized": "john@gmial.com", "domain": "gmial.com", "status": "risky",
"reasons": ["mx_found", "possible_typo"], "mxHost": "mx1.gmial.com", "isDisposable": false, "isRole": false,
"isFreeProvider": false, "didYouMean": "john@gmail.com", "smtpResult": null, "smtpDetail": null}

The mailbox probe (checkSmtp, off by default)

When on, for each address that passed the DNS check (and is not on a disposable domain) the Actor connects to its mail host on port 25, says hello, names an empty sender, asks whether it would accept mail for the address, then asks the same about one random address on the same domain, and says goodbye. No message is ever sent (no DATA command is issued). At most two connections are open to one mail host at a time.

What to expect:

  • Outbound port 25 is blocked on many platforms and networks, and many mail servers refuse or delay probes from shared addresses. If 5 connections fail and none has succeeded, probing is switched off for the rest of the run, the rows say smtp_unreachable, and the run summary says so. The DNS result of each row is kept.
  • A server may answer in ways that do not tell you about the mailbox. Those replies are recorded (smtp_blocked, smtp_temporary) and leave the status alone.
  • A mailbox probe is not a delivery guarantee: servers can accept an address and bounce later.

This behavior is tested against a scripted mail server in memory. It has not been tried against real mail servers from the Apify platform, so treat the first probe run as a test with a short list.

Pricing

Pay per event. One event, charged once for each row with status valid, invalid or risky:

EventPriceWhen
email-verified0.0006 USD (0.60 USD per 1,000 addresses), the same on every planper dataset row with status valid, invalid or risky

Rows with status unknown are delivered and never charged. Duplicates are removed before checking and are not charged. Apify also applies its standard run start event once per run, shown on the pricing tab of this Actor. Set a maximum cost per run in the Console and the Actor stops with the results it has when that limit is reached.

Reliability

  • DNS lookups use the name servers of the container the Actor runs in. A lookup is tried twice over every configured server, with a pause between the tries; a truncated UDP answer is asked again over TCP; answers that do not match the question are ignored.
  • Each address has a deadline of 12 times timeoutSeconds; past it the row is unknown and is not charged.
  • Up to 50 addresses are checked at once. A domain shared by many addresses is looked up once.
  • DNS stop: if 20 addresses in a row end as unknown because DNS does not answer, the run stops with a clear message instead of spending compute on a dead network. The rest are not checked and not charged.
  • Each run writes a RUN_SUMMARY record to the key-value store: results by status, charged results, reason counts, DNS queries and retries, list sizes, timing and why a run stopped.

Privacy

The Actor contacts no website and no third-party service: it sends DNS queries about the domain part of each address to the container's name servers, and, only with checkSmtp on, talks to the mail host of the address. Addresses are written to your run's dataset (the platform also keeps your input with the run), never to the run log, and the Actor keeps nothing between runs. Make sure you have a lawful basis to process the addresses you submit.

Support

If a verdict looks wrong, open an issue on the Actor page with the address's domain (not the whole list) and the reasons the row shows. Edge