Add ESP templates, batch send and merge

* message.template_id to use ESP stored templates
* message.merge_data and merge_global_data
  to supply per-recipient/global merge variables
  (with or without an ESP stored template)
* When using per-recipient merge_data, tell ESP to use
  batch send: individual message per "to" address.
  (Mailgun does this automatically; SendGrid requires
  using a different "to" field; Mandrill requires
  `preserve_recipients=False`; Postmark doesn't
  support *this type* of batch sending with merge data.)
* Allow message.from_email=None (must be set after
  init) and message.subject=None to suppress those
  fields in API calls (for ESPs that allow "From" and
  "Subject" in their template definitions).

Mailgun:
* Emulate merge_global_data by copying to
  recipient-variables for each recipient.

SendGrid:
* Add delimiters to merge field names via
  esp_extra['merge_field_format'] or
  ANYMAIL_SENDGRID_MERGE_FIELD_FORMAT setting.

Mandrill:
* Remove Djrill versions of these features;
  update migration notes.

Closes #5.
This commit is contained in:
medmunds
2016-05-03 18:25:37 -07:00
parent 271eb5c926
commit 75730e8219
20 changed files with 882 additions and 245 deletions

View File

@@ -142,7 +142,6 @@ class MandrillBackendDjrillFeatureTests(MandrillBackendMockAPITestCase):
'google_analytics_domains': ['example.com/test'],
'google_analytics_campaign': ['UA-00000000-1'],
'merge_language': 'mailchimp',
'global_merge_vars': {'TEST': 'djrill'},
'async': True,
'ip_pool': 'Pool1',
'invalid': 'invalid',
@@ -170,9 +169,6 @@ class MandrillBackendDjrillSendDefaultsTests(MandrillBackendMockAPITestCase):
self.assertEqual(data['message']['google_analytics_domains'], ['example.com/test'])
self.assertEqual(data['message']['google_analytics_campaign'], ['UA-00000000-1'])
self.assertEqual(data['message']['merge_language'], 'mailchimp')
self.assertEqual(data['message']['global_merge_vars'],
[{'name': 'TEST', 'content': 'djrill'}])
self.assertFalse('merge_vars' in data['message'])
self.assertFalse('recipient_metadata' in data['message'])
# Options at top level of api params (not in message dict):
self.assertTrue(data['async'])
@@ -217,69 +213,29 @@ class MandrillBackendDjrillSendDefaultsTests(MandrillBackendMockAPITestCase):
self.assertEqual(data['message']['google_analytics_domains'], ['override.example.com'])
self.assertEqual(data['message']['google_analytics_campaign'], ['UA-99999999-1'])
self.assertEqual(data['message']['merge_language'], 'handlebars')
self.assertEqual(data['message']['global_merge_vars'], [{'name': 'TEST', 'content': 'djrill'}])
# Options at top level of api params (not in message dict):
self.assertFalse(data['async'])
self.assertEqual(data['ip_pool'], 'Bulk Pool')
def test_global_merge(self):
# Test that global settings merge in
self.message.global_merge_vars = {'GREETING': "Hello"}
self.message.send()
data = self.get_api_call_json()
self.assertEqual(data['message']['global_merge_vars'],
[{'name': "GREETING", 'content': "Hello"},
{'name': 'TEST', 'content': 'djrill'}])
def test_global_merge_overwrite(self):
# Test that global merge settings are overwritten
self.message.global_merge_vars = {'TEST': "Hello"}
self.message.send()
data = self.get_api_call_json()
self.assertEqual(data['message']['global_merge_vars'],
[{'name': 'TEST', 'content': 'Hello'}])
class MandrillBackendDjrillTemplateTests(MandrillBackendMockAPITestCase):
"""Test backend support for ESP templating features"""
# Holdovers from Djrill, until we design Anymail's normalized esp-template support
def test_merge_data(self):
# Anymail expands simple python dicts into the more-verbose name/content
# structures the Mandrill API uses
def test_merge_language(self):
self.message.merge_language = "mailchimp"
self.message.global_merge_vars = {'GREETING': "Hello",
'ACCOUNT_TYPE': "Basic"}
self.message.merge_vars = {
"customer@example.com": {'GREETING': "Dear Customer",
'ACCOUNT_TYPE': "Premium"},
"guest@example.com": {'GREETING': "Dear Guest"},
}
self.message.send()
data = self.get_api_call_json()
self.assertEqual(data['message']['merge_language'], "mailchimp")
self.assertEqual(data['message']['global_merge_vars'],
[{'name': 'ACCOUNT_TYPE', 'content': "Basic"},
{'name': "GREETING", 'content': "Hello"}])
self.assertEqual(data['message']['merge_vars'],
[{'rcpt': "customer@example.com",
'vars': [{'name': 'ACCOUNT_TYPE', 'content': "Premium"},
{'name': "GREETING", 'content': "Dear Customer"}]},
{'rcpt': "guest@example.com",
'vars': [{'name': "GREETING", 'content': "Dear Guest"}]}
])
def test_send_template(self):
self.message.template_name = "PERSONALIZED_SPECIALS"
def test_template_content(self):
self.message.template_content = {
'HEADLINE': "<h1>Specials Just For *|FNAME|*</h1>",
'OFFER_BLOCK': "<p><em>Half off</em> all fruit</p>"
}
self.message.send()
self.assert_esp_called("/messages/send-template.json")
data = self.get_api_call_json()
self.assertEqual(data['template_name'], "PERSONALIZED_SPECIALS")
# Anymail expands simple python dicts into the more-verbose name/content
# structures the Mandrill API uses
self.assertEqual(data['template_content'],
@@ -287,47 +243,3 @@ class MandrillBackendDjrillTemplateTests(MandrillBackendMockAPITestCase):
'content': "<h1>Specials Just For *|FNAME|*</h1>"},
{'name': "OFFER_BLOCK",
'content': "<p><em>Half off</em> all fruit</p>"}])
def test_send_template_without_from_field(self):
self.message.template_name = "PERSONALIZED_SPECIALS"
self.message.use_template_from = True
self.message.send()
self.assert_esp_called("/messages/send-template.json")
data = self.get_api_call_json()
self.assertEqual(data['template_name'], "PERSONALIZED_SPECIALS")
self.assertFalse('from_email' in data['message'])
self.assertFalse('from_name' in data['message'])
def test_send_template_without_from_field_api_failure(self):
self.set_mock_response(status_code=400)
self.message.template_name = "PERSONALIZED_SPECIALS"
self.message.use_template_from = True
with self.assertRaises(AnymailAPIError):
self.message.send()
def test_send_template_without_subject_field(self):
self.message.template_name = "PERSONALIZED_SPECIALS"
self.message.use_template_subject = True
self.message.send()
self.assert_esp_called("/messages/send-template.json")
data = self.get_api_call_json()
self.assertEqual(data['template_name'], "PERSONALIZED_SPECIALS")
self.assertFalse('subject' in data['message'])
def test_no_template_content(self):
# Just a template, without any template_content to be merged
self.message.template_name = "WELCOME_MESSAGE"
self.message.send()
self.assert_esp_called("/messages/send-template.json")
data = self.get_api_call_json()
self.assertEqual(data['template_name'], "WELCOME_MESSAGE")
self.assertEqual(data['template_content'], []) # Mandrill requires this field
def test_non_template_send(self):
# Make sure the non-template case still uses /messages/send.json
self.message.send()
self.assert_esp_called("/messages/send.json")
data = self.get_api_call_json()
self.assertFalse('template_name' in data)
self.assertFalse('template_content' in data)
self.assertFalse('async' in data)