E-mailadressen verifiëren in Python

Een e-mailadres verifiëren in Python is eenvoudig voor de makkelijke delen en verrassend lastig voor het deel dat het meest telt. Deze gids loopt door syntaxchecks en DNS-lookups die je lokaal kunt uitvoeren, legt precies uit waarom ruwe SMTP-probes in de praktijk mislukken en laat zien hoe je met een REST API een resultaat op mailboxniveau krijgt.

In het kort

Gebruik Python om onjuist opgemaakte invoer te weigeren en eventueel DNS/MX-checks te cachen. Gebruik een verificatie-API voor het resultaat op mailboxniveau, want ruwe smtplib probes worden makkelijk afgeknepen en geven vaak dubbelzinnige antwoorden.

Wat het "verifiëren" van een e-mail echt betekent

E-mailverificatie is het proces waarmee je bevestigt dat een adres echt bestaat en mail kan ontvangen voordat je er iets naartoe stuurt. Het bestaat uit een reeks checks, elk moeilijker dan de vorige: is het adres correct opgemaakt, accepteert het domein überhaupt mail, en bestaat de specifieke mailbox. In Python zijn de eerste twee goed te doen. Bij de derde gaan de meeste zelfgebouwde oplossingen ongemerkt onderuit, en begrijpen waarom is het nuttigste in dit artikel.

Het plan hieronder is gelaagd. Voer de goedkope, snelle checks lokaal uit om duidelijk kapotte invoer eruit te gooien, en roep daarna een verificatie-API aan voor het antwoord op mailboxniveau over of het adres bruikbaar is. Zo weiger je onzin direct zonder API-call, en krijg je een duidelijker resultaat voor alles wat overblijft.

Gelaagde e-mailverificatiecontroles in Python met syntaxisvalidatie, DNS-MX-lookup, API-verificatie en applicatiebeleid
Een betrouwbare Python-implementatie houdt lokale checks snel en goedkoop, en vraagt daarna een beheerde verificatie-API om de oordelen over SMTP, catch-all, wegwerp- en rolgebaseerde adressen.

Stap 1: syntaxvalidatie

De eerste check bevestigt dat het adres correct is opgebouwd. Het is verleidelijk om hiervoor een reguliere expressie te schrijven, maar e-mailsyntax is veel ingewikkelder dan hij lijkt, en zelfgeschreven regexes zijn bijna altijd te streng of te los. De standaardbibliotheek hiervoor is email-validator, die de relevante standaarden correct implementeert en veel in productie wordt gebruikt.

Installeer hem met pip:

pip install email-validator

Valideer daarna een adres. De bibliotheek gooit EmailNotValidError bij alles wat onjuist is opgemaakt en geeft voor geldige invoer een genormaliseerd object terug:

from email_validator import validate_email, EmailNotValidError

def check_syntax(address):
    try:
        result = validate_email(address, check_deliverability=False)
        return True, result.normalized
    except EmailNotValidError as error:
        return False, str(error)

ok, value = check_syntax("jane.doe@example.com")
print(ok, value)  # True jane.doe@example.com

ok, value = check_syntax("jane.doe@@example")
print(ok, value)  # False, with a description of the problem

Door check_deliverability=False in te stellen blijft deze stap pure syntax zonder netwerkcalls, dus snel genoeg om bij elke toetsaanslag of formulierverzending uit te voeren. Sla altijd de normalized waarde op die de bibliotheek teruggeeft in plaats van de ruwe invoer van de gebruiker, omdat die het adres canoniek maakt voor consistente opslag.

Stap 2: domein- en MX-checks

Een correct opgemaakt adres is nog steeds nutteloos als het domein geen e-mail kan ontvangen. De volgende check vraagt de DNS-records van het domein op en zoekt naar MX-records, die de mailservers aanwijzen die voor dat domein verantwoordelijk zijn. Een domein zonder MX-records kan geen mail ontvangen, dus elk adres op dat domein kun je direct weigeren.

Het email-validator bibliotheek kan dit voor je doen als je check_deliverability=True meegeeft, waarmee de MX-lookup onderdeel wordt van de validatie:

from email_validator import validate_email, EmailNotValidError

def check_domain(address):
    try:
        result = validate_email(address, check_deliverability=True)
        return True, result.domain
    except EmailNotValidError as error:
        return False, str(error)

Wil je de DNS-query zelf uitvoeren, bijvoorbeeld om resultaten te cachen voor veel adressen op hetzelfde domein, gebruik dan direct de dnspython bibliotheek:

pip install dnspython
import dns.exception
import dns.resolver

def has_mx_record(domain):
    try:
        records = dns.resolver.resolve(domain, "MX")
        return len(records) > 0
    except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
        return False
    except dns.exception.DNSException:
        return False  # Treat resolver errors as inconclusive.

print(has_mx_record("gmail.com"))  # True
print(has_mx_record("no-such-domain-xyz.invalid"))  # False

