django-email-validators
no more invalid or disposable emails in your database.
Installation
- Run
pip install django-email-validators - Add
django_email_validatorstosettings.INSTALLED_APPS - Restart your application server
Usage
Validators
- 🗑️
validate_email_non_disposable - 🌐
validate_email_mx - ✍️
validate_email_provider_typo - 👤
validate_email_unique- ⚫
validate_email_unique_dot_insensitive - ➕
validate_email_unique_subaddress_insensitive
- ⚫
validate_email_non_disposable
Validates that the email is not from a disposable email provider (fast, offline check).
validate_email_mx
Validates that the email domain has valid MX records (slow, requires network access).
validate_email_provider_typo
Validates that the email domain is not a likely typo of a common email provider. Checks a one-character diff against 80+ common providers and verifies the domain has no valid MX records (prevents false positives).
Examples that will be caught:
user@gmai.com-> suggestsuser@gmail.comuser@gmail.co-> suggestsuser@gmail.comuser@yahooo.com-> suggestsuser@yahoo.com
validate_email_unique
Validates that the email is unique in the database, preventing multiple accounts that map to the same inbox:
dot_insensitive(default:True): on dot-insensitive providers (e.g. Gmail) dots in the local part are ignored when comparing, sous.er@gmail.comanduser@gmail.comare treated as the same inbox.subaddress_insensitive(default:True): the+tagsubaddress (RFC 5233) is ignored when comparing, on any domain, souser+tag@example.comanduser@example.comare treated as the same inbox. Emails with+remain valid and are stored as entered: only the uniqueness check changes.
With both options disabled it performs a plain case-insensitive uniqueness check.
Accepts an optional exclude_pk argument to exclude the current user when updating an existing account, and an optional field argument (default: "email") to specify the model field name.
Examples that will be caught:
user@gmail.comalready exists →us.er@gmail.comis rejecteduser@example.comalready exists →user+tag@example.comis rejected (and vice versa)user@gmail.comalready exists →us.er+tag@gmail.comis rejected
Since this validator requires access to the model instance (to exclude it on update), it cannot be used directly in a field's validators=[...]. Call it explicitly in a form or serializer:
from django_email_validators import validate_email_unique
# Form example
class UserForm(forms.ModelForm):
def clean_email(self):
email = self.cleaned_data["email"]
validate_email_unique(
email,
exclude_pk=self.instance.pk, # exclude the current user on update
field="email", # model field name (default: "email")
message=None, # custom error message (default: localized message)
dot_insensitive=True, # ignore dots on dot-insensitive providers
subaddress_insensitive=True, # ignore the "+tag" subaddress
)
return email
Or via validate_unique on the model:
class User(models.Model):
email = models.EmailField()
def validate_unique(self, exclude=None):
super().validate_unique(exclude=exclude)
validate_email_unique(
self.email,
exclude_pk=self.pk, # exclude the current instance on update
field="email", # model field name (default: "email")
message=None, # custom error message (default: localized message)
dot_insensitive=True, # ignore dots on dot-insensitive providers
subaddress_insensitive=True, # ignore the "+tag" subaddress
)
validate_email_unique_dot_insensitive
Validates that the email is unique in the database, accounting only for dot-insensitive providers (e.g. Gmail treats dots in the local part as insignificant), while the +tag subaddress is significant.
Equivalent to validate_email_unique(dot_insensitive=True, subaddress_insensitive=False), it accepts the same exclude_pk, field and message arguments.
Examples that will be caught:
user@gmail.comalready exists →us.er@gmail.comis rejected
Examples that will pass:
user@example.comalready exists →us.er@example.compasses (non dot-insensitive domain)user@gmail.comalready exists →user+tag@gmail.compasses (subaddress is significant)
validate_email_unique_subaddress_insensitive
Validates that the email is unique in the database, ignoring only the +tag subaddress (RFC 5233), on any domain, while dots in the local part are always significant.
Equivalent to validate_email_unique(dot_insensitive=False, subaddress_insensitive=True), it accepts the same exclude_pk, field and message arguments.
Examples that will be caught:
user@example.comalready exists →user+tag@example.comis rejected (and vice versa)
Examples that will pass:
user@gmail.comalready exists →us.er@gmail.compasses (dots are significant)
Usage
Note:
validate_email_uniquerequires access to the model instance and cannot be used invalidators=[...]. See the dedicated section above for usage examples.
from django.db import models
from django_email_validators import (
validate_email_non_disposable,
validate_email_mx,
validate_email_provider_typo,
)
class User(models.Model):
email = models.EmailField(
validators=[
validate_email_non_disposable,
validate_email_mx,
validate_email_provider_typo,
]
)
Lookup
- 🔍
get_user_queryset_by_email - 🔍
get_user_object_by_email
Retrieve the user account(s) matching an email address, using the same matching rules as validate_email_unique (case-insensitive, dot_insensitive and subaddress_insensitive, both True by default). Unlike the validators, these functions never raise ValidationError for a match: they return the matching records.
Both accept the same arguments: field (default: "email"), exclude_pk, dot_insensitive, subaddress_insensitive and an optional base queryset (default: all users).
get_user_queryset_by_email
Returns the queryset of matching users (0..N records). Raises ValueError if the field does not exist on the user model.
get_user_object_by_email
Returns the first matching user or None. Unlike Manager.get, it never raises for missing matches.
from django_email_validators import get_user_object_by_email
user = get_user_object_by_email("us.er+tag@gmail.com")
# -> the user registered as "user@gmail.com", or None if not found
This is useful for enumeration-safe signup/recovery flows: retrieve the existing account matching the submitted email (including dot/subaddress variants) without revealing its existence in the response:
from django_email_validators import get_user_object_by_email
def signup(request):
email = request.POST["email"]
user = get_user_object_by_email(email)
if user:
# account already exists: notify the account owner by email
send_account_exists_email(user)
else:
create_account_and_send_confirmation(email)
# same response in both cases: no account enumeration
return render(request, "signup_check_your_email.html")
Extending the providers list for typo check
You can extend the list of common email providers used by validate_email_provider_typo by adding your own list in Django settings:
EMAIL_VALIDATORS_EXTEND_COMMON_PROVIDERS = [
'hey.com',
]
Extending the dot-insensitive domains list
You can extend the list of dot-insensitive domains used by validate_email_unique by adding your own list in Django settings:
EMAIL_VALIDATORS_EXTEND_DOT_INSENSITIVE_DOMAINS = [
'fastmail.com',
]
Testing
# clone repository
git clone https://github.com/fabiocaccamo/django-email-validators.git && cd django-email-validators
# create virtualenv and activate it
python -m venv venv && . venv/bin/activate
# upgrade pip
python -m pip install --upgrade pip
# install requirements
pip install -r requirements.txt -r requirements-test.txt
# install pre-commit to run formatters and linters
pre-commit install --install-hooks
# run tests
tox
# or
pytest
License
Released under MIT License.
Supporting
- :star: Star this project on GitHub
- :octocat: Follow me on GitHub
- :blue_heart: Follow me on Bluesky
- :moneybag: Sponsor me on Github
See also
-
django-admin-interface- the default admin interface made customizable by the admin itself. popup windows replaced by modals. 🧙 ⚡ -
django-cache-cleaner- clear the entire cache or individual caches easily using the admin panel or management command. 🧹 -
django-colorfield- simple color field for models with a nice color-picker in the admin. 🎨 -
django-extra-settings- config and manage typed extra settings using just the django admin. ⚙️ -
django-maintenance-mode- shows a 503 error page when maintenance-mode is on. 🚧 🛠️ -
django-redirects- redirects with full control. ↪️ -
django-treenode- probably the best abstract model / admin for your tree based stuff. 🌳 -
python-benedict- dict subclass with keylist/keypath support, I/O shortcuts (base64, csv, json, pickle, plist, query-string, toml, xml, yaml) and many utilities. 📘 -
python-codicefiscale- encode/decode Italian fiscal codes - codifica/decodifica del Codice Fiscale. 🇮🇹 💳 -
python-fontbro- friendly font operations. 🧢 -
python-fsutil- file-system utilities for lazy devs. 🧟♂️
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_email_validators-0.5.0.tar.gz.
File metadata
- Download URL: django_email_validators-0.5.0.tar.gz
- Upload date:
- Size: 23.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e61ca11ffda0d2293c323cc185b331ce45205a2ec1ab5cd664d6caa8fb47340a
|
|
| MD5 |
9d4e3f42f6354f7ffbea446254d72152
|
|
| BLAKE2b-256 |
d44c5b03f6bcdb8d47d6e83b9ab9c7e51353d82730a9c07d22d998d599468c27
|
Provenance
The following attestation bundles were made for django_email_validators-0.5.0.tar.gz:
Publisher:
create-release.yml on fabiocaccamo/django-email-validators
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_email_validators-0.5.0.tar.gz -
Subject digest:
e61ca11ffda0d2293c323cc185b331ce45205a2ec1ab5cd664d6caa8fb47340a - Sigstore transparency entry: 2289256470
- Sigstore integration time:
-
Permalink:
fabiocaccamo/django-email-validators@3e13dba33e47cd6a3a27f0a72536c9a75405ad30 -
Branch / Tag:
refs/tags/0.5.0 - Owner: https://github.com/fabiocaccamo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
create-release.yml@3e13dba33e47cd6a3a27f0a72536c9a75405ad30 -
Trigger Event:
push
-
Statement type:
File details
Details for the file django_email_validators-0.5.0-py3-none-any.whl.
File metadata
- Download URL: django_email_validators-0.5.0-py3-none-any.whl
- Upload date:
- Size: 18.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
90eae9892de3318110bc64f972efb7fb853fae72fed530c974208b2ea98233fe
|
|
| MD5 |
36f83a611fa53dfed5aff42d31f60001
|
|
| BLAKE2b-256 |
abbd7df26e3261184e92f7df970a195b7630a99f80972053bd85aa5570a7166c
|
Provenance
The following attestation bundles were made for django_email_validators-0.5.0-py3-none-any.whl:
Publisher:
create-release.yml on fabiocaccamo/django-email-validators
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_email_validators-0.5.0-py3-none-any.whl -
Subject digest:
90eae9892de3318110bc64f972efb7fb853fae72fed530c974208b2ea98233fe - Sigstore transparency entry: 2289256504
- Sigstore integration time:
-
Permalink:
fabiocaccamo/django-email-validators@3e13dba33e47cd6a3a27f0a72536c9a75405ad30 -
Branch / Tag:
refs/tags/0.5.0 - Owner: https://github.com/fabiocaccamo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
create-release.yml@3e13dba33e47cd6a3a27f0a72536c9a75405ad30 -
Trigger Event:
push
-
Statement type: