diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3c7ed4a..a85d32e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -52,6 +52,10 @@ Fixes * **Postmark:** Handle Postmark's SubscriptionChange events as Anymail unsubscribe, subscribe, or bounce tracking events, rather than "unknown". (Thanks to `@puru02`_ for the fix.) +* **Sendinblue:** Work around recent (unannounced) Sendinblue API change + that caused "Invalid headers" API error with non-string custom header + values. Anymail now converts int and float header values to strings. + Other ~~~~~ diff --git a/anymail/backends/sendinblue.py b/anymail/backends/sendinblue.py index 4bfff08..4db75af 100644 --- a/anymail/backends/sendinblue.py +++ b/anymail/backends/sendinblue.py @@ -3,7 +3,7 @@ from requests.structures import CaseInsensitiveDict from .base_requests import AnymailRequestsBackend, RequestsPayload from ..exceptions import AnymailRequestsAPIError from ..message import AnymailRecipientStatus -from ..utils import get_anymail_setting +from ..utils import get_anymail_setting, BASIC_NUMERIC_TYPES class EmailBackend(AnymailRequestsBackend): @@ -112,7 +112,12 @@ class SendinBluePayload(RequestsPayload): self.data['replyTo'] = self.email_object(emails[0]) def set_extra_headers(self, headers): - self.data['headers'].update(headers) + # SendinBlue requires header values to be strings -- not integers -- as of 11/2022. + # We'll stringify ints and floats; anything else is the caller's responsibility. + self.data["headers"].update({ + k: str(v) if isinstance(v, BASIC_NUMERIC_TYPES) else v + for k, v in headers.items() + }) def set_tags(self, tags): if len(tags) > 0: diff --git a/tests/test_sendinblue_backend.py b/tests/test_sendinblue_backend.py index b83be90..f053f42 100644 --- a/tests/test_sendinblue_backend.py +++ b/tests/test_sendinblue_backend.py @@ -118,7 +118,8 @@ class SendinBlueBackendStandardEmailTests(SendinBlueBackendMockAPITestCase): self.message.send() data = self.get_api_call_json() self.assertEqual(data['headers']['X-Custom'], 'string') - self.assertEqual(data['headers']['X-Num'], 123) + # Header values must be strings (changed 11/2022) + self.assertEqual(data['headers']['X-Num'], "123") # Reply-To must be moved to separate param self.assertNotIn('Reply-To', data['headers']) self.assertEqual(data['replyTo'], {'name': "Do Not Reply", 'email': "noreply@example.com"})