From 25cf1cbb1cb92c6c57b76cc43d697cfdc61f568f Mon Sep 17 00:00:00 2001 From: Mike Edmunds Date: Wed, 13 May 2026 12:25:44 -0700 Subject: [PATCH] Refs #36664 -- Updated SMTP EmailBackend tests for Python 3.15. Versions of Python prior to 3.15 would incorrectly encode non-ASCII email addresses using rfc2047, resulting in undeliverable email. The SMTP EmailBackend detects and prevents that (#35713). Python 3.15 fixes that behavior (CPython issue gh-122476). Updated test_rejects_non_ascii_local_part() to feature-detect the fix (in case it is backported) and check for a representative section of the Python error message if so; otherwise test for the SMTP EmailBackend workaround. Updated comments to clarify need and requirement. --- django/core/mail/backends/smtp.py | 6 ++++-- tests/mail/test_backends.py | 29 ++++++++++++++++++++--------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/django/core/mail/backends/smtp.py b/django/core/mail/backends/smtp.py index e7cca2c01d1d..84154dd1c06e 100644 --- a/django/core/mail/backends/smtp.py +++ b/django/core/mail/backends/smtp.py @@ -217,8 +217,10 @@ def prep_address(self, address, force_ascii=True): # Django allows local mailboxes like "From: webmaster" (#15042). defects.discard("addr-spec local part with no domain") if not force_ascii: - # Non-ASCII local-part is valid with SMTPUTF8. Remove once - # https://github.com/python/cpython/issues/81074 is fixed. + # PY315: Non-ASCII local-part is valid with SMTPUTF8. This check + # can be removed once the minimum supported Python version is 3.15 + # (so the fix for https://github.com/python/cpython/issues/81074 + # is in all supported versions). defects.discard("local-part contains non-ASCII characters)") if defects: raise ValueError(f"Invalid address {address!r}: {'; '.join(defects)}") diff --git a/tests/mail/test_backends.py b/tests/mail/test_backends.py index eec7568a2d7a..0782b18afe03 100644 --- a/tests/mail/test_backends.py +++ b/tests/mail/test_backends.py @@ -5,6 +5,8 @@ import ssl import sys import tempfile +from email.errors import HeaderWriteError +from email.message import EmailMessage as PyEmailMessage from io import StringIO from pathlib import Path from smtplib import SMTPException @@ -1093,17 +1095,26 @@ def test_does_not_reencode_idna(self): ) def test_rejects_non_ascii_local_part(self): - """ - The SMTP EmailBackend does not currently support non-ASCII local-parts. - (That would require using the RFC 6532 SMTPUTF8 extension.) #35713. - """ + # The SMTP EmailBackend must work around invalid email encoding caused + # by https://github.com/python/cpython/issues/122476 (#35713). + try: + # Detect fix for CPython issue gh-122476. + message = PyEmailMessage() + message["To"] = "nø@example.dk" + message.as_bytes() + except HeaderWriteError: + # PY315: Error from Python email generator. + msg = "Non-ASCII local-part 'nø' is invalid" + else: + # Python <=3.14: Error from smtp.EmailBackend.prep_address(). + msg = ( + "Invalid address 'nø@example.dk': local-part contains " + "non-ASCII characters" + ) + backend = self.create_backend() - backend.connection = mock.Mock(spec=object()) email = EmailMessage(to=["nø@example.dk"]) - with self.assertRaisesMessage( - ValueError, - "Invalid address 'nø@example.dk': local-part contains non-ASCII characters", - ): + with self.assertRaisesMessage((ValueError, HeaderWriteError), msg): backend.send_messages([email]) def test_prep_address_without_force_ascii(self):