Skip to content

Conversation

@medmunds
Copy link
Contributor

@medmunds medmunds commented Dec 8, 2025

Based on testing each ESP's handling of Unicode in their API, update Anymail's ESP documentation, tests, and (where necessary) EmailBackends to ensure correct handling of non-ASCII characters in email headers.

As part of this work:

(Unicode attachment handling will be covered in a separate issue and PR.)

Improve Anymail's normalized EmailAddress object:
- Stop using Django's undocumented, deprecated sanitize_address() helper
- Add ANYMAIL["IDNA_ENCODER"] setting, defaulting to idna2008 [breaking]
  - Implement a few useful IDNA_ENCODER presets
  - Add idna package as direct dependency (already sub-dependency of 'requests')
  - Add uts46 package optional dependency
- Add base backend idna_encode method for subclass use
- Subclass EmailAddress from Python's modern email.headerregistry.Address object
- Add EmailAddress.as_dict() and formatting options useful for various ESPs
- Add utils to apply/reverse RFC 2047 encoded-word and RFC 5322 quoted-string
  formatting

Update each ESP's EmailBackend to use the appropriate EmailAddress and header
encodings, based on testing its API's behavior for Unicode characters.

Add some ESP-specific unsupported feature errors to prevent particularly
problematic Unicode handling:
- Brevo: Prevent non-ASCII values in custom headers to avoid raw, 8-bit
  utf-8 (also affects metadata, which uses a custom header)
- Mailgun: Prevent EAI in from_email (API accepts EAI, but generated
  message is undeliverable)
- Scaleway: Prevent EAI in any address field (API accepts EAI, but
  generated message is undeliverable)

Documentation:
- Add a new "International email" page in the advanced section, with
  general details on Unicode handling and the new IDNA_ENCODER setting
- Update each ESP's page to indicate whether it handles EAI
- Document some other ESP-specific Unicode quirks uncovered during testing

Closes #444
@medmunds medmunds force-pushed the feature/intl-handling branch from 80cd79f to 671f6e8 Compare December 8, 2025 22:47
@medmunds medmunds merged commit 671f6e8 into main Dec 8, 2025
54 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Django 6.0 support: replace sanitize_address() / international email address handling

2 participants