From faf98c22d73d82f6e35395f04e77a096401ae2ba Mon Sep 17 00:00:00 2001 From: Mike Edmunds Date: Fri, 21 Jun 2024 17:41:33 -0700 Subject: [PATCH] MailerSend: support extra headers MailerSend added a `"headers"` API field (which is available to "Enterprise accounts only"). --- CHANGELOG.rst | 3 +++ anymail/backends/mailersend.py | 8 +++++--- docs/esps/mailersend.rst | 14 +++++++++++--- tests/test_mailersend_backend.py | 25 ++++++++----------------- tests/test_mailersend_integration.py | 9 +++++++-- 5 files changed, 34 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bd69b9f..776ac73 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -55,6 +55,9 @@ Features and ``tags`` when sending with a ``template_id``. (Requires boto3 v1.34.98 or later.) +* **MailerSend:** Allow all extra headers. (Note that MailerSend limits use + of this feature to "Enterprise accounts only.") + Fixes ~~~~~ diff --git a/anymail/backends/mailersend.py b/anymail/backends/mailersend.py index 5554066..f9af9ae 100644 --- a/anymail/backends/mailersend.py +++ b/anymail/backends/mailersend.py @@ -242,8 +242,8 @@ class MailerSendPayload(RequestsPayload): self.data["reply_to"] = self.make_mailersend_email(emails[0]) def set_extra_headers(self, headers): - # MailerSend doesn't support arbitrary email headers, but has - # individual API params for In-Reply-To and Precedence: bulk. + # MailerSend has individual API params for In-Reply-To and Precedence: bulk. + # The general "headers" option "is available to Enterprise accounts only". # (headers is a CaseInsensitiveDict, and is a copy so safe to modify.) in_reply_to = headers.pop("In-Reply-To", None) if in_reply_to is not None: @@ -256,7 +256,9 @@ class MailerSendPayload(RequestsPayload): self.data["precedence_bulk"] = is_bulk if headers: - self.unsupported_feature("most extra_headers (see docs)") + self.data["headers"] = [ + {"name": field, "value": value} for field, value in headers.items() + ] def set_text_body(self, body): self.data["text"] = body diff --git a/docs/esps/mailersend.rst b/docs/esps/mailersend.rst index 6ce1f62..9872b53 100644 --- a/docs/esps/mailersend.rst +++ b/docs/esps/mailersend.rst @@ -204,7 +204,12 @@ see :ref:`unsupported-features`. Anymail will use only the first one. **Limited extra headers** - MailerSend does not allow most extra headers. There are two exceptions: + MailerSend allows extra email headers for "Enterprise accounts only." + If you try to send :ref:`extra headers ` with a non-enterprise + account, you may receive an API error. + + However, MailerSend has special handling for two headers, + and *any* MailerSend account can send messages with them: * You can include :mailheader:`In-Reply-To` in extra headers, set to a message-id (without the angle brackets). @@ -216,8 +221,11 @@ see :ref:`unsupported-features`. if your extra headers have :mailheader:`Precedence` set to ``"bulk"`` or ``"list"`` or ``"junk"``, or ``false`` for any other value. - Any other extra headers will raise an - :exc:`~anymail.exceptions.AnymailUnsupportedFeature` error. + .. versionchanged:: 11.0 + + In earlier releases, attempting to send other headers + (even with an enterprise account) would raise an + :exc:`~anymail.exceptions.AnymailUnsupportedFeature` error. **No merge headers support** MailerSend's API does not provide a way to support Anymail's diff --git a/tests/test_mailersend_backend.py b/tests/test_mailersend_backend.py index 3bf23a8..90e109d 100644 --- a/tests/test_mailersend_backend.py +++ b/tests/test_mailersend_backend.py @@ -145,23 +145,6 @@ class MailerSendBackendStandardEmailTests(MailerSendBackendMockAPITestCase): ) def test_custom_headers(self): - email = mail.EmailMessage( - "Subject", - "Body goes here", - "from@example.com", - ["to1@example.com"], - headers={ - "Reply-To": "another@example.com", - "In-Reply-To": "12345@example.com", - "X-MyHeader": "my value", - "Message-ID": "mycustommsgid@example.com", - "Precedence": "Bulk", - }, - ) - with self.assertRaisesMessage(AnymailUnsupportedFeature, "extra_headers"): - email.send() - - def test_supported_custom_headers(self): email = mail.EmailMessage( "Subject", "Body goes here", @@ -171,13 +154,20 @@ class MailerSendBackendStandardEmailTests(MailerSendBackendMockAPITestCase): "Reply-To": "another@example.com", "In-Reply-To": "12345@example.com", "Precedence": "Bulk", + # Other custom headers only available to enterprise accounts: + "X-Custom": "custom header", }, ) email.send() data = self.get_api_call_json() + # Special handling headers: self.assertEqual(data["reply_to"], {"email": "another@example.com"}) self.assertEqual(data["in_reply_to"], "12345@example.com") self.assertIs(data["precedence_bulk"], True) + # Other headers: + self.assertEqual( + data["headers"], [{"name": "X-Custom", "value": "custom header"}] + ) def test_html_message(self): text_content = "This is an important message." @@ -607,6 +597,7 @@ class MailerSendBackendAnymailFeatureTests(MailerSendBackendMockAPITestCase): self.assertNotIn("reply_to", data) self.assertNotIn("html", data) self.assertNotIn("attachments", data) + self.assertNotIn("headers", data) self.assertNotIn("template_id", data) self.assertNotIn("tags", data) self.assertNotIn("variables", data) diff --git a/tests/test_mailersend_integration.py b/tests/test_mailersend_integration.py index 1b804d2..106d62b 100644 --- a/tests/test_mailersend_integration.py +++ b/tests/test_mailersend_integration.py @@ -81,8 +81,13 @@ class MailerSendBackendIntegrationTests(AnymailTestMixin, SimpleTestCase): bcc=["test+bcc1@anymail.dev", "Blind Copy 2 "], # MailerSend only supports single reply_to: reply_to=["Reply "], - # MailerSend supports very limited extra headers: - headers={"Precedence": "bulk", "In-Reply-To": "earlier-id@anymail.dev"}, + headers={ + # Special handling for these headers (available to all accounts): + "Precedence": "bulk", + "In-Reply-To": "earlier-id@anymail.dev", + # Only "enterprise accounts" can send other custom headers: + "X-Custom": "anymail test", + }, send_at=send_at, tags=["tag 1", "tag 2"], track_clicks=False,