Skip to content
Inizia con 100 crediti di verifica gratuiti
Qualisend
Tutti gli articoli
Ingegneria / 11 giugno 2026

Come validare un indirizzo email in Django

7 min di lettura

Qualisend team
Come validare un indirizzo email in Django

Per validare un indirizzo email in Django devi eseguire tre controlli, non uno, e il framework fornisce solo il primo. I validatori di Django confermano che un indirizzo ha la forma corretta; non chiedono mai se il suo dominio può ricevere posta o se la casella esiste. La vera validazione è a livelli: sintassi, poi una ricerca DNS, poi un test SMTP della casella. Questa guida costruisce ogni livello come un validatore Django da inserire in un form o in un serializer di Django REST Framework, e mostra dove subentra un'API di verifica. Riprende da dove si ferma la guida per Python, quindi il ragionamento su DNS e SMTP spiegato lì resta valido così com'è.

In breve#

Usa validate_email (o un qualsiasi EmailField) per la sintassi, dnspython per la ricerca MX e un'API di verifica per il controllo SMTP della casella, collegati come tre validatori, partendo dal più economico e interrompendo la catena non appena uno è decisivo. Non aprire connessioni SMTP dal processo Django per testare le caselle: la porta 25 in uscita è bloccata dalla maggior parte degli host, e la risposta dipende dalla reputazione dell'IP di invio e dal greylisting, cose che non vuoi reimplementare. Ogni livello esclude indirizzi a un costo minore dell'ultimo; solo l'API può confermarne uno. Se la pipeline è nuova per te, cos'è la verifica email spiega prima i termini.

Livello 1: validazione del formato con i validatori di Django#

Django copre già il primo livello. django.core.validators.validate_email è un'istanza di EmailValidator che solleva ValidationError su un indirizzo malformato e restituisce None su uno corretto:

from django.core.validators import validate_email
from django.core.exceptions import ValidationError

def is_valid_syntax(email: str) -> bool:
    try:
        validate_email(email)
        return True
    except ValidationError:
        return False

Raramente lo chiami a mano. Ogni forms.EmailField, models.EmailField e serializers.EmailField di DRF aggiunge automaticamente EmailValidator, quindi dichiarare un campo ti dà gratis la validazione della sintassi:

from django import forms

class SignupForm(forms.Form):
    email = forms.EmailField()  # EmailValidator runs during clean()

Sappi esattamente cosa ottieni. EmailValidator controlla la forma rispetto a una grammatica derivata dalle RFC: non risolve mai il DNS, non apre mai un socket e non ha idea se example.com esista. definitely-fake@gmail.com passa. info@company-that-folded.com passa. typo@gmial.com passa. Sono tutti e tre non recapitabili, e nessun validatore che legge solo la stringa te lo dirà mai, per lo stesso motivo per cui la validazione delle email con le regex fallisce: sintassi e deliverability sono domande diverse. Una è un fatto che riguarda la stringa; l'altra è un fatto che riguarda internet.

Livello 2: il dominio accetta posta?#

È il primo livello che Django non ti fornisce, ed è economico da aggiungere. Un dominio senza record MX non può ricevere posta per nessuno, quindi una sola ricerca DNS elimina domini morti, nomi di aziende scritti male e TLD inventati. Django non ha un resolver MX integrato, quindi usa dnspython e racchiudilo in una semplice funzione:

import dns.resolver  # pip install dnspython

def has_mail_route(domain: str) -> bool:
    try:
        return len(dns.resolver.resolve(domain, "MX")) > 0
    except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.NoNameservers):
        return False

Un validatore Django è semplicemente un callable che solleva ValidationError su un input non valido, quindi trasforma il controllo in un validatore e potrai inserirlo direttamente nella lista validators di qualsiasi campo:

from django.core.exceptions import ValidationError

def validate_deliverable_domain(email: str) -> None:
    domain = email.rsplit("@", 1)[-1]
    if not has_mail_route(domain):
        raise ValidationError("This domain can't receive email.")
