From 6af3cb8327d24ac643c06ebfc5a89d3058935238 Mon Sep 17 00:00:00 2001 From: Mike Edmunds Date: Fri, 29 Sep 2023 14:20:37 -0700 Subject: [PATCH] Resend: workaround display-name bug --- anymail/backends/resend.py | 32 +++++++++++++++++++++++++++++--- tests/test_resend_backend.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/anymail/backends/resend.py b/anymail/backends/resend.py index 87801c37..09bc9166 100644 --- a/anymail/backends/resend.py +++ b/anymail/backends/resend.py @@ -1,9 +1,16 @@ import mimetypes +from email.charset import QP, Charset +from email.utils import formataddr from ..message import AnymailRecipientStatus from ..utils import CaseInsensitiveCasePreservingDict, get_anymail_setting from .base_requests import AnymailRequestsBackend, RequestsPayload +# Used to force RFC-2047 encoded word +# in address formatting workaround +QP_CHARSET = Charset("utf-8") +QP_CHARSET.header_encoding = QP + class EmailBackend(AnymailRequestsBackend): """ @@ -69,14 +76,31 @@ def serialize_data(self): def init_payload(self): self.data = {} # becomes json + @staticmethod + def _resend_email_address(address): + """ + Return EmailAddress address formatted for use with Resend. + + Works around a Resend bug that rejects properly formatted RFC 5822 + addresses that have the display-name enclosed in double quotes (e.g., + any display-name containing a comma). + """ + formatted = address.address + if formatted.startswith('"'): + # Workaround: force RFC-2047 encoded word + formatted = formataddr( + (QP_CHARSET.header_encode(address.display_name), address.addr_spec) + ) + return formatted + def set_from_email(self, email): - self.data["from"] = email.address + self.data["from"] = self._resend_email_address(email) def set_recipients(self, recipient_type, emails): assert recipient_type in ["to", "cc", "bcc"] if emails: field = recipient_type - self.data[field] = [email.address for email in emails] + self.data[field] = [self._resend_email_address(email) for email in emails] self.recipients += emails def set_subject(self, subject): @@ -84,7 +108,9 @@ def set_subject(self, subject): def set_reply_to(self, emails): if emails: - self.data["reply_to"] = [email.address for email in emails] + self.data["reply_to"] = [ + self._resend_email_address(email) for email in emails + ] def set_extra_headers(self, headers): self.data.setdefault("headers", {}).update(headers) diff --git a/tests/test_resend_backend.py b/tests/test_resend_backend.py index bc01b469..e624fc9a 100644 --- a/tests/test_resend_backend.py +++ b/tests/test_resend_backend.py @@ -94,6 +94,37 @@ def test_name_addr(self): data["bcc"], ["Blind Copy ", "bcc2@example.com"] ) + def test_quoted_display_names(self): + # Resend's API has a bug that rejects a display-name that is quoted + # (per RFC 5822 section 3.4). Attempting to omit the quotes works, unless + # the display-name also contains a comma. Try to avoid the whole problem + # by using RFC 2047 encoded words for addresses Resend will parse incorrectly. + msg = mail.EmailMessage( + "Subject", + "Message", + '"From, comma" ', + [ + '"To, comma" ', + "non–ascii ", + "=?utf-8?q?pre_encoded?= ", + ], + reply_to=['"Reply, comma" '], + ) + msg.send() + data = self.get_api_call_json() + self.assertEqual(data["from"], "=?utf-8?q?From=2C_comma?= ") + self.assertEqual( + data["to"], + [ + "=?utf-8?q?To=2C_comma?= ", + "=?utf-8?b?bm9u4oCTYXNjaWk=?= ", + "=?utf-8?q?pre_encoded?= ", + ], + ) + self.assertEqual( + data["reply_to"], ["=?utf-8?q?Reply=2C_comma?= "] + ) + def test_email_message(self): email = mail.EmailMessage( "Subject",