Batch sending/merge and ESP templates¶
If your ESP offers templates and batch-sending/merge capabilities, Anymail can simplify using them in a portable way. Anymail doesn’t translate template syntax between ESPs, but it does normalize using templates and providing merge data for batch sends.
Here’s an example using both an ESP stored template and merge data:
from django.core.mail import EmailMessage
message = EmailMessage(
subject=None, # use the subject in our stored template
from_email="[email protected]",
to=["Wile E. <[email protected]>", "[email protected]"])
message.template_id = "after_sale_followup_offer" # use this ESP stored template
message.merge_data = { # per-recipient data to merge into the template
'[email protected]': {'NAME': "Wile E.",
'OFFER': "15% off anvils"},
'[email protected]': {'NAME': "Mr. Runner"},
}
message.merge_global_data = { # merge data for all recipients
'PARTNER': "Acme, Inc.",
'OFFER': "5% off any Acme product", # a default if OFFER missing for recipient
}
message.send()
The message’s template_id
identifies a template stored
at your ESP which provides the message body and subject. (Assuming the
ESP supports those features.)
The message’s merge_data
supplies the per-recipient
data to substitute for merge fields in your template. Setting this attribute
also lets Anymail know it should use the ESP’s batch sending
feature to deliver separate, individually-customized messages
to each address on the “to” list. (Again, assuming your ESP
supports that.)
Note
Templates and batch sending capabilities can vary widely between ESPs, as can the syntax for merge fields. Be sure to read the notes for your specific ESP, and test carefully with a small recipient list before launching a gigantic batch send.
Although related and often used together, ESP stored templates
and merge data are actually independent features.
For example, some ESPs will let you use merge field syntax
directly in your EmailMessage
body, so you can do customized batch sending without needing
to define a stored template at the ESP.
ESP stored templates¶
Many ESPs support transactional email templates that are stored and
managed within your ESP account. To use an ESP stored template
with Anymail, set template_id
on an EmailMessage
.
-
AnymailMessage.
template_id
¶ The identifier of the ESP stored template you want to use. For most ESPs, this is a
str
name or unique id. (See the notes for your specific ESP.)message.template_id = "after_sale_followup_offer"
With most ESPs, using a stored template will ignore any
body (plain-text or HTML) from the EmailMessage
object.
A few ESPs also allow you to define the message’s subject as part of the template,
but any subject you set on the EmailMessage
will override the template subject. To use the subject stored with the ESP template,
set the message’s subject
to None
:
message.subject = None # use subject from template (if supported)
Similarly, some ESPs can also specify the “from” address in the template
definition. Set message.from_email = None
to use the template’s “from.”
(You must set this attribute after constructing an
EmailMessage
object; passing
from_email=None
to the constructor will use Django’s
DEFAULT_FROM_EMAIL
setting, overriding your template value.)
Batch sending with merge data¶
Several ESPs support “batch transactional sending,” where a single API call can send messages to multiple recipients. The message is customized for each email on the “to” list by merging per-recipient data into the body and other message fields.
To use batch sending with Anymail (for ESPs that support it):
- Use “merge fields” (sometimes called “substitution variables” or similar)
in your message. This could be in an ESP stored template
referenced by
template_id
, or with some ESPs you can use merge fields directly in yourEmailMessage
(meaning the message itself is treated as an on-the-fly template). - Set the message’s
merge_data
attribute to define merge field substitutions for each recipient, and optionally setmerge_global_data
to defaults or values to use for all recipients. - Specify all of the recipients for the batch in the message’s
to
list.
Caution
It’s critical to set the merge_data
(or merge_metadata
) attribute:
this is how Anymail recognizes the message as a batch send.
When you provide merge_data, Anymail will tell the ESP to send an individual customized message to each “to” address. Without it, you may get a single message to everyone, exposing all of the email addresses to all recipients. (If you don’t have any per-recipient customizations, but still want individual messages, just set merge_data to an empty dict.)
The exact syntax for merge fields varies by ESP. It might be something like
*|NAME|*
or -name-
or <%name%>
. (Check the notes for
your ESP, and remember you’ll need to change
the template if you later switch ESPs.)
-
AnymailMessage.
merge_data
¶ A
dict
of per-recipient template substitution/merge data. Each key in the dict is a recipient email address, and its value is adict
of merge field names and values to use for that recipient:message.merge_data = { '[email protected]': {'NAME': "Wile E.", 'OFFER': "15% off anvils"}, '[email protected]': {'NAME': "Mr. Runner", 'OFFER': "instant tunnel paint"}, }
When
merge_data
is set, Anymail will use the ESP’s batch sending option, so that eachto
recipient gets an individual message (and doesn’t see the other emails on theto
list).
-
AnymailMessage.
merge_global_data
¶ A
dict
of template substitution/merge data to use for all recipients. Keys are merge field names in your message template:message.merge_global_data = { 'PARTNER': "Acme, Inc.", 'OFFER': "5% off any Acme product", # a default OFFER }
Merge data values must be strings. (Some ESPs also allow other JSON-serializable types like lists or dicts.) See Formatting merge data for more information.
Like all Anymail additions, you can use these extended template and
merge attributes with any EmailMessage
or subclass object.
(It doesn’t have to be an AnymailMessage
.)
Tip: you can add merge_global_data
to your
global Anymail send defaults to supply merge data
available to all batch sends (e.g, site name, contact info). The global
defaults will be merged with any per-message merge_global_data
.
Formatting merge data¶
If you’re using a date
, datetime
, Decimal
, or anything other
than strings and integers, you’ll need to format them into strings
for use as merge data:
product = Product.objects.get(123) # A Django model
total_cost = Decimal('19.99')
ship_date = date(2015, 11, 18)
# Won't work -- you'll get "not JSON serializable" errors at send time:
message.merge_global_data = {
'PRODUCT': product,
'TOTAL_COST': total_cost,
'SHIP_DATE': ship_date
}
# Do something this instead:
message.merge_global_data = {
'PRODUCT': product.name, # assuming name is a CharField
'TOTAL_COST': "%.2f" % total_cost,
'SHIP_DATE': ship_date.strftime('%B %d, %Y') # US-style "March 15, 2015"
}
These are just examples. You’ll need to determine the best way to format your merge data as strings.
Although floats are usually allowed in merge data, you’ll generally want to format them into strings yourself to avoid surprises with floating-point precision.
Anymail will raise AnymailSerializationError
if you attempt
to send a message with merge data (or metadata) that can’t be sent to your ESP.
ESP templates vs. Django templates¶
ESP templating languages are generally proprietary, which makes them inherently non-portable.
Anymail only exposes the stored template capabilities that your ESP already offers, and then simplifies providing merge data in a portable way. It won’t translate between different ESP template syntaxes, and it can’t do a batch send if your ESP doesn’t support it.
There are two common cases where ESP template and merge features are particularly useful with Anymail:
- When the people who develop and maintain your transactional email templates are different from the people who maintain your Django page templates. (For example, you use a single ESP for both marketing and transactional email, and your marketing team manages all the ESP email templates.)
- When you want to use your ESP’s batch-sending capabilities for performance reasons, where a single API call can trigger individualized messages to hundreds or thousands of recipients. (For example, sending a daily batch of shipping notifications.)
If neither of these cases apply, you may find that using Django templates can be a more portable and maintainable approach for building transactional email.