diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6aed100..cca8edd 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -39,6 +39,10 @@ Fixes * **Amazon SES:** Fix bcc, which wasn't working at all on non-template sends. (Thanks to `@mwheels`_ for reporting the issue.) +* **Mailjet:** Fix TypeError when sending to or from addresses with display names + containing commas (introduced in Django 2.2.15, 3.0.9, and 3.1). + + Features ~~~~~~~~ diff --git a/anymail/backends/mailjet.py b/anymail/backends/mailjet.py index 12d57a1..62363e0 100644 --- a/anymail/backends/mailjet.py +++ b/anymail/backends/mailjet.py @@ -1,3 +1,5 @@ +from email.header import Header + from six.moves.urllib.parse import quote from ..exceptions import AnymailRequestsAPIError @@ -158,12 +160,16 @@ class MailjetPayload(RequestsPayload): def _format_email_for_mailjet(self, email): """Return EmailAddress email converted to a string that Mailjet can parse properly""" - # Workaround Mailjet 3.0 bug parsing display-name with commas + # Workaround Mailjet 3.0 bug parsing RFC-2822 quoted display-name with commas # (see test_comma_in_display_name in test_mailjet_backend for details) if "," in email.display_name: - return EmailAddress(email.display_name.encode('utf-8'), email.addr_spec).formataddr('utf-8') - else: - return email.address + # Force MIME "encoded-word" encoding on name, to hide comma from Mailjet. + # We just want the RFC-2047 quoting, not the header wrapping (which will + # be Mailjet's responsibility), so set a huge maxlinelen. + encoded_name = Header(email.display_name.encode('utf-8'), + charset='utf-8', maxlinelen=1000000).encode() + email = EmailAddress(encoded_name, email.addr_spec) + return email.address # # Payload construction diff --git a/tests/test_mailjet_backend.py b/tests/test_mailjet_backend.py index 749e392..d18d157 100644 --- a/tests/test_mailjet_backend.py +++ b/tests/test_mailjet_backend.py @@ -106,13 +106,19 @@ class MailjetBackendStandardEmailTests(MailjetBackendMockAPITestCase): # (This shouldn't be necessary in Mailjet 3.1, where Name becomes a separate json field for Cc/Bcc.) msg = mail.EmailMessage( 'Subject', 'Message', '"Example, Inc." ', - ['"Recipient, Ltd." ']) + ['"Recipient, Ltd." '], + cc=['"This is a very long display name, intended to test our workaround does not insert carriage returns' + ' or newlines into the encoded value, which would cause other problems" ') # this doesn't work self.assertEqual(data['To'], '=?utf-8?q?Recipient=2C_Ltd=2E?= ') # workaround + self.assertEqual(data['Cc'], '=?utf-8?q?This_is_a_very_long_display_name=2C_intended_to_test_our_workaround' + '_does_not_insert_carriage_returns_or_newlines_into_the_encoded_value=2C_which' + '_would_cause_other_problems?= ') def test_email_message(self): email = mail.EmailMessage(