Skip to content

Commit 382ebf2

Browse files
committed
SendGrid: Improve esp_extra["personalizations"] handling.
Allow merging `esp_extra["personalizations"]` dict into other message-derived personalizations. (See comments in #120)
1 parent dbca132 commit 382ebf2

File tree

4 files changed

+48
-1
lines changed

4 files changed

+48
-1
lines changed

CHANGELOG.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ Features
3939
* **SendGrid:** Support both new "dynamic" and original "legacy" transactional
4040
templates. (See
4141
`docs <https://anymail.readthedocs.io/en/latest/esps/sendgrid/#sendgrid-templates>`__.)
42+
* **SendGrid:** Allow merging `esp_extra["personalizations"]` dict into other message-derived
43+
personalizations. (See
44+
`docs <https://anymail.readthedocs.io/en/latest/esps/sendgrid/#sendgrid-esp-extra>`__.)
4245

4346

4447
v4.0

anymail/backends/sendgrid.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import uuid
2+
from collections import Mapping
23
from email.utils import quote as rfc822_quote
34
import warnings
45

@@ -171,7 +172,7 @@ def build_merge_data_legacy(self):
171172
pass # no merge_data for this recipient
172173
self.data["personalizations"].append(personalization)
173174

174-
if self.merge_field_format is None and all(field.isalnum() for field in all_fields):
175+
if self.merge_field_format is None and len(all_fields) and all(field.isalnum() for field in all_fields):
175176
warnings.warn(
176177
"Your SendGrid merge fields don't seem to have delimiters, "
177178
"which can cause unexpected results with Anymail's merge_data. "
@@ -347,6 +348,10 @@ def set_merge_global_data(self, merge_global_data):
347348
def set_esp_extra(self, extra):
348349
self.merge_field_format = extra.pop("merge_field_format", self.merge_field_format)
349350
self.use_dynamic_template = extra.pop("use_dynamic_template", self.use_dynamic_template)
351+
if isinstance(extra.get("personalizations", None), Mapping):
352+
# merge personalizations *dict* into other message personalizations
353+
assert len(self.data["personalizations"]) == 1
354+
self.data["personalizations"][0].update(extra.pop("personalizations"))
350355
if "x-smtpapi" in extra:
351356
raise AnymailConfigurationError(
352357
"You are attempting to use SendGrid v2 API-style x-smtpapi params "

docs/esps/sendgrid.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,12 @@ Your :attr:`esp_extra` dict will be deeply merged into the
123123
parameters Anymail has constructed for the send, with `esp_extra`
124124
having precedence in conflicts.
125125

126+
Anymail has special handling for `esp_extra["personalizations"]`. If that value
127+
is a `dict`, Anymail will merge that personalizations dict into the personalizations
128+
for each message recipient. (If you pass a `list`, that will override the
129+
personalizations Anymail normally constructs from the message, and you will need to
130+
specify each recipient in the personalizations list yourself.)
131+
126132
Example:
127133

128134
.. code-block:: python
@@ -140,6 +146,11 @@ Example:
140146
"substitution_tag": "%%OPEN_TRACKING_PIXEL%%",
141147
},
142148
},
149+
# Because "personalizations" is a dict, Anymail will merge "future_feature"
150+
# into the SendGrid personalizations array for each message recipient
151+
"personalizations": {
152+
"future_feature": {"future": "data"},
153+
},
143154
}
144155
145156

tests/test_sendgrid_backend.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,34 @@ def test_esp_extra(self):
627627
self.assertEqual(data['categories'], ["tag"])
628628
self.assertEqual(data['tracking_settings']['click_tracking'], {'enable': True})
629629

630+
def test_esp_extra_pesonalizations(self):
631+
self.message.to = ["First recipient <[email protected]>", "[email protected]"]
632+
self.message.merge_data = {} # force separate messages for each 'to'
633+
634+
# esp_extra['personalizations'] dict merges with message-derived personalizations
635+
self.message.esp_extra = {
636+
"personalizations": {"future_feature": "works"}}
637+
self.message.send()
638+
data = self.get_api_call_json()
639+
self.assertEqual(data['personalizations'], [
640+
{'to': [{'email': '[email protected]', 'name': '"First recipient"'}],
641+
'future_feature': "works"},
642+
{'to': [{'email': '[email protected]'}],
643+
'future_feature': "works"}, # merged into *every* recipient
644+
])
645+
646+
# but esp_extra['personalizations'] list just overrides entire personalizations
647+
# (for backwards compatibility)
648+
self.message.esp_extra = {
649+
"personalizations": [{"to": [{"email": "[email protected]"}],
650+
"future_feature": "works"}]}
651+
self.message.send()
652+
data = self.get_api_call_json()
653+
self.assertEqual(data['personalizations'], [
654+
{'to': [{'email': '[email protected]'}],
655+
'future_feature': "works"},
656+
])
657+
630658
# noinspection PyUnresolvedReferences
631659
def test_send_attaches_anymail_status(self):
632660
""" The anymail_status should be attached to the message when it is sent """

0 commit comments

Comments
 (0)