Email: always use quoted-printable (#6617)

This commit is contained in:
pajowu
2026-09-30 09:54:21 +02:00
committed by GitHub
parent da6753b53f
commit 7bdb2c1555
2 changed files with 36 additions and 5 deletions
+5 -3
View File
@@ -54,7 +54,7 @@ from celery.exceptions import MaxRetriesExceededError
from django.conf import settings
from django.core.files.storage import default_storage
from django.core.mail import EmailMultiAlternatives, SafeMIMEMultipart
from django.core.mail.message import SafeMIMEText
from django.core.mail.message import SafeMIMEText, utf8_charset_qp
from django.db import connection, transaction
from django.db.models import Q
from django.dispatch import receiver
@@ -380,6 +380,8 @@ def mail(email: Union[str, Sequence[str]], subject: Union[str, FormattedString],
class CustomEmail(EmailMultiAlternatives):
encoding = utf8_charset_qp
def _create_mime_attachment(self, content, mimetype):
"""
Convert the content, mimetype pair into a MIME attachment object.
@@ -449,9 +451,9 @@ def mail_send_task(self, **kwargs) -> bool:
# Rewrite all <img> tags from real URLs or data URLs to inline attachments referred to by content ID
if outgoing_mail.body_html is not None:
html_message = SafeMIMEMultipart(_subtype='related', encoding=settings.DEFAULT_CHARSET)
html_message = SafeMIMEMultipart(_subtype='related')
html_with_cid, cid_images = replace_images_with_cid_paths(outgoing_mail.body_html)
html_message.attach(SafeMIMEText(html_with_cid, 'html', settings.DEFAULT_CHARSET))
html_message.attach(SafeMIMEText(html_with_cid, 'html', utf8_charset_qp))
attach_cid_images(html_message, cid_images, verify_ssl=True)
email.attach_alternative(html_message, "multipart/related")
+31 -2
View File
@@ -34,6 +34,7 @@
import datetime
import os
import quopri
import re
import socket
from contextlib import contextmanager
@@ -316,8 +317,10 @@ def _extract_html(mail):
if "multipart/related" in mimetype:
for sp in content._payload:
if isinstance(sp, MIMEText):
return sp._payload
break
if sp.get('content-transfer-encoding').startswith('quoted-printable'):
return quopri.decodestring(sp.get_payload()).decode()
else:
return sp.get_payload()
elif "text/html" in mimetype:
return content
@@ -711,3 +714,29 @@ def test_send_mail_private_ip(res, use_ssl, allow_private_networks, env):
assert m.status == OutgoingMail.STATUS_SENT
else:
assert m.status == OutgoingMail.STATUS_FAILED
@pytest.mark.django_db
def test_mail_encoding(env, mailoutbox):
m = OutgoingMail.objects.create(
to=['recipient@example.com'],
subject='Test',
body_plain='Test öäü testtesttest',
body_html='Test <b>ÖÄÜ</b>',
sender='sender@example.com',
)
assert m.status == OutgoingMail.STATUS_QUEUED
mail_send_task.apply(kwargs={
'outgoing_mail': m.pk,
}, max_retries=0)
m.refresh_from_db()
assert m.status == OutgoingMail.STATUS_SENT
assert len(mailoutbox) == 1
mail_plain = mailoutbox[0].message().as_string()
assert "ä" not in mail_plain
assert "Ä" not in mail_plain
assert "Test =C3=B6=C3=A4=C3=BC" in mail_plain
assert "Test <b>=C3=96=C3=84=C3=9C</b>" in mail_plain
assert max(len(x) for x in mail_plain.splitlines()) <= 76