Validate emails in Django forms
A Django validator and form and model fields that fail an email address that cannot receive email. They are built on the official Python client library, check each address once, and never stop a real person from signing up when the API cannot answer in time.
from django import forms
from email_validation_django import DeliverableEmailFormField
class SignupForm(forms.Form):
email = DeliverableEmailFormField()Install
The package is not on PyPI yet. Download it here and install it with pip:
Install it into your project's environment (pip installs Django and the Python client library from PyPI):
pip install ./email_validation_django-0.1.0-py3-none-any.whlTo keep it in your requirements, commit the file to your repository and list its path there.
Create an API key on the API keys page with the permission "Validate addresses", and give it a daily credit cap to limit what a busy form can spend. Set it in your environment as
EV_API_KEY; never put it in code. Each new address a visitor sends is a paid check, so also rate-limit the form's view per visitor (with a rate-limiting package or at your proxy).
It needs Django 4.2 or newer and Python 3.10 or newer. Nothing goes in INSTALLED_APPS.
Use it
from django import forms
from django.db import models
from email_validation_django import (
DeliverableEmailField,
DeliverableEmailFormField,
DeliverableEmailValidator,
validate_email_deliverable,
)
# a form field: forms.EmailField, then the check once the format is right
class SignupForm(forms.Form):
email = DeliverableEmailFormField()
work_email = DeliverableEmailFormField(fail_on=["invalid", "disposable"], fail_open=False, required=False)
# a model field: models.EmailField, checked in full_clean() and in model forms
class Customer(models.Model):
email = DeliverableEmailField(unique=True)
# the validator on any field
email = forms.EmailField(validators=[validate_email_deliverable])
strict = DeliverableEmailValidator(fail_on=["invalid", "disposable"], messages={"disposable": "Please use your work address."})A malformed address is refused by Django's own email check and never sent.
Settings
All optional, in settings.py:
EMAIL_VALIDATION = {
# default: the EV_API_KEY environment variable
"API_KEY": None,
# default: EV_BASE_URL, else https://api.email-validation.org
"BASE_URL": None,
"FAIL_ON": ["invalid"],
"BLOCK_DISPOSABLE": False,
"FAIL_OPEN": True,
"TIMEOUT": 5,
"CLIENT_TIMEOUT": 8,
"CACHE": "default",
"CACHE_TTL": 3600,
}- Which statuses fail. Only
invalidby default. Set"BLOCK_DISPOSABLE": Trueto faildisposableaddresses as well.catch_allandunknownpass unless you add them: see what each status means. - When the check cannot finish (it timed out, the API could not be reached, it answered with a 5xx or 429 error, the account has no credits or no key is set), the address passes and a warning goes to the package's logger (named after the package) with the error code and request id, never the address. Set
"FAIL_OPEN": Falseto fail the field with "We could not check this address right now" instead. - EU data residency. Set
"BASE_URL": "https://api-eu.email-validation.org"; the same key works on both.
Speed and caching
The validator gives the API 5 seconds for the check (the API takes 5 to 15) and waits 8 seconds for the answer, without retrying: a slow mail server then answers unknown, which passes and is free.
Each result is kept in Django's cache for an hour, under a SHA-256 hash of the trimmed, lower-cased address, so a form sent again after fixing another field is not checked or charged again. Only the status is kept, never the address, and unknown results are not kept.
Messages
The messages are translatable (lazy gettext strings): translate them in your project's locale files, or replace one with the validator's messages argument. Each error has a code for your templates or API responses:
email_invalid email_disposable email_catch_all email_unknown email_unavailableThe messages never show the API's own error text.
Costs
Each completed check costs one credit. Results with the status unknown are free, and so are malformed addresses and the test addresses: try the field with accepted@sandbox.email-validation.org (passes) and no_mailbox@sandbox.email-validation.org (fails) at no cost. See credits and billing.
Related
- Validating sign-up forms: what to block and what to accept
- The Python client library for other calls, such as bulk lists