Skip to content

Commit 53643da

Browse files
committed
Fix MIME-structure of HTML emails
To render properly in particular on Apple Mail, HTML mails with inline attachments need the structure: multipart/alternate text/plain multipart/related text/html image/png In particular, the cid-linked images must be part of the same multipart/related section as the HTML, otherwise they *also* end up at the bottom of the email as an attachment. To do this, separate out the idea of html attachments from other attachments. We currently don't have any other types of attachments, but since we might in the future, we shouldn't disable that functionality. Diagnosed by Tobias Bussmann <t.bussmann@gmx.net> Tested by Daniel Gustafsson <daniel@yesql.se>
1 parent 58a5ebd commit 53643da

2 files changed

Lines changed: 38 additions & 28 deletions

File tree

pgweb/mailqueue/util.py

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,43 @@ def _encoded_email_header(name, email):
2525
_utf8_charset.body_encoding = charset.QP
2626

2727

28-
def send_simple_mail(sender, receiver, subject, msgtxt, attachments=None, usergenerated=False, cc=None, replyto=None, sendername=None, receivername=None, messageid=None, suppress_auto_replies=True, is_auto_reply=False, htmlbody=None, headers={}, staggertype=None, stagger=None):
29-
# attachment format, each is a tuple of (name, mimetype,contents)
30-
# content should be *binary* and not base64 encoded, since we need to
31-
# use the base64 routines from the email library to get a properly
32-
# formatted output message
33-
msg = MIMEMultipart()
28+
def _add_attachments(attachments, msg):
29+
for a in attachments:
30+
main, sub = a['contenttype'].split('/')
31+
part = MIMENonMultipart(main, sub)
32+
part.set_payload(a['content'])
33+
part.add_header('Content-Disposition', a.get('disposition', 'attachment; filename="%s"' % a['filename']))
34+
if 'id' in a:
35+
part.add_header('Content-ID', a['id'])
36+
37+
encoders.encode_base64(part)
38+
msg.attach(part)
39+
40+
41+
def send_simple_mail(sender, receiver, subject, msgtxt, attachments=None, usergenerated=False, cc=None, replyto=None, sendername=None, receivername=None, messageid=None, suppress_auto_replies=True, is_auto_reply=False, htmlbody=None, headers={}, staggertype=None, stagger=None, htmlattachments=None):
42+
if htmlbody:
43+
mpart = MIMEMultipart("alternative")
44+
mpart.attach(MIMEText(msgtxt, _charset=_utf8_charset))
45+
if htmlattachments:
46+
# HTML with attachments go in as a separate part that's multipart/related
47+
hpart = MIMEMultipart("related")
48+
hpart.attach(MIMEText(htmlbody, 'html', _charset=_utf8_charset))
49+
_add_attachments(htmlattachments, hpart)
50+
mpart.attach(hpart)
51+
else:
52+
# Just HTML with nothing more
53+
mpart.attach(MIMEText(htmlbody, 'html', _charset=_utf8_charset))
54+
else:
55+
# Just a plaintext body, so append it directly
56+
mpart = MIMEText(msgtxt, _charset='utf-8')
57+
58+
if attachments:
59+
msg = MIMEMultipart()
60+
msg.attach(mpart)
61+
_add_attachments(attachments, msg)
62+
else:
63+
msg = mpart
64+
3465
msg['Subject'] = subject
3566
msg['To'] = _encoded_email_header(receivername, receiver)
3667
msg['From'] = _encoded_email_header(sendername, sender)
@@ -61,27 +92,6 @@ def send_simple_mail(sender, receiver, subject, msgtxt, attachments=None, userge
6192
else:
6293
msg.add_header(h, v)
6394

64-
if htmlbody:
65-
mpart = MIMEMultipart("alternative")
66-
mpart.attach(MIMEText(msgtxt, _charset=_utf8_charset))
67-
mpart.attach(MIMEText(htmlbody, 'html', _charset=_utf8_charset))
68-
msg.attach(mpart)
69-
else:
70-
# Just a plaintext body, so append it directly
71-
msg.attach(MIMEText(msgtxt, _charset='utf-8'))
72-
73-
if attachments:
74-
for a in attachments:
75-
main, sub = a['contenttype'].split('/')
76-
part = MIMENonMultipart(main, sub)
77-
part.set_payload(a['content'])
78-
part.add_header('Content-Disposition', a.get('disposition', 'attachment; filename="%s"' % a['filename']))
79-
if 'id' in a:
80-
part.add_header('Content-ID', a['id'])
81-
82-
encoders.encode_base64(part)
83-
msg.attach(part)
84-
8595
with transaction.atomic():
8696
if staggertype and stagger:
8797
# Don't send a second one too close after another one of this class.

pgweb/news/util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ def send_news_email(news):
9797
receivername=settings.NEWS_MAIL_RECEIVER_NAME,
9898
messageid=messageid,
9999
htmlbody=html,
100-
attachments=attachments,
100+
htmlattachments=attachments,
101101
headers=headers,
102102
staggertype='news',
103103
stagger=timedelta(minutes=30),

0 commit comments

Comments
 (0)