Skip to content

Commit

Permalink
SendGrid: support multiple reply_to
Browse files Browse the repository at this point in the history
Closes #325
  • Loading branch information
medmunds committed Jul 28, 2023
1 parent c8a5e13 commit 05afde0
Show file tree
Hide file tree
Showing 5 changed files with 17 additions and 38 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ Features
* **Brevo (Sendinblue):** Add support for inbound email. (See
`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
~~~~~~~~~~~~
Expand Down Expand Up @@ -1546,6 +1548,7 @@ Features
.. _@ewingrj: https://github.com/ewingrj
.. _@fdemmer: https://github.com/fdemmer
.. _@Flexonze: https://github.com/Flexonze
.. _@gdvalderrama: https://github.com/gdvalderrama
.. _@Honza-m: https://github.com/Honza-m
.. _@janneThoft: https://github.com/janneThoft
.. _@jc-ee: https://github.com/jc-ee
Expand Down
7 changes: 2 additions & 5 deletions anymail/backends/sendgrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,11 +254,8 @@ def set_subject(self, subject):
self.data["subject"] = subject

def set_reply_to(self, emails):
# SendGrid only supports a single address in the reply_to API param.
if len(emails) > 1:
self.unsupported_feature("multiple reply_to addresses")
if len(emails) > 0:
self.data["reply_to"] = self.email_object(emails[0])
if emails:
self.data["reply_to_list"] = [self.email_object(email) for email in emails]

def set_extra_headers(self, headers):
# SendGrid requires header values to be strings -- not integers.
Expand Down
8 changes: 0 additions & 8 deletions docs/esps/sendgrid.rst
Original file line number Diff line number Diff line change
Expand Up @@ -202,14 +202,6 @@ Limitations and quirks
webhook :attr:`message_id` will fall back to "smtp-id" when "anymail_id"
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**
SendGrid will accept *and send* just about anything as
a message's :attr:`from_email`. (And email protocols are
Expand Down
34 changes: 11 additions & 23 deletions tests/test_sendgrid_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def test_email_message(self):
self.assertEqual(
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(
data["headers"],
{
Expand Down Expand Up @@ -243,7 +243,8 @@ def test_extra_headers(self):
# Reply-To must be moved to separate param
self.assertNotIn("Reply-To", data["headers"])
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):
Expand All @@ -252,35 +253,20 @@ def test_extra_headers_serialization_error(self):
self.message.send()

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 = [
'"Reply recipient" <reply@example.com',
"reply2@example.com",
]
self.message.send()
data = self.get_api_call_json()
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):
text_content = "* Item one\n* Item two\n* Item three"
Expand Down Expand Up @@ -1050,6 +1036,8 @@ def test_default_omits_options(self):
self.assertNotIn("headers", data)
self.assertNotIn("ip_pool_name", data)
self.assertNotIn("mail_settings", data)
self.assertNotIn("reply_to", data)
self.assertNotIn("reply_to_list", data)
self.assertNotIn("sections", data)
self.assertNotIn("send_at", data)
self.assertNotIn("template_id", data)
Expand Down
3 changes: 1 addition & 2 deletions tests/test_sendgrid_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,7 @@ def test_all_options(self):
to=["to1@sink.sendgrid.net", '"Recipient 2, OK?" <to2@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>"],
# v3 only supports single reply-to:
reply_to=['"Reply, with comma" <reply@example.com>'],
reply_to=['"Reply, with comma" <reply@example.com>', "reply2@example.com"],
headers={"X-Anymail-Test": "value", "X-Anymail-Count": 3},
metadata={"meta1": "simple string", "meta2": 2},
send_at=send_at,
Expand Down

0 comments on commit 05afde0

Please sign in to comment.