[FIX] base: missing MIME-Version header
Send an rich HTML email from Odoo, it lands in spam whereas it would land in inbox in 13.0. The problem is due to a missing "MIME-Version: 1.0" header on the email. This header is correctly set on both the html and text alternatives of the messages but it should be set on the enveloppe too. The problem is present in the newer EmailMessage mail API of python that is used since 14.0. Using the newer API, it doesn't set the header on the enveloppe itself. This reverts commit 8663f1e20727c315924bb20fc1de1accf3014c61. opw-3098621 closes odoo/odoo#109212 X-original-commit: c480d2bf1de7b70892130e74bb7a8d4003a8a783 Signed-off-by: Xavier Dollé (xdo) <xdo@odoo.com> Signed-off-by: Julien Castiaux <juc@odoo.com>
This commit is contained in:
@@ -467,8 +467,6 @@ class IrMailServer(models.Model):
|
||||
body = body or u''
|
||||
|
||||
msg = EmailMessage(policy=email.policy.SMTP)
|
||||
msg.set_charset('utf-8')
|
||||
|
||||
if not message_id:
|
||||
if object_id:
|
||||
message_id = tools.generate_tracking_message_id(object_id)
|
||||
@@ -492,9 +490,11 @@ class IrMailServer(models.Model):
|
||||
|
||||
email_body = ustr(body)
|
||||
if subtype == 'html' and not body_alternative:
|
||||
msg['MIME-Version'] = '1.0'
|
||||
msg.add_alternative(tools.html2plaintext(email_body), subtype='plain', charset='utf-8')
|
||||
msg.add_alternative(email_body, subtype=subtype, charset='utf-8')
|
||||
elif body_alternative:
|
||||
msg['MIME-Version'] = '1.0'
|
||||
msg.add_alternative(ustr(body_alternative), subtype=subtype_alternative, charset='utf-8')
|
||||
msg.add_alternative(email_body, subtype=subtype, charset='utf-8')
|
||||
else:
|
||||
|
||||
@@ -486,37 +486,42 @@ class EmailConfigCase(TransactionCase):
|
||||
self.assertEqual(message["From"], "settings@example.com")
|
||||
|
||||
|
||||
class _FakeSMTP:
|
||||
"""SMTP stub"""
|
||||
def __init__(self):
|
||||
self.messages = []
|
||||
self.from_filter = 'example.com'
|
||||
|
||||
# Python 3 before 3.7.4
|
||||
def sendmail(self, smtp_from, smtp_to_list, message_str,
|
||||
mail_options=(), rcpt_options=()):
|
||||
self.messages.append(message_str)
|
||||
|
||||
# Python 3.7.4+
|
||||
def send_message(self, message, smtp_from, smtp_to_list,
|
||||
mail_options=(), rcpt_options=()):
|
||||
self.messages.append(message.as_string())
|
||||
|
||||
|
||||
class TestEmailMessage(TransactionCase):
|
||||
def test_as_string(self):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls._fake_smtp = _FakeSMTP()
|
||||
|
||||
def build_email(self, **kwargs):
|
||||
kwargs.setdefault('email_from', 'from@example.com')
|
||||
kwargs.setdefault('email_to', 'to@example.com')
|
||||
kwargs.setdefault('subject', 'subject')
|
||||
return self.env['ir.mail_server'].build_email(**kwargs)
|
||||
|
||||
def send_email(self, msg):
|
||||
with patch.object(threading.current_thread(), 'testing', False):
|
||||
self.env['ir.mail_server'].send_email(msg, smtp_session=self._fake_smtp)
|
||||
return self._fake_smtp.messages.pop()
|
||||
|
||||
def test_bpo_34424_35805(self):
|
||||
"""Ensure all email sent are bpo-34424 and bpo-35805 free"""
|
||||
|
||||
message_truth = (
|
||||
r'From: .+? <joe@example\.com>\r\n'
|
||||
r'To: .+? <joe@example\.com>\r\n'
|
||||
r'Message-Id: <[0-9a-z.-]+@[0-9a-z.-]+>\r\n'
|
||||
r'References: (<[0-9a-z.-]+@[0-9a-z.-]+>\s*)+\r\n'
|
||||
r'\r\n'
|
||||
)
|
||||
|
||||
class FakeSMTP:
|
||||
"""SMTP stub"""
|
||||
def __init__(this):
|
||||
this.email_sent = False
|
||||
this.from_filter = 'example.com'
|
||||
|
||||
# Python 3 before 3.7.4
|
||||
def sendmail(this, smtp_from, smtp_to_list, message_str,
|
||||
mail_options=(), rcpt_options=()):
|
||||
this.email_sent = True
|
||||
self.assertRegex(message_str, message_truth)
|
||||
|
||||
# Python 3.7.4+
|
||||
def send_message(this, message, smtp_from, smtp_to_list,
|
||||
mail_options=(), rcpt_options=()):
|
||||
message_str = message.as_string()
|
||||
this.email_sent = True
|
||||
self.assertRegex(message_str, message_truth)
|
||||
|
||||
msg = email.message.EmailMessage(policy=email.policy.SMTP)
|
||||
msg['From'] = '"Joé Doe" <joe@example.com>'
|
||||
msg['To'] = '"Joé Doe" <joe@example.com>'
|
||||
@@ -525,7 +530,33 @@ class TestEmailMessage(TransactionCase):
|
||||
msg['Message-Id'] = '<929227342217024.1596730490.324691772460938-example-30661-some.reference@test-123.example.com>'
|
||||
msg['References'] = '<345227342212345.1596730777.324691772483620-example-30453-other.reference@test-123.example.com>'
|
||||
|
||||
smtp = FakeSMTP()
|
||||
self.patch(threading.current_thread(), 'testing', False)
|
||||
self.env['ir.mail_server'].send_email(msg, smtp_session=smtp)
|
||||
self.assertTrue(smtp.email_sent)
|
||||
msg_on_the_wire = self.send_email(msg)
|
||||
self.assertEqual(msg_on_the_wire,
|
||||
'From: =?utf-8?q?Jo=C3=A9?= Doe <joe@example.com>\r\n'
|
||||
'To: =?utf-8?q?Jo=C3=A9?= Doe <joe@example.com>\r\n'
|
||||
'Message-Id: <929227342217024.1596730490.324691772460938-example-30661-some.reference@test-123.example.com>\r\n'
|
||||
'References: <345227342212345.1596730777.324691772483620-example-30453-other.reference@test-123.example.com>\r\n'
|
||||
'\r\n'
|
||||
)
|
||||
|
||||
def test_alternative_correct_order(self):
|
||||
"""
|
||||
RFC-1521 7.2.3. The Multipart/alternative subtype
|
||||
> the alternatives appear in an order of increasing faithfulness
|
||||
> to the original content. In general, the best choice is the
|
||||
> LAST part of a type supported by the recipient system's local
|
||||
> environment.
|
||||
|
||||
Also, the MIME-Version header should be present in BOTH the
|
||||
enveloppe AND the parts
|
||||
"""
|
||||
msg = self.build_email(body='<p>Hello world</p>', subtype='html')
|
||||
msg_on_the_wire = self.send_email(msg)
|
||||
|
||||
self.assertGreater(msg_on_the_wire.index('text/html'), msg_on_the_wire.index('text/plain'),
|
||||
"The html part should be preferred (=appear after) to the text part")
|
||||
self.assertEqual(msg_on_the_wire.count('==============='), 2 + 2, # +2 for the header and the footer
|
||||
"There should be 2 parts: one text and one html")
|
||||
self.assertEqual(msg_on_the_wire.count('MIME-Version: 1.0'), 3,
|
||||
"There should be 3 headers MIME-Version: one on the enveloppe, "
|
||||
"one on the html part, one on the text part")
|
||||
|
||||
Reference in New Issue
Block a user