SendGrid: support multiple reply_to

Closes #325
This commit is contained in:
Mike Edmunds
2023-07-27 18:33:27 -07:00
parent c8a5e13c89
commit 05afde0651
5 changed files with 17 additions and 38 deletions

View File

@@ -53,6 +53,8 @@ Features
* **Brevo (Sendinblue):** Add support for inbound email. (See * **Brevo (Sendinblue):** Add support for inbound email. (See
`docs <https://anymail.dev/en/latest/esps/sendinblue/#sendinblue-inbound>`_.) `docs <https://anymail.dev/en/latest/esps/sendinblue/#sendinblue-inbound>`_.)
* **SendGrid:** Support for multiple ``reply_to`` addresses.
(Thanks to `@gdvalderrama`_ for pointing out the new API.)
Deprecations Deprecations
~~~~~~~~~~~~ ~~~~~~~~~~~~
@@ -1546,6 +1548,7 @@ Features
.. _@ewingrj: https://github.com/ewingrj .. _@ewingrj: https://github.com/ewingrj
.. _@fdemmer: https://github.com/fdemmer .. _@fdemmer: https://github.com/fdemmer
.. _@Flexonze: https://github.com/Flexonze .. _@Flexonze: https://github.com/Flexonze
.. _@gdvalderrama: https://github.com/gdvalderrama
.. _@Honza-m: https://github.com/Honza-m .. _@Honza-m: https://github.com/Honza-m
.. _@janneThoft: https://github.com/janneThoft .. _@janneThoft: https://github.com/janneThoft
.. _@jc-ee: https://github.com/jc-ee .. _@jc-ee: https://github.com/jc-ee

View File

@@ -254,11 +254,8 @@ class SendGridPayload(RequestsPayload):
self.data["subject"] = subject self.data["subject"] = subject
def set_reply_to(self, emails): def set_reply_to(self, emails):
# SendGrid only supports a single address in the reply_to API param. if emails:
if len(emails) > 1: self.data["reply_to_list"] = [self.email_object(email) for email in emails]
self.unsupported_feature("multiple reply_to addresses")
if len(emails) > 0:
self.data["reply_to"] = self.email_object(emails[0])
def set_extra_headers(self, headers): def set_extra_headers(self, headers):
# SendGrid requires header values to be strings -- not integers. # SendGrid requires header values to be strings -- not integers.

View File

