To verify emails in Python, check three things in order: syntax with a regex or the email-validator library, the domain's MX records with dnspython, and mailbox existence with an SMTP handshake using the smtplib module. Syntax and MX checks are reliable. SMTP checks are fragile and often blocked.
What does it mean to verify an email in Python?
Verifying an email in Python means confirming an address can receive mail without sending a test message. It runs in layers: format validation, domain and MX-record lookup, then an optional SMTP mailbox probe. Each layer filters more junk. The deeper you go, the less reliable and more rate-limited the checks become.
Think of it as a funnel. Bad syntax gets caught for free, in memory, with no network call at all. A domain with no MX record cannot accept mail, so those addresses die at a single DNS lookup. The final SMTP step asks the receiving server whether one specific mailbox exists. That last answer is the one servers lie about most, which is why layering matters.
How do you check email syntax and MX records in Python?
Use the email-validator library for syntax and normalization, then dnspython to resolve MX records. Install both with pip install email-validator dnspython. Syntax validation catches typos and illegal characters. The MX lookup confirms the domain actually runs a mail server. Together they remove most invalid addresses before you touch SMTP.
- Validate format: from email_validator import validate_email, then call validate_email(address) and catch EmailNotValidError. It normalizes the address and rejects bad syntax.
- Extract the domain, which is everything after the @ sign in the normalized address.
- Resolve MX records: import dns.resolver, then run dns.resolver.resolve(domain, 'MX'). No records means the domain cannot receive mail, so mark it invalid.
- Sort MX hosts by preference. The lowest preference value is the primary mail server you would talk to next.
- Cache the MX result per domain so repeated addresses on the same domain do not trigger repeated lookups.
One detail saves you real time: deduplicate and group by domain before any lookup. A list of 5,000 addresses might cover only 300 domains. Resolve MX once per domain, cache it, and reuse it. You cut network calls by an order of magnitude and stay well under any resolver rate limits.
Can you verify a mailbox with SMTP in Python?
Yes, with smtplib you can open a connection to the MX host, send HELO, MAIL FROM, and RCPT TO, then read the response code. A 250 suggests the mailbox exists. A 550 suggests it does not. But many servers accept everything or reject probes outright, so the signal is noisy.
A minimal probe looks like this: connect with smtplib.SMTP(mx_host, 25), call server.helo(), then server.mail('[email protected]') and server.rcpt('[email protected]'). The tuple returned by rcpt holds the status code. You never send DATA, so no email is delivered. Set a timeout of 10 seconds or the socket can hang on unresponsive hosts.
Run this at scale and problems appear fast. You need a real sending domain, a static IP with decent reputation, and port 25 open outbound. Most residential and cloud provider networks block port 25 by default. Gmail and Outlook throttle or greylist unknown probers within a few requests, and repeated RCPT TO probes from a fresh IP can get that IP flagged. The check meant to protect your list can quietly damage your sender reputation.
Check your list right now, free
10 checks a day with no signup. 100 a day with just your email.
Why does DIY SMTP verification break in production?
DIY SMTP breaks because mail servers are built to resist exactly this behavior. Catch-all domains accept every address. Greylisting delays first-time senders. IP reputation degrades when you probe in volume, which can hurt your real deliverability. You end up with Unknown results and a warmed-up spam signature you did not want.
| Check layer | Reliability | Main limitation |
|---|---|---|
| Syntax (regex / email-validator) | High | Catches format only, not real mailboxes |
| MX record (dnspython) | High | Confirms the domain accepts mail, not the address |
| SMTP RCPT TO (smtplib) | Low to medium | Blocked, throttled, or faked by many servers |
| Catch-all detection | Hard in DIY | Server accepts everything, so no clear answer |
| Disposable domain check | Needs a maintained list | New throwaway domains appear daily |
None of this means Python is useless here. It means SMTP is the wrong layer to lean on for bulk cleaning. Use it for insight, not as the deciding vote on whether an address is safe to email.
When should you skip the script and use the free tool?
Write your own Python when you are verifying a handful of addresses inside an app, or learning how SMTP works. Use a hosted verifier when you have a real list to clean, need catch-all and disposable detection, or want stable verdicts without risking your sending IP. For most list cleaning, the script is not worth the maintenance.
A practical hybrid works well. Use email-validator and dnspython in your signup form to reject obvious junk at the point of entry, cheaply and instantly. Then batch-clean your existing lists with a tool that handles the SMTP and catch-all logic for you. That keeps live checks fast and offloads the fragile part.
The Free Email Verifier keeps your CSV in the browser, so the file is parsed locally and never uploaded, which matters when the list is customer data. A local safety scan flags bad syntax, duplicates, and disposable domains without using quota, then the remaining addresses get MX and SMTP-level checks and clear verdicts.
Start with the two reliable layers, syntax and MX, in Python. Treat SMTP results as hints, not verdicts. When a list actually matters, hand the noisy work to a checker built for it and keep your sending reputation clean. Synthisia, which builds this tool, runs the same checks before every outbound campaign for the same reason.