validate_deliverable_domain("jane@gmail.com")               # passes
validate_deliverable_domain("jane@company-that-folded.com") # raises ValidationError

Se vuoi accettare i domini che ricevono posta su un record A senza MX, ripiega sulla risoluzione di A/AAAA quando l'insieme MX è vuoto, ma un controllo MX copre la stragrande maggioranza degli indirizzi reali.

Livello 3: la casella esiste davvero?#

I primi due livelli possono solo escludere un indirizzo. Un dominio può pubblicare record MX perfetti e non avere comunque alcuna casella all'indirizzo che hai in mano: noreply-9f2x@gmail.com ha una sintassi valida su un instradamento di posta attivo, ma resta una casella mai creata. Confermare una casella specifica richiede la conversazione SMTP di consegna: collegarsi all'host di posta, inviare RCPT TO, leggere la risposta e disconnettersi prima di inviare qualsiasi cosa.

In teoria potresti automatizzarlo da Django con smtplib. In pratica non dovresti eseguirlo dal tuo server applicativo: la maggior parte degli host cloud blocca la porta 25 in uscita, la risposta dipende dalla reputazione dell'IP da cui ti colleghi, e i server riceventi applicano il greylisting e limitano le connessioni dei mittenti sconosciuti. Così un test che funziona in locale fallisce senza avvisi in produzione, o ti fa finire in blacklist. Come funziona la verifica email illustra l'intera pipeline, domini catch-all compresi. Questo è il livello che conviene delegare.

L'endpoint di verifica di Qualisend esegue l'intera pipeline (sintassi, DNS e test SMTP della casella) da un'infrastruttura con reputazione gestita e restituisce un esito. Chiamalo da un validatore con requests, leggendo la chiave dalle variabili d'ambiente:

import os
import requests  # pip install requests

QUALISEND_VERIFY_URL = "https://api.qualisend.com/v1/verify"

def verify(email: str) -> dict:
    res = requests.post(
        QUALISEND_VERIFY_URL,
        headers={"Authorization": f"Bearer {os.environ['QUALISEND_API_KEY']}"},
        json={"email": email},
        timeout=10,
    )
    res.raise_for_status()
    return res.json()["result"]  # {"status", "score", "reason", "sub_flags"}

La risposta è un involucro { "result": { ... } }. Leggi result["status"] (deliverable, risky, undeliverable o unknown) insieme a score, reason e sub_flags per caratteristiche come indirizzi generici o usa e getta. Consulta il riferimento API per la struttura esatta dei campi; qui ti serve solo lo stato per sollevare un errore su un indirizzo che non verrà recapitato:

def validate_mailbox(email: str) -> None:
    if verify(email)["status"] == "undeliverable":
        raise ValidationError("We couldn't confirm a mailbox at this address.")

Potresti sollevare un errore anche su risky, oppure salvare lo score e lasciar passare l'indirizzo: è una scelta di policy. Rifiutare undeliverable è l'impostazione predefinita sicura; la guida alla verifica serverless in fase di registrazione spiega quale livello di rigore adottare nel punto di raccolta.

Mettere insieme i livelli per validare un indirizzo email in Django#

Il punto idiomatico per comporre i tre livelli in un form è un metodo clean_<field>. Django chiama clean_email solo dopo che l'EmailValidator del campo è stato superato, quindi la sintassi è già gestita; aggiungi in ordine i controlli su dominio e casella, ciascuno pronto a sollevare subito l'errore:

from django import forms

class SignupForm(forms.Form):
    email = forms.EmailField()  # layer 1 for free

    def clean_email(self):
        email = self.cleaned_data["email"]  # syntax already passed
        validate_deliverable_domain(email)  # layer 2 — cheap, local
        validate_mailbox(email)             # layer 3 — the API round-trip
        return email

Tutto il trucco sta in quell'ordine: l'EmailValidator del campo blocca la spazzatura prima che il tuo codice venga eseguito, il controllo DNS locale esclude i domini morti a costo zero e l'API viene chiamata solo per gli indirizzi che hanno superato entrambi. Una validazione nell'ordine sbagliato, o che salta i livelli economici, spende un credito per ogni errore di battitura.