@@ -202,14 +202,6 @@ Limitations and quirks
webhook :attr:`message_id` will fall back to "smtp-id" when "anymail_id" webhook :attr:`message_id` will fall back to "smtp-id" when "anymail_id"
isn't present.) isn't present.)
**Single Reply-To**
SendGrid's v3 API only supports a single Reply-To address.
If your message has multiple reply addresses, you'll get an
:exc:`~anymail.exceptions.AnymailUnsupportedFeature` error---or
if you've enabled :setting:`ANYMAIL_IGNORE_UNSUPPORTED_FEATURES`,
Anymail will use only the first one.
**Invalid Addresses** **Invalid Addresses**
SendGrid will accept *and send* just about anything as SendGrid will accept *and send* just about anything as
a message's :attr:`from_email`. (And email protocols are a message's :attr:`from_email`. (And email protocols are

View File

@@ -187,7 +187,7 @@ class SendGridBackendStandardEmailTests(SendGridBackendMockAPITestCase):
self.assertEqual( self.assertEqual(
data["content"], [{"type": "text/plain", "value": "Body goes here"}] data["content"], [{"type": "text/plain", "value": "Body goes here"}]
) )
self.assertEqual(data["reply_to"], {"email": "another@example.com"}) self.assertEqual(data["reply_to_list"], [{"email": "another@example.com"}])
self.assertEqual( self.assertEqual(
data["headers"], data["headers"],
{ {
@@ -243,7 +243,8 @@ class SendGridBackendStandardEmailTests(SendGridBackendMockAPITestCase):
# Reply-To must be moved to separate param # Reply-To must be moved to separate param
self.assertNotIn("Reply-To", data["headers"]) self.assertNotIn("Reply-To", data["headers"])
self.assertEqual( self.assertEqual(
data["reply_to"], {"name": "Do Not Reply", "email": "noreply@example.com"} data["reply_to_list"],
[{"name": "Do Not Reply", "email": "noreply@example.com"}],
) )
def test_extra_headers_serialization_error(self): def test_extra_headers_serialization_error(self):
@@ -252,26 +253,6 @@ class SendGridBackendStandardEmailTests(SendGridBackendMockAPITestCase):
self.message.send() self.message.send()
def test_reply_to(self): def test_reply_to(self):
self.message.reply_to = ['"Reply recipient" <reply@example.com']
self.message.send()
data = self.get_api_call_json()
self.assertEqual(
data["reply_to"], {"name": "Reply recipient", "email": "reply@example.com"}
)
def test_multiple_reply_to(self):
# SendGrid v3 prohibits Reply-To in custom headers,
# and only allows a single reply address
self.message.reply_to = [
'"Reply recipient" <reply@example.com',
"reply2@example.com",
]
with self.assertRaises(AnymailUnsupportedFeature):
self.message.send()
@override_settings(ANYMAIL_IGNORE_UNSUPPORTED_FEATURES=True)
def test_multiple_reply_to_ignore_unsupported(self):
# Should use first Reply-To if ignoring unsupported features
self.message.reply_to = [ self.message.reply_to = [
'"Reply recipient" <reply@example.com', '"Reply recipient" <reply@example.com',
"reply2@example.com", "reply2@example.com",
@@ -279,8 +260,13 @@ class SendGridBackendStandardEmailTests(SendGridBackendMockAPITestCase):
self.message.send() self.message.send()
data = self.get_api_call_json() data = self.get_api_call_json()
self.assertEqual( self.assertEqual(
data["reply_to"], {"name": "Reply recipient", "email": "reply@example.com"} data["reply_to_list"],
[
{"name": "Reply recipient", "email": "reply@example.com"},
{"email": "reply2@example.com"},
],
) )
self.assertNotIn("reply_to", data) # not allowed with reply_to_list
def test_attachments(self): def test_attachments(self):
text_content = "* Item one\n* Item two\n* Item three" text_content = "* Item one\n* Item two\n* Item three"
@@ -1050,6 +1036,8 @@ class SendGridBackendAnymailFeatureTests(SendGridBackendMockAPITestCase):
self.assertNotIn("headers", data) self.assertNotIn("headers", data)
self.assertNotIn("ip_pool_name", data) self.assertNotIn("ip_pool_name", data)
self.assertNotIn("mail_settings", data) self.assertNotIn("mail_settings", data)
self.assertNotIn("reply_to", data)
self.assertNotIn("reply_to_list", data)
self.assertNotIn("sections", data) self.assertNotIn("sections", data)
self.assertNotIn("send_at", data) self.assertNotIn("send_at", data)
self.assertNotIn("template_id", data) self.assertNotIn("template_id", data)

View File

@@ -82,8 +82,7 @@ class SendGridBackendIntegrationTests(AnymailTestMixin, SimpleTestCase):
to=["to1@sink.sendgrid.net", '"Recipient 2, OK?" <to2@sink.sendgrid.net>'], to=["to1@sink.sendgrid.net", '"Recipient 2, OK?" <to2@sink.sendgrid.net>'],
cc=["cc1@sink.sendgrid.net", "Copy 2 <cc2@sink.sendgrid.net>"], cc=["cc1@sink.sendgrid.net", "Copy 2 <cc2@sink.sendgrid.net>"],
bcc=["bcc1@sink.sendgrid.net", "Blind Copy 2 <bcc2@sink.sendgrid.net>"], bcc=["bcc1@sink.sendgrid.net", "Blind Copy 2 <bcc2@sink.sendgrid.net>"],
# v3 only supports single reply-to: reply_to=['"Reply, with comma" <reply@example.com>', "reply2@example.com"],
reply_to=['"Reply, with comma" <reply@example.com>'],
headers={"X-Anymail-Test": "value", "X-Anymail-Count": 3}, headers={"X-Anymail-Test": "value", "X-Anymail-Count": 3},
metadata={"meta1": "simple string", "meta2": 2}, metadata={"meta1": "simple string", "meta2": 2},
send_at=send_at, send_at=send_at,