Repository: sendgrid/sendgrid-python

Issue:
Add Dynamic Template Support
#### Issue Summary

On 7/24/2018, our team publicly launched dynamic content for transactional templates. It is now available for all customers sending over v3 of our Mail Send API. Iterate over lists, handle conditionals and more, thanks to native support for a subset of Handlebars syntax!

More information can be found in our [blog post announcement](https://sendgrid.com/blog/how-to-use-sendgrids-dynamic-templates-for-your-transactional-emails/).

You can currently use this feature by manually creating the request body as [shown here](https://github.com/sendgrid/sendgrid-python/blob/master/USAGE.md#v3-mail-send). 

Now, we need to create helper code and examples for this SDK.

#### Acceptance Criteria

* [Implement a helper similar to what we have for the legacy templates](https://github.com/sendgrid/sendgrid-python/blob/master/use_cases/transational_templates.md)
* Update the [transactional_templates.md](https://github.com/sendgrid/sendgrid-python/blob/master/use_cases/transational_templates.md) example to demonstrate the new *Dynamic Templates* using the helper and re-name the current example to *Legacy*

#### Documentation
- [Reference Implementation](https://github.com/sendgrid/sendgrid-nodejs/pull/691#issuecomment-407490342)
- [How to Send an Email With Dynamic Transactional Templates](https://sendgrid.com/docs/User_Guide/Transactional_Templates/how_to_send_an_email_with_transactional_templates.html)
- [Using Handlebars](https://sendgrid.com/docs/User_Guide/Transactional_Templates/Using_handlebars.html)
- [Interactive API Docs](https://dynamic-templates.api-docs.io/3.0/mail-send-with-dynamic-transactional-templates/v3-mail-send)


Discussion:

Patch:
diff --git a/examples/helpers/mail/mail_example.py b/examples/helpers/mail_example.py
similarity index 91%
rename from examples/helpers/mail/mail_example.py
rename to examples/helpers/mail_example.py
index b2de7f0..0a5b868 100644
--- a/examples/helpers/mail/mail_example.py
+++ b/examples/helpers/mail_example.py
@@ -1,8 +1,6 @@
-import json
-import os
-import urllib2
+from sendgrid import SendGridAPIClient
 from sendgrid.helpers.mail import *
-from sendgrid import *
+
 
 # NOTE: you will need move this file to the root
 # directory of this project to execute properly.
@@ -217,3 +215,27 @@ send_hello_email()
 
 # this will only send an email if you set SandBox Mode to False
 send_kitchen_sink()
+
+
+def dynamic_template_usage():
+    """
+    Sample usage of dynamic (handlebars) transactional templates.
+    To make this work, you should have dynamic template created within your
+    SendGrid account. For this particular example, template may be like::
+
+        <p>Hello, {{name}}! Your current balance is {{balance}}<p>
+
+    """
+    mail = Mail()
+    mail.from_email = 'templates@sendgrid.com'
+    mail.template_id = 'd-your-dynamic-template-uid'
+    p = Personalization()
+    p.add_to(Email('user@example.com'))
+    p.dynamic_template_data = {
+        'name': 'Bob',
+        'balance': 42
+    }
+    mail.add_personalization(p)
+
+    sg = SendGridAPIClient(apikey='SG.your-api-key')
+    sg.client.mail.send.post(request_body=mail.get())
diff --git a/sendgrid/helpers/mail/personalization.py b/sendgrid/helpers/mail/personalization.py
index 8bb4bed..8032af9 100644
--- a/sendgrid/helpers/mail/personalization.py
+++ b/sendgrid/helpers/mail/personalization.py
@@ -1,6 +1,10 @@
 class Personalization(object):
     """A Personalization defines who should receive an individual message and
     how that message should be handled.
+
+    :var dynamic_template_data: data for dynamic transactional template.
+        Should be JSON-serializeable structure. No pre-processing sill be done
+        prior to sending this via http client.
     """
 
     def __init__(self):
@@ -13,6 +17,7 @@ class Personalization(object):
         self._substitutions = []
         self._custom_args = []
         self._send_at = None
+        self.dynamic_template_data = None
 
     @property
     def tos(self):
@@ -198,4 +203,8 @@ class Personalization(object):
 
         if self.send_at is not None:
             personalization["send_at"] = self.send_at
+
+        if self.dynamic_template_data is not None:
+            personalization['dynamic_template_data'] = self.dynamic_template_data
+
         return personalization
diff --git a/use_cases/transational_templates.md b/use_cases/transational_templates.md
index d3e3a00..2d74f92 100644
--- a/use_cases/transational_templates.md
+++ b/use_cases/transational_templates.md
@@ -66,6 +66,38 @@ print(response.body)
 print(response.headers)
 ```
 
+### With dynamic templates
+
+Sendgrid dynamic templates let you leverage power of [handlebars](https://handlebarsjs.com/)
+syntax to easily manage complex dynamic content in transactional emails.
+
+To check this example snippet, create
+[transactional email template](https://sendgrid.com/dynamic_templates) with code like
+```html
+<p>Hello, {{name}}! Your current balance is {{balance}}<p>
+```
+
+Than send email based on it, providing context for substitutions:
+```python
+from sendgrid import SendGridAPIClient
+from sendgrid.helpers.mail import Email, Personalization
+
+
+sg = SendGridAPIClient(apikey='SG.your-api-key')
+
+mail = Mail()
+mail.from_email='templates@example.com'
+mail.template_id = 'd-your-dynamic-template-uid'
+p = Personalization()
+p.add_to(Email('user@example.com'))
+p.dynamic_template_data = {'name': 'Bob', 'balance': 42}
+mail.add_personalization(p)
+
+sg.client.mail.send.post(request_body=mail.get())
+```
+
+Read more about dynamic templates in [docs](https://sendgrid.com/docs/User_Guide/Transactional_Templates/how_to_send_an_email_with_transactional_templates.html)
+
 ## Without Mail Helper Class
 
 ```python
@@ -113,4 +145,4 @@ except urllib.HTTPError as e:
 print(response.status_code)
 print(response.body)
 print(response.headers)
-```
\ No newline at end of file
+```


Test patch:
diff --git a/test/test_mail.py b/test/test_mail.py
index 7721b52..08d0feb 100644
--- a/test/test_mail.py
+++ b/test/test_mail.py
@@ -80,7 +80,6 @@ class UnitTests(unittest.TestCase):
         else:
             self.fail("Should have failed as SendGrid API key included")
 
-
     def test_helloEmail(self):
         self.max_diff = None
 
@@ -130,7 +129,7 @@ class UnitTests(unittest.TestCase):
         personalization = Personalization()
         personalization.add_to(Email("test@example.com"))
         mail.add_personalization(personalization)
-        
+
         mail.add_content(Content("text/html", "<html><body>some text here</body></html>"))
         mail.add_content(Content("text/plain", "some text here"))
 
@@ -562,3 +561,26 @@ class UnitTests(unittest.TestCase):
     def test_directly_setting_substitutions(self):
         personalization = Personalization()
         personalization.substitutions = [{'a': 0}]
+
+    def test_dynamic_template_data(self):
+        p = Personalization()
+        p.add_to(Email('test@sendgrid.com'))
+        p.dynamic_template_data = {
+            'customer': {
+                'name': 'Bob',
+                'returning': True
+                },
+            'total': 42
+            }
+
+        expected = {
+            'to': [{'email': 'test@sendgrid.com'}],
+            'dynamic_template_data': {
+                'customer': {
+                    'name': 'Bob',
+                    'returning': True
+                    },
+                'total': 42
+                }
+        }
+        self.assertDictEqual(p.get(), expected)