Validate and normalize email addresses with email-validator offline: working versions (tested 2026-10-16)
A small wrapper around email-validator for validating and normalizing addresses without DNS lookups, including internationalized domain names and Unicode SMTPUTF8 local parts.
passed6 passed · 0 failed2026-10-16 21:56 UTC- Status
- passed
- Last passed
- 2026-10-16 21:56 UTC
- Tests
- 6 / 6
- Language
- Python
- Runtime
- Python 3.13.16
pinned versions2
email-validator@2.3.0pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
test environment
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 38972656245
The code
Exactly the files that passed. Your agent can pull them with get_recipe("email-validator-normalize") (install), or see them on GitHub.
requirements.txt
email-validator==2.3.0 pytest==9.1.1
emails.py
from dataclasses import dataclass
from email_validator import EmailNotValidError, validate_email
class InvalidEmail(ValueError):
pass
@dataclass(frozen=True)
class NormalizedEmail:
address: str
ascii_address: str | None
local_part: str
domain: str
smtputf8: bool
def normalize_email(raw, *, allow_smtputf8=True, allow_display_name=False):
try:
info = validate_email(
raw,
check_deliverability=False,
allow_smtputf8=allow_smtputf8,
allow_display_name=allow_display_name,
)
except (EmailNotValidError, TypeError) as exc:
raise InvalidEmail(str(exc)) from exc
return NormalizedEmail(
address=info.normalized,
ascii_address=info.ascii_email,
local_part=info.local_part,
domain=info.domain,
smtputf8=info.smtputf8,
)
def storage_key(raw) -> str:
info = normalize_email(raw)
return info.ascii_address or info.address
tests/test_email.py
import email_validator.deliverability
import pytest
from emails import InvalidEmail, normalize_email, storage_key
def test_role_mailbox_local_part_is_lowercased():
assert normalize_email("[email protected]").address == "[email protected]"
assert normalize_email("[email protected]").address == "[email protected]"
def test_idn_domain_round_trips_to_unicode():
unicode_form = normalize_email("user@münchen.de")
punycode_form = normalize_email("[email protected]")
assert unicode_form.address == punycode_form.address == "user@münchen.de"
assert unicode_form.ascii_address == "[email protected]"
assert storage_key("user@münchen.de") == "[email protected]"
def test_smtputf8_local_part_has_no_ascii_form():
info = normalize_email("пользователь@example.com")
assert info.smtputf8 is True and info.ascii_address is None
assert storage_key("пользователь@example.com") == "пользователь@example.com"
with pytest.raises(InvalidEmail):
normalize_email("пользователь@example.com", allow_smtputf8=False)
def test_bad_input_is_always_an_invalid_email_error():
for raw in ["not-an-email", "a@b", "[email protected].", None, 42]:
with pytest.raises(InvalidEmail):
normalize_email(raw)
def test_display_name_is_stripped():
info = normalize_email("John Doe <[email protected]>", allow_display_name=True)
assert info.address == "[email protected]"
def test_offline_never_touches_dns(monkeypatch):
def boom(*args, **kwargs):
raise AssertionError("DNS lookup attempted")
monkeypatch.setattr(
email_validator.deliverability, "validate_email_deliverability", boom
)
assert normalize_email("[email protected]").address == "[email protected]"
pytest.ini
[pytest] pythonpath = .
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.