Upgrading to Django 6 is causing a number of email related tests to fail.
The tests are using ‘django.core.mail.backends.filebased.EmailBackend’.
When the sent email files are inspected, it appears that the default encoding has changed (not sure what it was previously, exactly. Something close to UTF-8), to ‘quoted printable’.
The emails are being constructed and sent using Django’s EmailMultiAlternatives class.
e.g.
Both quoted-printable and 8bit are valid Content-Transfer-Encoding values for an email message body part.[1] Python’s email package uses various heuristics to decide what encoding(s) to use for each particular message, and the logic is different in the legacy API used by earlier Django releases vs. the modern API used in Django 6.0.
I’m actually not able to reproduce the behavior you describe with Django 6.0 and a simple Unicode html_email body, so I’m guessing it might be dependent on your specific rendered template content. (Which suggests that approach to testing might already have been fragile.)
You have a few options for your tests:
If this is something like a snapshot test, I’d recommend updating your snapshots to match the new serialized message.
If you are trying to test that the sent message contains particular Unicode content, one approach is to use Python’s (modern) email package to parse the sent message and then check the parsed content. Something like:
from email import message_from_bytes, policy
message_bytes = ... # get one message from the output file
# Parse the bytes into a modern Python EmailMessage:
sent_message = message_from_bytes(message_bytes, policy=policy.default)
# Get the decoded body content:
sent_body = sent_message.get_body(("html", "plain")).get_content()
assert "Юнікодний текст" in sent_body
(Django’s own email tests use this approach.)
Or to verify that your code is sending particular content, it’s usually easier to use Django’s locmem EmailBackend (which is the default during testing) and examine the Django EmailMessage objects in the test outbox:
from django.core import mail
... # do something that should send a message
assert len(mail.outbox) == 1
assert isinstance(mail.outbox[0], mail.EmailMultiAlternatives)
assert mail.outbox[0].body_contains("Юнікодний текст")
(This keeps your tests focused on the behavior of your code, rather than the specifics of Django and Python email serialization in any given release.)
To answer your original question, if you truly want a filebased EmailBackend that uses Python’s legacy email serialization as in Django 5.2, you can subclass it to override the policy:
from django.core.mail.backends import filebased
from email import policy
class LegacyFileBasedBackend(filebased.EmailBackend):
def write_message(self, message):
serialized = message.message().as_bytes(policy=policy.compat32)
# use legacy serialization: ^^^^^^^^^^^^^^^^^^^^^^
self.stream.write(serialized + b"\n")
self.stream.write(b"-" * 79)
self.stream.write(b"\n")
# Then in your settings.py or tests override_settings():
EMAIL_BACKEND="path.to.above.LegacyFileBasedBackend"
(But note this makes your tests diverge significantly from the modern serialization used by Django’s SMTP EmailBackend.)
Personally, I’d lean toward option 3, or option 1 if these are snapshot tests.
quoted-printable is arguably a safer encoding if your message will pass through any SMTP servers that don’t support 8bitmime along the way to its destination. This is fairly common in older—but still active—enterprise email scanners. Even Amazon SES doesn’t fully support 8bit content. ↩︎