diff --git a/anymail/backends/sendgrid.py b/anymail/backends/sendgrid.py index b7e6f44..8434735 100644 --- a/anymail/backends/sendgrid.py +++ b/anymail/backends/sendgrid.py @@ -290,6 +290,12 @@ class SendGridPayload(RequestsPayload): def set_template_id(self, template_id): self.add_filter('templates', 'enable', 1) self.add_filter('templates', 'template_id', template_id) + # Must ensure text and html are non-empty, or template parts won't render. + # https://sendgrid.com/docs/API_Reference/Web_API_v3/Transactional_Templates/smtpapi.html#-Text-or-HTML-Templates + if not self.data.get("text", ""): + self.data["text"] = " " + if not self.data.get("html", ""): + self.data["html"] = " " def set_merge_data(self, merge_data): # Becomes smtpapi['sub'] in build_merge_data, after we know recipients and merge_field_format. diff --git a/docs/esps/sendgrid.rst b/docs/esps/sendgrid.rst index be169f0..6cc9dbf 100644 --- a/docs/esps/sendgrid.rst +++ b/docs/esps/sendgrid.rst @@ -282,6 +282,11 @@ your SendGrid template definition where you want the message-specific versions to appear). If you don't want to supply any additional subject or body content from your Django app, set those EmailMessage attributes to empty strings. +(Anymail will convert empty text and HTML bodies to single spaces whenever +:attr:`~anymail.message.AnymailMessage.template_id` is set, to ensure the +plaintext and HTML from your template are present in your outgoing email. +This works around a `limitation in SendGrid's template rendering`_.) + See the `SendGrid's template overview`_ and `transactional template docs`_ for more information. @@ -289,6 +294,8 @@ for more information. https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html .. _transactional template docs: https://sendgrid.com/docs/API_Reference/Web_API_v3/Transactional_Templates/smtpapi.html +.. _limitation in SendGrid's template rendering: + https://sendgrid.com/docs/API_Reference/Web_API_v3/Transactional_Templates/smtpapi.html#-Text-or-HTML-Templates .. _sendgrid-webhooks: diff --git a/tests/test_sendgrid_backend.py b/tests/test_sendgrid_backend.py index 42ffa89..f0b764d 100644 --- a/tests/test_sendgrid_backend.py +++ b/tests/test_sendgrid_backend.py @@ -400,6 +400,7 @@ class SendGridBackendAnymailFeatureTests(SendGridBackendMockAPITestCase): self.assertEqual(smtpapi['filters']['opentrack'], {'settings': {'enable': 0}}) def test_template_id(self): + self.message.attach_alternative("HTML Body", "text/html") self.message.template_id = "5997fcf6-2b9f-484d-acd5-7e9a99f0dc1f" self.message.send() smtpapi = self.get_smtpapi() @@ -407,6 +408,19 @@ class SendGridBackendAnymailFeatureTests(SendGridBackendMockAPITestCase): 'settings': {'enable': 1, 'template_id': "5997fcf6-2b9f-484d-acd5-7e9a99f0dc1f"} }) + data = self.get_api_call_data() + self.assertEqual(data['text'], "Text Body") + self.assertEqual(data['html'], "HTML Body") + + def test_template_id_with_empty_body(self): + # Text and html must be present (and non-empty-string), or the corresponding + # part will not render from the template. Make sure we fill in strings: + message = mail.EmailMessage(from_email='from@example.com', to=['to@example.com']) + message.template_id = "5997fcf6-2b9f-484d-acd5-7e9a99f0dc1f" + message.send() + data = self.get_api_call_data() + self.assertEqual(data['text'], " ") # single space is sufficient + self.assertEqual(data['html'], " ") def test_merge_data(self): self.message.from_email = 'from@example.com'