ProvenPin Install

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

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.