Metadata-Version: 2.1
Name: email-validation-django
Version: 0.1.0
Summary: Django validator and form fields for email-validation.org: rejects undeliverable addresses in forms.
Home-page: https://email-validation.org/docs/guides/django/
Author-email: "email-validation.org" <support@email-validation.org>
License: MIT
Requires-Python: >=3.10
Requires-Dist: Django>=4.2
Requires-Dist: email-validation-sdk>=1.0,<2
Description-Content-Type: text/markdown

# email-validation.org for Django

A validator and form and model fields that fail addresses that cannot receive email, checked with the
[email-validation.org](https://email-validation.org/) API through the official Python SDK (`email-validation-sdk`).

```python
from email_validation_django import DeliverableEmailField, DeliverableEmailFormField, validate_email_deliverable

class SignupForm(forms.Form):
    email = DeliverableEmailFormField()                       # forms.EmailField + the check

class User(models.Model):
    email = DeliverableEmailField(unique=True)                # models.EmailField + the check in full_clean()

email = forms.EmailField(validators=[validate_email_deliverable])
```

Docs: https://email-validation.org/docs/guides/django/. Django 4.2 or newer and Python 3.10 or newer (tested with
Django 6.1).

## Install

Not on PyPI yet. Download the wheel from the docs page, then:

```bash
pip install ./email_validation_django-<version>-py3-none-any.whl
```

pip installs Django and the SDK from PyPI. Set your API key in the environment as `EV_API_KEY` (never in code).
Nothing goes in `INSTALLED_APPS`.

## Settings

All optional, in `settings.EMAIL_VALIDATION` (a dict):

| Key | Default | |
|---|---|---|
| `API_KEY` | the `EV_API_KEY` environment variable | your key |
| `BASE_URL` | `EV_BASE_URL`, else `https://api.email-validation.org` | `https://api-eu.email-validation.org` for EU data residency (the same key works). Must be `https://` (plain `http://` only to `localhost`, `127.0.0.1` or `[::1]`); otherwise nothing is sent and `FAIL_OPEN` decides |
| `FAIL_ON` | `["invalid"]` | the statuses that fail; `catch_all` and `unknown` pass unless listed |
| `BLOCK_DISPOSABLE` | `False` | also fail `disposable` |
| `FAIL_OPEN` | `True` | a check that cannot finish passes (see below) |
| `TIMEOUT` / `CLIENT_TIMEOUT` | `5` / `8` | seconds the API may spend / this app waits |
| `CACHE` / `CACHE_TTL` | `"default"` / `3600` | the cache alias and seconds a result is kept |

A validator or field can also take `fail_on=[...]` and `fail_open=...`; `DeliverableEmailValidator(messages={...})`
replaces a message.

## What it does

- **Fail open.** A check that cannot finish (a timeout, no connection, a 5xx or 429 answer, no credits, no key) lets
  the address through and logs a warning on the `email_validation_django` logger with the error code and request id,
  never the address. With `FAIL_OPEN: False` the field fails with "We could not check this address right now."
- **Fast.** The API may spend 5 seconds, the app waits 8, and nothing is retried. A malformed address is refused
  by Django's own check and never sent.
- **One charge per address.** Each completed check costs one credit; `unknown` results, malformed addresses and the
  test addresses on `sandbox.email-validation.org` are free. A result is kept in Django's cache under a SHA-256 hash
  of the trimmed, lower-cased address, so a form sent again is not charged again. `unknown` results are not kept.
- **Messages** are `gettext_lazy` strings (translate them with your project's locale files); their codes are
  `email_invalid`, `email_disposable`, `email_catch_all`, `email_unknown` and `email_unavailable`. They never show the
  API's own error text.

## Tests

Django's test runner (`django.test.SimpleTestCase`) against a stub transport and the sandbox addresses: no network,
no key, no database.

```bash
python runtests.py
```

## Licence

MIT
