Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for transactional template in Sendgrid v6 #68

Merged
merged 2 commits into from
Aug 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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