getaddresses strict: Validate Email Address Lists

Published on: October 17, 2026
Reading time: 4 minutes
Email address validation with Python getaddresses strict

The strict parameter in email.utils.getaddresses helps applications parse address lists with tighter rules. It is especially useful when software reads To, Cc, or Reply-To values from external messages, forms, APIs, or imported data. Instead of accepting malformed structures and trying to repair them silently, strict parsing makes failures easier to detect and handle.

What getaddresses does

getaddresses receives a sequence of strings and returns pairs containing a display name and an address. It understands commas, quoted names, angle brackets, and common mailbox syntax.

from email.utils import getaddresses

headers = [
    'Alice <alice@example.com>, Bob <bob@example.com>'
]

for name, address in getaddresses(headers, strict=True):
    print(name, address)

With strict=True, malformed input is less likely to be converted into a result that only appears valid. This gives the calling application a clearer boundary between accepted and rejected data.

Why strict parsing matters

Permissive parsing can hide problems. A malformed address list may be interpreted differently from what the user intended, which can cause delivery mistakes, inconsistent records, or filter bypasses. Strict mode reduces ambiguity by following the message syntax more carefully.

However, strict parsing is only one layer. It does not prove that a domain exists, that a mailbox accepts mail, or that the address belongs to a specific person. Real systems should combine parsing with domain rules, confirmation workflows, rate limits, and application-specific authorization.

Validate the returned values

Do not assume that every returned pair is usable. Check for empty strings and enforce your own rules:

from email.utils import getaddresses


def extract_recipients(values: list[str]) -> list[str]:
    recipients = []

    for name, address in getaddresses(values, strict=True):
        address = address.strip()
        if not address or '@' not in address:
            raise ValueError('Invalid email address')
        recipients.append(address)

    if not recipients:
        raise ValueError('No valid recipients')

    return recipients

This example is intentionally simple. Production validation may also limit the number of recipients, reject control characters, and apply a domain allowlist.

Avoid destructive normalization

Many applications lowercase the whole address. That can be practical for internal comparison, but the local part is technically case-sensitive on some systems. Preserve the original value and create a separate normalized key when necessary.

Do not remove characters such as plus signs, dots, hyphens, or underscores without a provider-specific reason. An aggressive cleanup step can turn one valid address into another address. For broader Python guidance, browse the Python section and the Advanced Python articles.

Separate parsing from authorization

Parsing answers: “Is this structured like an address list?” Authorization answers: “May this address be used here?” Keep those responsibilities separate.

ALLOWED_DOMAINS = {'example.com', 'company.com'}


def is_allowed(address: str) -> bool:
    _, domain = address.rsplit('@', 1)
    return domain.lower() in ALLOWED_DOMAINS

This design makes testing easier and prevents business rules from being mixed into low-level syntax handling.

Handling web input

When addresses come from a browser or public API, enforce input limits before parsing. Restrict the total length of the field and the maximum number of recipients. Large inputs can waste CPU, memory, and logging capacity.

Reject carriage returns and line feeds supplied by users. Uncontrolled \r or \n characters can create header-injection problems. Prefer the structured classes in Python’s email package instead of manually concatenating header strings.

Using a data model

from dataclasses import dataclass
from email.utils import getaddresses

@dataclass(frozen=True)
class Recipient:
    name: str
    address: str


def parse_recipients(values: list[str]) -> list[Recipient]:
    items = []
    for name, address in getaddresses(values, strict=True):
        if not address or '@' not in address:
            raise ValueError(f'Invalid input: {name!r}')
        items.append(Recipient(name.strip(), address.strip()))
    return items

An immutable model prevents accidental changes after validation and works well across APIs, queues, and mail-delivery services.

Important tests

Test plain addresses, quoted display names, multiple recipients, empty values, extra commas, malformed brackets, duplicate addresses, Unicode names, and maximum lengths.

def test_valid_recipient():
    items = parse_recipients(['Alice <alice@example.com>'])
    assert items[0].address == 'alice@example.com'


def test_invalid_recipient():
    try:
        parse_recipients(['not an address'])
    except ValueError:
        pass
    else:
        raise AssertionError('ValueError was expected')

See the site’s testing content and automation guides for related practices.

Version compatibility

Confirm the minimum Python version supported by your project before passing strict. If your library must run on older interpreters, isolate compatibility logic in one documented function and test it in continuous integration.

Avoid catching every TypeError and retrying without strict mode. That pattern can hide programming errors unrelated to compatibility. Prefer explicit version checks or a clearly tested adapter.

Security boundaries

Strict parsing does not make an address trustworthy. Never use the parsed text as a shell command, file path, database fragment, or authorization token. Apply validation appropriate to every destination.

Logs also deserve care. Recording complete address lists can expose personal information. Log a request identifier, count, domain summary, or redacted value when full addresses are unnecessary.

Duplicate handling

Applications often need to remove duplicates while preserving order:

def unique_recipients(addresses: list[str]) -> list[str]:
    seen = set()
    result = []
    for address in addresses:
        key = address.casefold()
        if key not in seen:
            seen.add(key)
            result.append(address)
    return result

Decide whether deduplication applies across To, Cc, and Bcc. Keep the original recipient categories when privacy or audit requirements depend on them.

Error reporting

Public error messages should be useful without exposing parser internals. “One or more recipient addresses are invalid” is often safer than echoing the full malformed input. Internal logs can include structured diagnostic details with sensitive parts removed.

Official references

The Python email.utils documentation describes helper functions and version behavior. The complete Internet Message Format is specified by RFC 5322.

Conclusion

email.utils.getaddresses(..., strict=True) gives Python applications a stronger starting point for recipient parsing. Use it with explicit checks, size limits, immutable models, security rules, and thorough tests. Strict parsing reduces ambiguity, while the surrounding architecture determines whether the final system is reliable and safe.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Python code representing functools.Placeholder and positional arguments
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    functools.Placeholder: Flexible Arguments in partial

    Learn functools.Placeholder in Python 3.14 to build flexible partial and partialmethod calls with open positional arguments.

    Ler mais

    Tempo de leitura: 5 minutos
    17/10/2026
    Code on a screen representing Python t-strings
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python 3.14 T-strings: Structured Templates

    Learn Python t-strings in Python 3.14, inspect Template and Interpolation objects, and build structured, extensible renderers.

    Ler mais

    Tempo de leitura: 5 minutos
    16/10/2026
    CPU and memory modules representing Python shared_memory track
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    shared_memory track: Control the Resource Tracker

    Learn Python shared_memory track for safe resource tracking, ownership, cleanup, and independent process coordination.

    Ler mais

    Tempo de leitura: 5 minutos
    16/10/2026
    Data storage unit representing compression with Python compression.zstd
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    compression.zstd: Streaming and Safe Compression

    Learn Python compression.zstd for fast file and data compression with streaming, dictionaries, testing, and safe limits.

    Ler mais

    Tempo de leitura: 6 minutos
    15/10/2026
    Code on screen for diagnosing processes with Python sys.remote_exec
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    sys.remote_exec: Diagnose Running Python Processes

    Learn Python sys.remote_exec for diagnosing running processes with controlled scripts, strong permissions, and safe operational practices.

    Ler mais

    Tempo de leitura: 6 minutos
    15/10/2026
    Python code and environment configuration with os.reload_environ
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    os.reload_environ: Thread Safety and Environment

    Learn Python os.reload_environ to refresh externally changed environment variables with validation, compatibility, and thread-safety precautions.

    Ler mais

    Tempo de leitura: 4 minutos
    14/10/2026