Dit is echt nuttig. Het verwijdert adressen op domeinen die verlopen zijn, nooit hebben bestaan of simpelweg geen mail verwerken. Maar let op wat het je nog steeds niet vertelt: of someone@gmail.com een echte mailbox is. Gmail heeft MX-records, dus elk Gmail-adres komt door deze check, geldig of niet.

Stap 3: de mailboxcheck, en waarom ruwe SMTP tekortschiet

Om de mailbox zelf te bevestigen, moet je met de ontvangende mailserver praten. In principe maak je verbinding via SMTP, identificeer je jezelf, geef je een RCPT TO commando met het doeladres en lees je de responscode: een positieve code suggereert dat de mailbox bestaat, een weigering dat hij niet bestaat. Python's ingebouwde smtplib kan dit, en de code ziet er bedrieglijk eenvoudig uit:

import smtplib
import dns.resolver

def naive_smtp_check(address):
    domain = address.split("@")[1]
    mx_host = str(dns.resolver.resolve(domain, "MX")[0].exchange)

    server = smtplib.SMTP(timeout=10)
    server.connect(mx_host)
    server.helo("example.com")
    server.mail("probe@example.com")
    code, message = server.rcpt(address)
    server.quit()

    # 250 and 251 generally mean the mailbox was accepted.
    return code in (250, 251)

Dit werkt in een tutorial en faalt in productie. De redenen zijn de moeite waard om in detail te kennen, omdat ze verklaren waarom geen enkele serieuze verifier op zo'n script vertrouwt.

IP-reputatie en throttling

Mailboxproviders herkennen verificatieprobes actief. Voer een handvol RCPT TO checks uit vanaf één server en providers zoals Gmail en Outlook knijpen dat IP binnen een paar pogingen af of blokkeren het. Eenmaal afgeknepen geeft de server dubbelzinnige antwoorden, of de mailbox nu bestaat of niet, waardoor je resultaten betekenisloos worden. Precies daarom gebeurt echte verificatie vanaf grote pools van goed opgewarmde, goed beheerde IP-adressen.

Greylisting

Veel servers gebruiken greylisting en geven bewust een tijdelijke 4xx fout terug de eerste keer dat een onbekende afzender contact maakt. Legitieme mailservers proberen het later opnieuw; spammers meestal niet. Een naïef script dat niet opnieuw probeert, leest die tijdelijke fout als weigering en markeert een prima mailbox als ongeldig. Dat is een fout-negatief dat puur door ongeduld ontstaat.

Catch-all-domeinen

Een catch-all-domein accepteert mail voor elk mogelijk adres, dus RCPT TO geeft een positieve code voor realperson@domain.com en de random-string-9f3a@domain.com zonder onderscheid. Een ruwe SMTP-check kan op deze domeinen een echte mailbox niet onderscheiden van een niet-bestaande. Om een catch-all te detecteren, stuur je een tweede probe naar een adres dat zeker niet bestaat en kijk je of ook dat wordt geaccepteerd. Dat is catch-all-detectie, geen simpel ja of nee.

Dubbelzinnige antwoorden van grote providers

Sommige grote providers hebben hun RCPT TO gedrag bewust dubbelzinnig gemaakt. Bepaalde providers accepteren elk adres tijdens de handshake en bouncen pas later, dus de SMTP-response bevat simpelweg niet de informatie waar je om vraagt.

Daarom laten de beheerders van email-validator SMTP-checks bewust weg uit de bibliotheek. Hun documentatie zegt het ronduit: het heeft geen zin om zelf contact te leggen met een SMTP-server, omdat servers er goed in zijn niet te verraden of een adres afleverbaar is, en een adres dat mail lijkt te accepteren kan daarna alsnog bouncen. De eerlijke conclusie: syntax- en MX-checks kun je goed in je eigen code doen, een betrouwbare mailboxcheck niet.

Stap 4: verifiëren met de VeriMails API

De praktische oplossing is de mailboxcheck over te laten aan een verificatiedienst met infrastructuur die daarvoor gebouwd is, en die dienst vanuit Python aan te roepen met één HTTP-request. VeriMails biedt een REST API die de volledige pipeline uitvoert: syntax, MX, DNS, een live SMTP-handshake, catch-all-detectie, wegwerpdetectie en rolgebaseerde detectie, en één gestructureerd resultaat teruggeeft.

Met de standaard requests bibliotheek ziet een verificatiecall er zo uit. Voor details over het endpoint, zie de VeriMails API-documentatie:

import requests

def verify_email(address, api_key):
    response = requests.get(
        "https://api.verimails.com/v1/verify",
        params={"email": address},
        headers={"Authorization": f"Bearer {api_key}"},
        timeout=10,
    )
    response.raise_for_status()
    return response.json()

result = verify_email("jane.doe@example.com", api_key="YOUR_API_KEY")
print(result["status"])      # e.g. "deliverable", "undeliverable", "catch_all"
print(result["disposable"])  # True if a throwaway address
print(result["role_based"])  # True for addresses such as info@ or support@

Daarna kun je op het resultaat handelen. Een aanmeldformulier kan deliverable adressen accepteren, undeliverable en de disposable adressen weigeren en een eigen beleid bepalen voor catch_all:

def is_acceptable_for_signup(address, api_key):
    result = verify_email(address, api_key)

    if result["disposable"]:
        return False, "Disposable addresses are not allowed."
    if result["status"] == "undeliverable":
        return False, "That address does not appear to exist."

    return True, "Address verified."

Omdat elke call een duidelijk resultaat geeft, is het snel genoeg om binnen een formulierverzending uit te voeren zonder de gebruiker te laten wachten. Voor een bestaande lijst adressen die al in je database staat, past bulk-CSV-verificatie beter, omdat één bestand uploaden veel efficiënter is dan het endpoint voor losse adressen duizenden keren in een lus aanroepen.

Het aanbevolen patroon voor een Python-applicatie combineert beide lagen: houd de lokale email-validator syntaxcheck om onzin direct te weigeren voordat je een credit uitgeeft, en roep daarna de API aan voor het gezaghebbende deliverability-resultaat op alles wat erdoor komt. Zo krijg je snelle feedback op duidelijke fouten en een betrouwbaar oordeel over echte adressen.

Beleidstabel voor applicaties

ResultaatBeleid bij aanmeldingBeleid bij import of CRM
deliverableAccepteer en sla het genormaliseerde adres opHoud in het actieve segment
undeliverableWeiger met een duidelijke melding om het te corrigerenOnderdruk vóór elke campagne
catch_allAccepteer of beoordeel op basis van het accountrisicoSegmenteer apart en verstuur voorzichtig
disposableWeiger bij de meeste productenOnderdruk in outreach-lijsten
role_basedSta alleen toe als gedeelde inboxen acceptabel zijnBeoordeel vóór cold outreach

API-calls versus lijstjobs

Gebruik de API waar een directe beslissing telt: aanmelding, checkout, leadformulieren, uitnodigingen en accountwijzigingen. Gebruik bulkverificatie voor een CSV-export, CRM-opschoning of campagnepubliek waar duizenden adressen dezelfde beoordeling nodig hebben. Beide workflows moeten dezelfde suppressieregels voeden, zodat een adres dat in de ene route is geweigerd niet later vanuit een andere tool wordt gemaild.

Alles bij elkaar

Een nette implementatie heeft drie lagen. Ten eerste een email-validator syntaxcheck met check_deliverability=False, lokaal en direct uitgevoerd om onjuist opgemaakte invoer te weigeren. Ten tweede een optionele MX-check met dnspython als je dode domeinen wilt filteren voordat je de API aanroept, al dekt de API dit ook af. Ten derde een call naar de VeriMails API voor het resultaat op mailboxniveau, inclusief catch-all-, wegwerp- en rolgebaseerde detectie die je zelf niet betrouwbaar kunt nabootsen.

Onthoud vooral de taakverdeling. Syntax en DNS kun je echt zelf in Python afhandelen. De live mailboxcheck hangt af van IP-reputatie, greylisting-gedrag en catch-all-detectie die een enkel script niet aankan, en daarom laat de standaard validatiebibliotheek SMTP-probes bewust weg. Gebruik lokale checks voor snelheid, een API voor verificatie op mailboxniveau, en de prijspagina om het plan te kiezen dat bij je verwachte volume past.

Veelgestelde vragen

Je kunt de syntax controleren met de email-validator-bibliotheek en met dnspython bevestigen dat een domein MX-records heeft, allebei volledig in je eigen code. Wat je niet betrouwbaar alleen kunt, is bevestigen dat de mailbox bestaat. Daarvoor is een live SMTP-handshake vanaf vertrouwde IP-adressen nodig, en ruwe smtplib-probes vanaf één server worden meestal geblokkeerd of afgeknepen door mailboxproviders.
Grote providers herkennen verificatieprobes en knijpen het bron-IP binnen een paar pogingen af, waarna ze dubbelzinnige antwoorden geven, of de mailbox nu bestaat of niet. Greylisting geeft onbekende afzenders een tijdelijke fout, die een naïef script verkeerd leest als ongeldig. Catch-all-domeinen accepteren elk adres. Om deze redenen laat de officiële email-validator-bibliotheek SMTP bewust weg.
Gebruik een gelaagde aanpak: voer lokaal een snelle syntaxcheck uit om duidelijk kapotte invoer te weigeren, en roep daarna een verificatie-API zoals VeriMails aan voor het resultaat op mailboxniveau. De API voert de SMTP-handshake uit vanaf goed beheerde infrastructuur en geeft in één response de resultaten voor syntax, MX, DNS, SMTP, catch-all, wegwerp- en rolgebaseerde adressen terug.
De VeriMails API geeft voor elk adres een duidelijk resultaat, snel genoeg om aan te roepen binnen een formulierverzending. Nieuwe accounts krijgen 100 gratis credits zonder creditcard, zodat je de integratie kunt testen voordat je iets betaalt.

Begin met schone data

100 gratis credits bij aanmelding. Geen creditcard nodig. Breng het advies vandaag nog in de praktijk.

Gratis starten
Geen creditcard nodig. Credits verlopen nooit.