C'è una cosa da decidere in anticipo: cosa succede quando fallisce la chiamata all'API stessa. Un timeout di rete o un'eccezione di requests dentro clean_email non dovrebbe restituire a un cliente reale un errore di validazione che non può correggere. Intercetta l'errore della richiesta separatamente da ValidationError e tratta un servizio irraggiungibile come unknown anziché undeliverable: lascia passare la registrazione e riverifica l'indirizzo più tardi, invece di bloccare la registrazione per un'interruzione temporanea. I livelli di sintassi e MX sono già stati eseguiti in locale, quindi stai ammorbidendo solo il livello che dipende dalla rete.

Poiché entrambi i validatori personalizzati sollevano il ValidationError di Django, le stesse due funzioni si inseriscono senza modifiche in un serializer di Django REST Framework. L'hook validate_<field> di DRF intercetta quell'eccezione e la trasforma in un 400 pulito:

from rest_framework import serializers

class SignupSerializer(serializers.Serializer):
    email = serializers.EmailField()  # layer 1 for free

    def validate_email(self, value):
        validate_deliverable_domain(value)  # layer 2
        validate_mailbox(value)             # layer 3
        return value

È la stessa struttura a tre livelli che trovi nelle versioni di questa guida per Node.js e Python: è la struttura a livelli, non il framework, a far funzionare la validazione. Quando devi scegliere quale servizio di verifica usare per il terzo livello, il confronto tra API passa in rassegna le opzioni.

Domande frequenti#

L'EmailValidator di Django basta per validare un indirizzo email?#

Per la sintassi sì: validate_email (e ogni EmailField che lo usa) è il controllo giusto per il primo livello ed è migliore di una regex scritta a mano. Ma valida la forma, non la deliverability: non risolve mai il DNS né contatta un server di posta, quindi superarlo significa "sembra un'email", non "verrà recapitata". Abbinalo a una ricerca MX e a un controllo SMTP della casella prima di fidarti dell'indirizzo.

Come scrivo un validatore email personalizzato in Django?#

Un validatore è qualsiasi callable che riceve il valore e solleva django.core.exceptions.ValidationError in caso di errore. Definisci una funzione come validate_deliverable_domain(email) che solleva l'eccezione quando c'è un problema e restituisce None altrimenti, poi passala nella lista validators=[...] del campo o chiamala dal metodo clean_email di un form. Lo stesso callable funziona nell'hook validate_email di un serializer DRF, perché anche DRF intercetta il ValidationError di Django.

Posso controllare se una casella esiste da Django senza un'API?#

In parte. dnspython conferma che il dominio accetta posta, il che esclude gratis i domini morti e non richiede altro che una piccola dipendenza. Confermare la casella significa una conversazione SMTP che puoi tentare con smtplib ma che non dovresti eseguire dal tuo server applicativo: la porta 25 è bloccata quasi ovunque e il risultato dipende dalla reputazione del tuo IP. È proprio il livello di cui si occupa un servizio di verifica.

Conviene eseguire questi controlli alla registrazione o durante la pulizia di una lista?#

Entrambe le cose, con una profondità diversa. Esegui i livelli di sintassi e MX in modo sincrono in clean_email (sono abbastanza veloci da bloccare la richiesta e dare un riscontro immediato) e lì agisci anche in base all'esito dell'API. Riserva la verifica in blocco, più pesante, alla pulizia delle liste e al lavoro di back office, dove la latenza non conta e puoi elaborare gli indirizzi in blocco.


Vuoi aggiungere il livello SMTP? Inserisci un indirizzo sintatticamente perfetto nella verifica email gratuita per vedere una stringa approvata da EmailField risultare undeliverable, poi collega lo stesso esito ai tuoi form con il riferimento API, esempi pronti da copiare e incollare inclusi.

La tua reputazione, protetta..

Pulisci la tua prima lista in pochi minuti. 100 crediti gratuiti, senza carta di credito.

Inizia