• Blackhat Pakistan — Ethical Hacking, Hacking Tools & Cybersecurity Tutorials

How to Make an Email Checker 2026

Blackhatpakistan

Administrator
Staff member
Joined
Dec 30, 2024
Messages
435
Reaction score
219
Points
62
Website
blackhatpakistan.net
Points
1,378
USD
1,378
5qzs8b.png


An email checker answers one question at scale: does this address actually receive mail — and the honest answer has five verification levels, each more expensive than the last, each lying less. Syntax validation catches malformed strings in microseconds with zero network cost. DNS/MX lookup confirms the domain publishes mail-exchanger records. SMTP handshake probing — connecting to the domain's mail server and walking the RCPT TO dialogue without sending mail — tells you whether the server claims the mailbox exists. Full verification is the expensive truth-tier most mass tools skip. This is email-checker making decoded: the five-level pipeline, the catch-all and disposable-domain problems that break naive checkers, the mass architecture that keeps SMTP servers from blacklisting the checker, and the output taxonomy that downstream pipelines actually consume. Post your checker log — cleanest accuracy story gets pinned to Carding-method, first fifteen get the provider-pacing map and the catch-all detection protocol via PM.

TL;DR - Email validation runs five levels in strict order: (1) syntax — RFC-shaped checks, typo-domain detection (gnail/hotmal classes with probable-intended recorded), kills malformed lines at zero cost; (2) domain resolution — NXDOMAIN kills the line before mail-server contact; (3) MX lookup — null-MX (MX 0 .) is explicit refuse, no-MX-with-A falls back to implicit-A mail per RFC; (4) SMTP RCPT probe — TCP connect to highest-priority MX, EHLO, MAIL FROM, RCPT TO, read response: 250 = server claims mailbox (qualified by catch-all status), 550-class = dead with extended code preserved, 4xx = temporary defer retry-class, 554 = policy-block on the checker's sender not the address; (5) full-verify — actual delivery, expensive, skipped by mass pipelines. The two structural problems: catch-all servers accept every RCPT (random improbable-address probe per domain flags it — 250 on a UUID local-part means reclassify the whole domain's passes as catchall-qualified), and disposable-domain tracking needs its own class not dead (addresses work by design, briefly). Mass architecture: async DNS layers parallel, per-domain serialized SMTP with connection reuse (MAIL FROM once, multiple RCPT, QUIT), multi-egress, greylist-aware retry windows, output classes: verified/catchall/dead/typo/disposable/policy/defer/unknown.

51nlbk.png


THE FIVE VERIFICATION LEVELS

- Syntax layer: RFC-shaped validation — local-part rules (dot-atom vs quoted-string forms, no leading/trailing/double dots in dot-atom, label length caps, total length caps), domain-label rules (hyphen-position constraints, TLD presence, no all-numeric TLD), case-folding the domain while preserving local-part case; kills malformed lines before they earn DNS or SMTP cost, and the catch-rate on typo-heavy subscriber lists runs high enough to pay for the whole checker's existence.
- Domain and MX layers: A/AAAA resolution (NXDOMAIN class), MX record lookup (priority-ordered list feeding the SMTP layer's connection queue), MX-null detection (MX 0 . = explicit refuse, distinct dead-class), implicit-A fallback flagging (no-MX with resolvable A — mail assumed at the domain itself per RFC, probed with adjusted expectations); the MX list's priority order is the connection order, failover down the list on connect failure rather than classify the address dead.
- Provider-aware shortcuts: known-provider domains route to provider-aware handling — some aggressively rate-limit RCPT probing (the pacing layer respects it), some implement catch-all-by-default at the edge; the provider map, built from observed behavior not documentation, lets the checker pace and classify per-domain-class instead of one global policy that misfits both ends.
- Typo-domain detection: addresses whose domain almost-matches a known major provider with a one-character deviation classify as typo-dead with the probable-intended-domain recorded for downstream auto-correction; this layer alone justifies email checkers on list-hygiene deployments where typo domains run 1-3% of typical inbound lists.

438ama.png


CODE: SYNTAX + TYPO-DOMAIN DETECTOR

Cheap layers first: RFC-shaped validation plus typo-domain matching against the known-provider map. The typo layer records the probable-intended-domain so downstream consumers can offer correction instead of just dropping the line.

Code:
# level 1-2: syntax validator + typo-domain detector

import re, dns.resolver

KNOWN_PROVIDERS = {

    "gmail.com": ["googlemail.com"], "yahoo.com": ["ymail.com", "rocketmail.com"],

    "hotmail.com": ["outlook.com", "live.com", "msn.com"], "aol.com": [],

    "protonmail.com": ["proton.me", "pm.me"], "icloud.com": ["me.com", "mac.com"],

}

def syntax_check(addr):

    # dot-atom local-part, no leading/trailing/double dots

    pat = r"^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+"

    pat += r"(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*"

    pat += r"@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$"

    if not re.match(pat, addr):

        return False, "SYNTAX_FAIL"

    local, domain = addr.rsplit("@", 1)

    if len(local) > 64 or len(addr) > 254:

        return False, "LENGTH_FAIL"

    if domain.startswith(".") or ".." in domain:

        return False, "DOMAIN_SHAPE_FAIL"

    return True, "OK"

def typo_detect(addr):

    domain = addr.split("@")[1].lower()

    if domain in KNOWN_PROVIDERS:

        return None  # exact match

    for provider, aliases in KNOWN_PROVIDERS.items():

        candidates = [provider] + aliases

        for cand in candidates:

            # single-char deviation check (len >= 5 to avoid noise)

            if abs(len(cand) - len(domain)) <= 1 and len(cand) >= 5:

                diffs = sum(1 for a, b in zip(cand, domain) if a != b)

                diffs += abs(len(cand) - len(domain))

                if diffs == 1:

                    return cand  # probable intended domain

    return None

def mx_lookup(domain):

    try:

        mx = dns.resolver.resolve(domain, "MX")

        records = sorted([(r.preference, str(r.exchange).rstrip(".")) for r in mx])

        if records and records[0][1] == "" or (records and records[0][0] == 0 and records[0][1] == "."):

            return "MX_NULL"  # explicit refuse

        return records

    except dns.resolver.NXDOMAIN:

        return "NXDOMAIN"

    except dns.resolver.NoAnswer:

        # no MX — check A fallback

        try:

            dns.resolver.resolve(domain, "A")

            return "IMPLICIT_A"

        except Exception:

            return "NO_MAIL"

# demo

for a in ["user@gmail.com", "userr@gmail.com", "bad@@x.com", "info@gnail.com"]:

    ok, why = syntax_check(a)

    typo = typo_detect(a) if ok else None

    print(a, "->", why, "| typo:", typo)

THE SMTP PROBE, HONESTLY

- Connection discipline: TCP connect to highest-priority MX with tight timeout budget, SMTP handshake (EHLO with properly-formed checker domain, honoring advertised capabilities), MAIL FROM (a checker-domain envelope sender — unknown senders get greylisted on first contact, which the retry-class handles), then RCPT TO with the candidate address, then QUIT. No mail is ever sent.
- Response-code taxonomy: 250-class = server claims mailbox, qualified by catch-all status; 550 5.1.1 user-unknown = dead with extended status preserved; 4xx families (421/450/451/452) = temporary defer, retry-class with backoff — never dead on 4xx, ever; 554 rejections = policy-class, the checker's sending identity blocked rather than the address dead — the distinction matters because policy-rejections apply to the sender, not the address.
- Greylisting handling: many servers 4xx-reject first contact from unknown senders and accept on retry from same IP+sender within minutes-to-hours — the checker's retry queue backs off and re-probes; first-pass stats on greylist-heavy domains always undercount valids, which is why the run report separates first-pass and post-retry tallies.
- QUIT discipline and connection reuse: clean SMTP termination per probe; connection reuse where the server allows multiple RCPT per session (MAIL FROM once, multiple RCPT TO, QUIT — massive throughput on cooperative servers); per-connection RCPT caps to avoid tarpitting — hammering one server with hundreds of RCPT on one session triggers throttling and blacklisting.

boj9x0.png


CODE: SMTP RCPT PROBE + CATCH-ALL DETECTION

The probe walks the RCPT dialogue and classifies by response code; the catch-all probe fires a random improbable address per domain — if the server accepts that too, the whole domain flags catch-all and every subsequent RCPT-pass at that domain reclassifies as catchall-qualified rather than verified.

Code:
# level 4: SMTP RCPT probe + catch-all detection

import smtplib, socket, uuid

SMTP_TIMEOUT = 12

RCPT_PER_CONN_CAP = 80  # tarpit avoidance

def smtp_rcpt_probe(mx_host, mail_from, address):

    try:

        with smtplib.SMTP(mx_host, 25, timeout=SMTP_TIMEOUT) as s:

            s.ehlo("checker.example.com")

            s.mail(mail_from)

            code, resp = s.rcpt(address)

            s.quit()

            if 200 <= code < 300:

                return "CLAIM_250"       # server accepts mailbox

            if 500 <= code < 600:

                if b"5.1.1" in resp or code == 550:

                    return "DEAD_550"

                if code == 554:

                    return "POLICY_554"  # sender-reputation block, not address

                return "DEAD_5xx"

            if 400 <= code < 500:

                return "DEFER_4xx"       # greylist / temp — retry class

            return "UNKNOWN"

    except smtplib.SMTPServerDisconnected:

        return "DEFER_CONN"   # connection dropped mid-dialogue — retry

    except (socket.timeout, ConnectionRefusedError, OSError):

        return "UNKNOWN_NET"  # tool-side failure, never dead

def catchall_probe(mx_host, mail_from, domain):

    """Fire a UUID local-part — if accepted, domain is catch-all."""

    probe = f"{uuid.uuid4().hex}@{domain}"

    result = smtp_rcpt_probe(mx_host, mail_from, probe)

    return result == "CLAIM_250"

# usage: per domain — if catchall, reclassify all CLAIM_250 as catchall-qualified

mx = "mail.example.com"

print("normal:", smtp_rcpt_probe(mx, "check@checker.example.com", "user@example.com"))

print("catchall:", catchall_probe(mx, "check@checker.example.com", "example.com"))

DETECTION LAYER

What runs against a mass email checker: SMTP-server-side rate limiting (per-IP RCPT count thresholds before tarpitting), greylisting windows (first-contact 4xx with retry-accept patterns), IP-reputation scoring on the checker's egress (datacenter exits hit policy-554 and greylist harder than residential on major providers), provider-specific anti-probing behavior (major free-mail providers aggressively rate-limit RCPT enumeration), and domain-level catch-all configurations that silently inflate valid counts on naive checkers. The detection surface is the SMTP server's own logging — every RCPT probe is a logged event against the checker's sending identity and egress IP, which is why multi-egress distribution and sender-domain reputation hygiene are not optional at volume.

zd7nlx.png


OPSEC CHECKLIST

- Multi-egress from the start: one domain's throttle should not serialize the whole batch behind one exit — distribute SMTP-layer connections across multiple source IPs, and rotate sender-identity domains when policy-554 blocks accumulate at specific providers.
- Catch-all detection per domain, not per address: a random UUID local-part probe per domain is one extra RCPT per domain — the cheapest honesty insurance in the whole checker; without it, corporate-heavy lists inflate valid-counts by 20%+ and downstream consumers trust the inflation.
- Never dead-class on 4xx: greylisting windows run minutes to hours — the retry queue with domain-aware backoff recovers the majority of 4xx-class; first-pass undercounts on greylist-heavy domains are the server's design, not the checker's bug.
- Policy-class is not dead: 554 rejections fingerprint as sender-reputation or IP-reputation blocks — park for sender/egress rotation, never dump real addresses into the dead file on a policy signal.
- Per-connection RCPT caps: tarpitting and blacklisting follow RCPT-per-connection abuse — the cap belongs in the runner's connection layer, not in a human's after-the-fact judgment call.
- Run-report honesty: first-pass vs post-retry tallies, catch-all-domain count, policy-blocked domains — the numbers that let a consumer know exactly what "verified" means on this run (RCPT-level truth on non-catch-all domains, not delivery-level truth).

TEST YOURSELF: SIX QUESTIONS

Score honestly: (1) name the five verification levels in order and which one mass pipelines skip. (2) what does a random UUID local-part probe at a domain tell you, and what happens to that domain's RCPT-passes when it returns 250? (3) why is 554 different from 550, and which one should park for egress rotation instead of joining dead.txt? (4) a domain returns 421 on first RCPT probe — dead, defer, or policy, and what does the retry queue do with it? (5) how does connection reuse (MAIL FROM once, multiple RCPT, QUIT) differ from hammering one server with hundreds of RCPT on one session, and what does the second pattern trigger? (6) your corporate-heavy list shows 14,688 "valid" from a naive checker but 11,844 verified from your honest one — what is the 24% gap made of, and which detection catches it? Zero to one: you read the TL;DR. Two to three: re-run your last checker's output through catch-all detection and compare the inflation. Four plus: post your run report with first-pass vs post-retry tallies.

e4aj2x.png


POST YOUR LOG

Scoreboard format: list size, provider-segment breakdown, first-pass verified / post-retry recovered, catch-all domains flagged, dead count with extended codes, policy-blocked domains, disposable matches, wall time, egress count. Field-log framing, own lists only. Cleanest accuracy story gets pinned to Carding-method, first fifteen posters get the provider-pacing map and the catch-all detection protocol via PM — per-provider RCPT thresholds, greylist-window timing by provider class, and the disposable-domain list update method.

FAQ

- Q: What does an email checker actually verify?

A: Five levels, each more expensive: syntax (RFC-shaped), domain resolution, MX publication, SMTP RCPT-probe (server claims mailbox — no mail sent), and full delivery (rare, expensive). Mass tools run through RCPT-probe level, and "verified" honestly means the server accepted the address at RCPT, qualified by whether the domain is catch-all — delivery-level truth requires the fifth tier most pipelines skip.
- Q: What is a catch-all domain and why does it break checkers?

A: A domain accepting RCPT for every address (accept-all configuration) — the probe gets 250 on dead addresses, inflating valid-counts. The fix: random improbable-address probe per domain — if the server accepts a UUID local-part too, flag the domain catch-all and reclassify all its RCPT-passes as catchall-qualified (server-accepted but unverifiable), which keeps the output honest instead of 20%+ inflated on corporate-heavy lists.
- Q: Why do some valid addresses come back as deferred?

A: Greylisting — many servers 4xx-reject first contact from unknown senders and accept on retry from the same IP+sender within a window. The checker's retry queue with domain-aware backoff re-probes after the window, which recovers the majority of 4xx-class on greylist-heavy domains — which is why run reports separate first-pass and post-retry tallies.
- Q: What is the difference between dead and policy-blocked?

A: Dead (550 user-unknown) is the address — the server says this mailbox does not exist. Policy-blocked (554-class) is usually the checker's sending identity or egress — the server refuses the dialogue on sender-reputation or IP-reputation grounds regardless of the address's validity. Conflating the two dumps real addresses into dead-file and hides an egress problem behind address verdicts.
- Q: How does mass checking avoid getting SMTP-blacklisted?

A: Per-domain serialization (concurrent across domains, paced within each), connection reuse with RCPT caps, multi-egress distribution, greylist-aware retry timing rather than hammering 4xx servers, and provider-map pacing — the throttle discipline is what separates a sustainable checker from one that burns its egress reputation in the first hour.
- Q: Should disposable addresses classify as dead?

A: No — they receive mail by design (briefly); classify disposable-class with the domain recorded, and let the downstream consumer apply policy. The disposable domain-list is a living artifact (services rotate domains constantly), so unknown-new disposables slip through until the list updates, with domain-age and MX-pattern heuristics catching some newcomers in the meantime.

RELATED

SMTP Cracking 2026 - JEX Bot, Hydra & Checkers

How CC Checkers Actually Work - Response Codes, CPM and Accuracy

OpenBullet Config Making 2026 - Complete Guide to Creating Configs

Proxies for Carding 2026 - Complete Proxy Guide
 
Threads
1,152Threads
Messages
2,308Messages
Members
3,771Members
Latest member
BlotmotLatest member
Top