Skip to content

Commit

Permalink
feat: add support for dynamic template data to Email class
Browse files Browse the repository at this point in the history
Fixes #899

This is helpful when sending multiple emails to multiple recipients. You can now include the dynamic template data with the recipient which will then be included in the personalization.
  • Loading branch information
Sam Harrison committed Jun 13, 2020
1 parent 75cc2d1 commit 8af3975
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 59 deletions.
12 changes: 6 additions & 6 deletions sendgrid/helpers/mail/dynamic_template_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ class DynamicTemplateData(object):

def __init__(self, dynamic_template_data=None, p=0):
"""Data for a transactional template.
Should be JSON-serializeable structure.
Should be JSON-serializable structure.
:param dynamic_template_data: Data for a transactional template.
:type dynamic_template_data: A JSON-serializeable structure
:type dynamic_template_data: A JSON-serializable structure
:param name: p is the Personalization object or Personalization object
index
:type name: Personalization, integer, optional
Expand All @@ -25,7 +25,7 @@ def __init__(self, dynamic_template_data=None, p=0):
def dynamic_template_data(self):
"""Data for a transactional template.
:rtype: A JSON-serializeable structure
:rtype: A JSON-serializable structure
"""
return self._dynamic_template_data

Expand All @@ -34,7 +34,7 @@ def dynamic_template_data(self, value):
"""Data for a transactional template.
:param value: Data for a transactional template.
:type value: A JSON-serializeable structure
:type value: A JSON-serializable structure
"""
self._dynamic_template_data = value

Expand All @@ -59,7 +59,7 @@ def personalization(self, value):
def __str__(self):
"""Get a JSON representation of this object.
:rtype: A JSON-serializeable structure
:rtype: A JSON-serializable structure
"""
return str(self.get())

Expand All @@ -68,6 +68,6 @@ def get(self):
Get a JSON-ready representation of this DynamicTemplateData object.
:returns: Data for a transactional template.
:rtype: A JSON-serializeable structure.
:rtype: A JSON-serializable structure.
"""
return self.dynamic_template_data
49 changes: 33 additions & 16 deletions sendgrid/helpers/mail/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
html_entity_decode = __html_parser__.unescape

try:
basestring = basestring
basestring = basestring
except NameError:
# Define basestring when Python >= 3.0
basestring = str
Expand All @@ -32,7 +32,8 @@ def __init__(self,
name=None,
substitutions=None,
subject=None,
p=0):
p=0,
dynamic_template_data=None):
"""Create an Email with the given address and name.
Either fill the separate name and email fields, or pass all information
Expand All @@ -41,17 +42,19 @@ def __init__(self,
:type email: string, optional
:param name: Name for this sender or recipient.
:type name: string, optional
:param substitutions: String substitutions to be applied to the email.
:type substitutions: list(Substitution), optional
:param subject: Subject for this sender or recipient.
:type subject: string, optional
:param p: p is the Personalization object or Personalization object
index
:type p: Personalization, integer, optional
:param dynamic_template_data: Data for a dynamic transactional template.
:type dynamic_template_data: DynamicTemplateData, optional
"""
self._name = None
self._email = None
self._substitutions = None
self._subject = None
self._personalization = None
self._personalization = p

if email and not name:
# allows passing emails as "Example Name <[email protected]>"
Expand All @@ -64,14 +67,11 @@ def __init__(self,
if name is not None:
self.name = name

if substitutions is not None:
self.substitutions = substitutions

if subject is not None:
self.subject = subject

if p is not None:
self.personalization = p
# Note that these only apply to To Emails (see Personalization.add_to)
# and should be moved but have not been for compatibility.
self._substitutions = substitutions
self._dynamic_template_data = dynamic_template_data
self._subject = subject

@property
def name(self):
Expand Down Expand Up @@ -129,7 +129,7 @@ def email(self, value):
@property
def substitutions(self):
"""A list of Substitution objects. These substitutions will apply to
the text and html content of the body of your email, in addition
the text and html content of the body of your email, in addition
to the subject and reply-to parameters. The total collective size
of your substitutions may not exceed 10,000 bytes per
personalization object.
Expand All @@ -141,20 +141,37 @@ def substitutions(self):
@substitutions.setter
def substitutions(self, value):
"""A list of Substitution objects. These substitutions will apply to
the text and html content of the body of your email, in addition to
the text and html content of the body of your email, in addition to
the subject and reply-to parameters. The total collective size of
your substitutions may not exceed 10,000 bytes per personalization
object.
:param value: A list of Substitution objects. These substitutions will
apply to the text and html content of the body of your email, in
apply to the text and html content of the body of your email, in
addition to the subject and reply-to parameters. The total collective
size of your substitutions may not exceed 10,000 bytes per
personalization object.
:type value: list(Substitution)
"""
self._substitutions = value

@property
def dynamic_template_data(self):
"""Data for a dynamic transactional template.
:rtype: DynamicTemplateData
"""
return self._dynamic_template_data

@dynamic_template_data.setter
def dynamic_template_data(self, value):
"""Data for a dynamic transactional template.
:param value: DynamicTemplateData
:type value: DynamicTemplateData
"""
self._dynamic_template_data = value

@property
def subject(self):
"""Subject for this sender or recipient.
Expand Down
2 changes: 1 addition & 1 deletion sendgrid/helpers/mail/mail.py
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ def dynamic_template_data(self, value):
"""Data for a transactional template
:param value: Data for a transactional template
:type value: DynamicTemplateData, a JSON-serializeable structure
:type value: DynamicTemplateData, a JSON-serializable structure
"""
if not isinstance(value, DynamicTemplateData):
value = DynamicTemplateData(value)
Expand Down
20 changes: 14 additions & 6 deletions sendgrid/helpers/mail/personalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,16 @@ def add_to(self, email):
self.add_substitution(substitution)
else:
self.add_substitution(email.substitutions)

if email.dynamic_template_data:
self.dynamic_template_data = email.dynamic_template_data

if email.subject:
if isinstance(email.subject, str):
self.subject = email.subject
else:
self.subject = email.subject.get()

self._tos.append(email.get())

@property
Expand Down Expand Up @@ -149,10 +154,10 @@ def add_substitution(self, substitution):
:type substitution: Substitution
"""
if isinstance(substitution, dict):
self._substitutions.append(substitution)
else:
self._substitutions.append(substitution.get())
if not isinstance(substitution, dict):
substitution = substitution.get()

self._substitutions.append(substitution)

@property
def custom_args(self):
Expand Down Expand Up @@ -190,14 +195,17 @@ def send_at(self, value):
@property
def dynamic_template_data(self):
"""Data for dynamic transactional template.
Should be JSON-serializeable structure.
Should be JSON-serializable structure.
:rtype: JSON-serializeable structure
:rtype: JSON-serializable structure
"""
return self._dynamic_template_data

@dynamic_template_data.setter
def dynamic_template_data(self, value):
if not isinstance(value, dict):
value = value.get()

self._dynamic_template_data = value

def get(self):
Expand Down
82 changes: 65 additions & 17 deletions test/test_mail_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@
EmailMessage = message.Message

from sendgrid.helpers.mail import (
Asm, ApiKeyIncludedException, Attachment, BccSettings,
BypassListManagement, Category, ClickTracking, Content, CustomArg,
DynamicTemplateData, Email, FooterSettings, From, Ganalytics, Header,
Mail, MailSettings, OpenTracking, Personalization, SandBoxMode, Section,
SendGridException, SpamCheck, Subject, SubscriptionTracking, Substitution,
TrackingSettings, To, ValidateApiKey
Asm, Attachment,
ClickTracking, Content,
DynamicTemplateData, Email, From,
Mail, Personalization,
Subject, Substitution, To
)


Expand Down Expand Up @@ -210,18 +209,18 @@ def test_multiple_emails_to_multiple_recipients(self):

to_emails = [
To(email='[email protected]',
name='Example Name 0',
substitutions=[
Substitution('-name-', 'Example Name Substitution 0'),
Substitution('-github-', 'https://example.com/test0'),
],
subject=Subject('Override Global Subject')),
name='Example Name 0',
substitutions=[
Substitution('-name-', 'Example Name Substitution 0'),
Substitution('-github-', 'https://example.com/test0'),
],
subject=Subject('Override Global Subject')),
To(email='[email protected]',
name='Example Name 1',
substitutions=[
Substitution('-name-', 'Example Name Substitution 1'),
Substitution('-github-', 'https://example.com/test1'),
])
name='Example Name 1',
substitutions=[
Substitution('-name-', 'Example Name Substitution 1'),
Substitution('-github-', 'https://example.com/test1'),
])
]
global_substitutions = Substitution('-time-', '2019-01-01 00:00:00')
message = Mail(
Expand Down Expand Up @@ -285,6 +284,55 @@ def test_multiple_emails_to_multiple_recipients(self):
}''')
)

def test_dynamic_template_data(self):
self.maxDiff = None

to_emails = [
To(email='[email protected]',
name='Example To Name',
dynamic_template_data=DynamicTemplateData({'name': 'Example Name'}))
]
message = Mail(
from_email=From('[email protected]', 'Example From Name'),
to_emails=to_emails,
subject=Subject('Hi!'),
plain_text_content='Hello!',
html_content='<strong>Hello!</strong>')

self.assertEqual(
message.get(),
json.loads(r'''{
"content": [
{
"type": "text/plain",
"value": "Hello!"
},
{
"type": "text/html",
"value": "<strong>Hello!</strong>"
}
],
"from": {
"email": "[email protected]",
"name": "Example From Name"
},
"personalizations": [
{
"dynamic_template_data": {
"name": "Example Name"
},
"to": [
{
"email": "[email protected]",
"name": "Example To Name"
}
]
}
],
"subject": "Hi!"
}''')
)

def test_kitchen_sink(self):
from sendgrid.helpers.mail import (
Mail, From, To, Cc, Bcc, Subject, Substitution, Header,
Expand Down
23 changes: 10 additions & 13 deletions use_cases/send_multiple_emails_to_multiple_recipients.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,30 @@
```python
import os
import json
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail, To

to_emails = [
To(email='[email protected]',
name='Example Name 0',
substitutions={
'-name-': 'Example Name Substitution 0',
'-github-': 'https://example.com/test0',
dynamic_template_data={
'name': 'Dynamic Name 0',
'url': 'https://example.com/test0',
},
subject='Override Global Subject'),
To(email='[email protected]',
name='Example Name 1',
substitutions={
'-name-': 'Example Name Substitution 1',
'-github-': 'https://example.com/test1',
dynamic_template_data={
'name': 'Dynamic Name 1',
'url': 'https://example.com/test1',
}),
]
global_substitutions = {'-time-': '2019-01-01 00:00:00'}
message = Mail(
from_email=('[email protected]', 'Example From Name'),
to_emails=to_emails,
subject='Hi -name-, this is the global subject',
html_content='<strong>Hello -name-, your URL is ' +
'<a href=\"-github-\">here</a></strong> email sent at -time-',
global_substitutions=global_substitutions,
subject='Global subject',
is_multiple=True)
message.template_id = 'd-12345678901234567890123456789012'

try:
sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))
response = sendgrid_client.send(message)
Expand All @@ -36,4 +33,4 @@ try:
print(response.headers)
except Exception as e:
print(e.message)
```
```

0 comments on commit 8af3975

Please sign in to comment.