Skip to content

Commit

Permalink
Support for transactional template in Sendgrid v6 (#68)
Browse files Browse the repository at this point in the history
* Added support for transactional template in Sendgrid v6

* Increased version and added read version in setup.py

Co-authored-by: Mattia Fantoni <m.fantoni@reply.de>
  • Loading branch information
MattFanto and Mattia Fantoni authored Aug 24, 2020
1 parent 2a973d1 commit 1bcf544
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 27 deletions.
80 changes: 56 additions & 24 deletions sendgrid_backend/mail.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import logging

from future.builtins import str
import base64
from email.mime.base import MIMEBase
Expand All @@ -21,6 +23,8 @@

from python_http_client.exceptions import HTTPError

from sendgrid_backend.signals import sendgrid_email_sent

SENDGRID_VERSION = sendgrid.__version__

# Need to change imports because of breaking changes in sendgrid's v6 api
Expand All @@ -34,6 +38,8 @@
if sys.version_info >= (3.0, 0.0):
basestring = str

logger = logging.getLogger(__name__)


class SendgridBackend(BaseEmailBackend):
"""
Expand Down Expand Up @@ -118,16 +124,23 @@ def send_messages(self, email_messages):
for msg in email_messages:
data = self._build_sg_mail(msg)

fail_flag = True
try:
resp = self.sg.client.mail.send.post(request_body=data)
msg.extra_headers['status'] = resp.status_code
x_message_id = resp.headers.get('x-message-id', None)
if x_message_id:
msg.extra_headers['message_id'] = x_message_id
else:
logger.warning('No x_message_id header received from sendgrid api')
success += 1
except HTTPError:
fail_flag = False
except HTTPError as e:
logger.error("Failed to send email %s" % e)
if not self.fail_silently:
raise
finally:
sendgrid_email_sent.send(sender=self.__class__, message=msg, fail_flag=fail_flag)
return success

def _create_sg_attachment(self, django_attch):
Expand Down Expand Up @@ -188,7 +201,6 @@ def _build_sg_mail(self, msg):
mail = Mail()

mail.from_email = Email(*self._parse_email_address(msg.from_email))
mail.subject = msg.subject

personalization = Personalization()
for addr in msg.to:
Expand All @@ -204,22 +216,20 @@ def _build_sg_mail(self, msg):
for k, v in msg.custom_args.items():
personalization.add_custom_arg(CustomArg(k, v))

personalization.subject = msg.subject
if self._is_transaction_template(msg):
if msg.subject:
logger.warning("Message subject is ignored in transactional template, "
"please add it as template variable (e.g. {{ subject }}")
# See https://github.com/sendgrid/sendgrid-nodejs/issues/843
else:
personalization.subject = msg.subject

for k, v in msg.extra_headers.items():
if k.lower() == "reply-to":
mail.reply_to = Email(v)
else:
personalization.add_header(Header(k, v))

if hasattr(msg, "template_id"):
mail.template_id = msg.template_id
if hasattr(msg, "substitutions"):
for k, v in msg.substitutions.items():
personalization.add_substitution(Substitution(k, v))
if hasattr(msg, "dynamic_template_data"):
personalization.dynamic_template_data = msg.dynamic_template_data

if hasattr(msg, "ip_pool_name"):
if not isinstance(msg.ip_pool_name, basestring):
raise ValueError(
Expand Down Expand Up @@ -249,8 +259,6 @@ def _build_sg_mail(self, msg):
type(msg.send_at)))
personalization.send_at = msg.send_at

mail.add_personalization(personalization)

if hasattr(msg, "reply_to") and msg.reply_to:
if mail.reply_to:
# If this code path is triggered, the reply_to on the sg mail was set in a header above
Expand All @@ -270,18 +278,39 @@ def _build_sg_mail(self, msg):
sg_attch = self._create_sg_attachment(attch)
mail.add_attachment(sg_attch)

msg.body = ' ' if msg.body == '' else msg.body

if isinstance(msg, EmailMultiAlternatives):
mail.add_content(Content("text/plain", msg.body))
for alt in msg.alternatives:
if alt[1] == "text/html":
mail.add_content(Content(alt[1], alt[0]))
elif msg.content_subtype == "html":
mail.add_content(Content("text/plain", " "))
mail.add_content(Content("text/html", msg.body))
if self._is_transaction_template(msg):
if msg.body:
logger.warning("Message body is ignored in transactional template")
else:
mail.add_content(Content("text/plain", msg.body))
msg.body = ' ' if msg.body == '' else msg.body

if hasattr(msg, "template_id"):
# Template mails should not have subject and content attributes
mail.template_id = msg.template_id
if hasattr(msg, "substitutions"):
for k, v in msg.substitutions.items():
personalization.add_substitution(Substitution(k, v))
if hasattr(msg, "dynamic_template_data"):
if SENDGRID_VERSION < "6":
logger.warning("dynamic_template_data not available in sendgrid version < 6")
personalization.dynamic_template_data = msg.dynamic_template_data

if not self._is_transaction_template(msg):
# In sendgrid v6 we should not specify subject and content between request parameter
# when we are sending a request for a transactional template
mail.subject = msg.subject
if isinstance(msg, EmailMultiAlternatives):
mail.add_content(Content("text/plain", msg.body))
for alt in msg.alternatives:
if alt[1] == "text/html":
mail.add_content(Content(alt[1], alt[0]))
elif msg.content_subtype == "html":
mail.add_content(Content("text/plain", " "))
mail.add_content(Content("text/html", msg.body))
else:
mail.add_content(Content("text/plain", msg.body))

mail.add_personalization(personalization)

if hasattr(msg, "categories"):
for cat in msg.categories:
Expand All @@ -307,3 +336,6 @@ def _build_sg_mail(self, msg):
mail.tracking_settings = tracking_settings

return mail.get()

def _is_transaction_template(self, msg):
return SENDGRID_VERSION >= "6" and hasattr(msg, "template_id")
3 changes: 3 additions & 0 deletions sendgrid_backend/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import django.dispatch

sendgrid_email_sent = django.dispatch.Signal(providing_args=["message", "fail_flag"])
2 changes: 1 addition & 1 deletion sendgrid_backend/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.8.0"
__version__ = "0.9.0"
6 changes: 5 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@
with open(path.join(here, 'README.md'), encoding='utf-8') as f:
long_description = f.read()

__version__ = None
with open('sendgrid_backend/version.py') as f:
exec(f.read())

setup(
name="django-sendgrid-v5",
version="0.8.1",
version=str(__version__),
description="An implementation of Django's EmailBackend compatible with sendgrid-python v5+",
long_description=long_description,
url="https://github.com/sklarsa/django-sendgrid-v5",
Expand Down
39 changes: 38 additions & 1 deletion test/test_mail.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ def test_mime(self):
self.assertEqual(result["attachments"][0]["content"], base64.b64encode(f.read()))
self.assertEqual(result["attachments"][0]["type"], "image/png")

def test_templating(self):
def test_templating_sendgrid_v5(self):
msg = EmailMessage(
subject="Hello, World!",
body="Hello, World!",
Expand All @@ -354,6 +354,43 @@ def test_templating(self):
self.assertIn("template_id", result)
self.assertEquals(result["template_id"], "test_template")

def test_templating_sendgrid(self):
if SENDGRID_VERSION < "6":
msg = EmailMessage(
subject="Hello, World!",
body="Hello, World!",
from_email="Sam Smith <sam.smith@example.com>",
to=["John Doe <john.doe@example.com>", "jane.doe@example.com"],
)
msg.template_id = "test_template"
result = self.backend._build_sg_mail(msg)

self.assertIn("template_id", result)
self.assertEquals(result["template_id"], "test_template")
# Testing that for sendgrid v5 the code behave in the same way
self.assertEquals(result["content"], [{"type": "text/plain", "value": "Hello, World!"}])
self.assertEquals(result["subject"], "Hello, World!")
self.assertEquals(result["personalizations"][0]["subject"], "Hello, World!")
else:
msg = EmailMessage(
from_email="Sam Smith <sam.smith@example.com>",
to=["John Doe <john.doe@example.com>", "jane.doe@example.com"],
)
msg.template_id = "test_template"
msg.dynamic_template_data = {
"subject": "Hello, World!",
"content": "Hello, World!",
"link": "http://hello.com"
}
result = self.backend._build_sg_mail(msg)

self.assertIn("template_id", result)
self.assertEquals(result["template_id"], "test_template")
self.assertEquals(result["personalizations"][0]["dynamic_template_data"], msg.dynamic_template_data)
# Subject and content should not be between request param
self.assertNotIn("subject", result)
self.assertNotIn("content", result)

def test_asm(self):
msg = EmailMessage(
subject="Hello, World!",
Expand Down

0 comments on commit 1bcf544

Please sign in to comment.