mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-21 20:31:06 -05:00
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:
@@ -25,22 +25,28 @@ The table below summarizes the Anymail features supported for each ESP.
|
||||
|
||||
.. currentmodule:: anymail.message
|
||||
|
||||
=========================================== ========= ========== ========== ==========
|
||||
Email Service Provider |Mailgun| |Mandrill| |Postmark| |SendGrid|
|
||||
=========================================== ========= ========== ========== ==========
|
||||
============================================ ========== ========== ========== ==========
|
||||
Email Service Provider |Mailgun| |Mandrill| |Postmark| |SendGrid|
|
||||
============================================ ========== ========== ========== ==========
|
||||
.. rubric:: :ref:`Anymail send options <anymail-send-options>`
|
||||
-------------------------------------------------------------------------------------------
|
||||
:attr:`~AnymailMessage.metadata` Yes Yes No Yes
|
||||
:attr:`~AnymailMessage.send_at` Yes Yes No Yes
|
||||
:attr:`~AnymailMessage.tags` Yes Yes Max 1 tag Yes
|
||||
:attr:`~AnymailMessage.track_clicks` Yes Yes No Yes
|
||||
:attr:`~AnymailMessage.track_opens` Yes Yes Yes Yes
|
||||
--------------------------------------------------------------------------------------------
|
||||
:attr:`~AnymailMessage.metadata` Yes Yes No Yes
|
||||
:attr:`~AnymailMessage.send_at` Yes Yes No Yes
|
||||
:attr:`~AnymailMessage.tags` Yes Yes Max 1 tag Yes
|
||||
:attr:`~AnymailMessage.track_clicks` Yes Yes No Yes
|
||||
:attr:`~AnymailMessage.track_opens` Yes Yes Yes Yes
|
||||
|
||||
.. rubric:: :ref:`templates-and-merge`
|
||||
--------------------------------------------------------------------------------------------
|
||||
:attr:`~AnymailMessage.template_id` No Yes Yes Yes
|
||||
:attr:`~AnymailMessage.merge_data` Yes Yes No Yes
|
||||
:attr:`~AnymailMessage.merge_global_data` (emulated) Yes Yes Yes
|
||||
|
||||
.. rubric:: :ref:`Status <esp-send-status>` and :ref:`event tracking <event-tracking>`
|
||||
-------------------------------------------------------------------------------------------
|
||||
:attr:`~AnymailMessage.anymail_status` Yes Yes Yes Yes
|
||||
|AnymailTrackingEvent| from webhooks Yes Yes Yes Yes
|
||||
=========================================== ========= ========== ========== ==========
|
||||
--------------------------------------------------------------------------------------------
|
||||
:attr:`~AnymailMessage.anymail_status` Yes Yes Yes Yes
|
||||
|AnymailTrackingEvent| from webhooks Yes Yes Yes Yes
|
||||
============================================ ========== ========== ========== ==========
|
||||
|
||||
|
||||
.. .. rubric:: :ref:`inbound`
|
||||
|
||||
@@ -105,6 +105,51 @@ values directly to Mailgun. You can use any of the (non-file) parameters listed
|
||||
.. _Mailgun sending docs: https://documentation.mailgun.com/api-sending.html#sending
|
||||
|
||||
|
||||
.. _mailgun-templates:
|
||||
|
||||
Batch sending/merge and ESP templates
|
||||
-------------------------------------
|
||||
|
||||
Mailgun does not offer :ref:`ESP stored templates <esp-stored-templates>`,
|
||||
so Anymail's :attr:`~anymail.message.AnymailMessage.template_id` message
|
||||
attribute is not supported with the Mailgun backend.
|
||||
|
||||
Mailgun *does* support :ref:`batch sending <batch-send>` with per-recipient
|
||||
merge data. You can refer to Mailgun "recipient variables" in your
|
||||
message subject and body, and supply the values with Anymail's
|
||||
normalized :attr:`~anymail.message.AnymailMessage.merge_data`
|
||||
and :attr:`~anymail.message.AnymailMessage.merge_global_data`
|
||||
message attributes:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
message = EmailMessage(
|
||||
...
|
||||
subject="Your order %recipient.order_no% has shipped",
|
||||
body="""Hi %recipient.name%,
|
||||
We shipped your order %recipient.order_no%
|
||||
on %recipient.ship_date%.""",
|
||||
to=["alice@example.com", "Bob <bob@example.com>"]
|
||||
)
|
||||
# (you'd probably also set a similar html body with %recipient.___% variables)
|
||||
message.merge_data = {
|
||||
'alice@example.com': {'name': "Alice", 'order_no': "12345"},
|
||||
'bob@example.com': {'name': "Bob", 'order_no': "54321"},
|
||||
}
|
||||
message.merge_global_data = {
|
||||
'ship_date': "May 15" # Anymail maps globals to all recipients
|
||||
}
|
||||
|
||||
Mailgun does not natively support global merge data. Anymail emulates
|
||||
the capability by copying any `merge_global_data` values to each
|
||||
recipient's section in Mailgun's "recipient-variables" API parameter.
|
||||
|
||||
See the `Mailgun batch sending`_ docs for more information.
|
||||
|
||||
.. _Mailgun batch sending:
|
||||
https://documentation.mailgun.com/user_manual.html#batch-sending
|
||||
|
||||
|
||||
.. _mailgun-webhooks:
|
||||
|
||||
Status tracking webhooks
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Mandrill
|
||||
========
|
||||
|
||||
Anymail integrates with the `Mandrill <http://mandrill.com/>`_
|
||||
Anymail integrates with the `Mandrill <http://mandrill.com/>`__
|
||||
transactional email service from MailChimp.
|
||||
|
||||
.. note:: **Limited Support for Mandrill**
|
||||
@@ -101,6 +101,61 @@ Anymail's Mandrill backend does not yet implement the
|
||||
:attr:`~anymail.message.AnymailMessage.esp_extra` feature.
|
||||
|
||||
|
||||
.. _mandrill-templates:
|
||||
|
||||
Batch sending/merge and ESP templates
|
||||
-------------------------------------
|
||||
|
||||
Mandrill offers both :ref:`ESP stored templates <esp-stored-templates>`
|
||||
and :ref:`batch sending <batch-send>` with per-recipient merge data.
|
||||
|
||||
You can use a Mandrill stored template by setting a message's
|
||||
:attr:`~anymail.message.AnymailMessage.template_id` to the
|
||||
template's name. Alternatively, you can refer to merge fields
|
||||
directly in an EmailMessage's subject and body---the message itself
|
||||
is used as an on-the-fly template.
|
||||
|
||||
In either case, supply the merge data values with Anymail's
|
||||
normalized :attr:`~anymail.message.AnymailMessage.merge_data`
|
||||
and :attr:`~anymail.message.AnymailMessage.merge_global_data`
|
||||
message attributes.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# This example defines the template inline, using Mandrill's
|
||||
# default MailChimp merge *|field|* syntax.
|
||||
# You could use a stored template, instead, with:
|
||||
# message.template_id = "template name"
|
||||
message = EmailMessage(
|
||||
...
|
||||
subject="Your order *|order_no|* has shipped",
|
||||
body="""Hi *|name|*,
|
||||
We shipped your order *|order_no|*
|
||||
on *|ship_date|*.""",
|
||||
to=["alice@example.com", "Bob <bob@example.com>"]
|
||||
)
|
||||
# (you'd probably also set a similar html body with merge fields)
|
||||
message.merge_data = {
|
||||
'alice@example.com': {'name': "Alice", 'order_no': "12345"},
|
||||
'bob@example.com': {'name': "Bob", 'order_no': "54321"},
|
||||
}
|
||||
message.merge_global_data = {
|
||||
'ship_date': "May 15",
|
||||
}
|
||||
|
||||
When you supply per-recipient :attr:`~anymail.message.AnymailMessage.merge_data`,
|
||||
Anymail automatically forces Mandrill's `preserve_recipients` option to false,
|
||||
so that each person in the message's "to" list sees only their own email address.
|
||||
|
||||
To use the subject or from address defined with a Mandrill template, set the message's
|
||||
`subject` or `from_email` attribute to `None`.
|
||||
|
||||
See the `Mandrill's template docs`_ for more information.
|
||||
|
||||
.. _Mandrill's template docs:
|
||||
https://mandrill.zendesk.com/hc/en-us/articles/205582507-Getting-Started-with-Templates
|
||||
|
||||
|
||||
.. _mandrill-webhooks:
|
||||
|
||||
Status tracking webhooks
|
||||
@@ -198,7 +253,8 @@ Changes to EmailMessage attributes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``message.send_at``
|
||||
If you are using an aware datetime for :attr:`send_at`,
|
||||
If you are using an aware datetime for
|
||||
:attr:`~anymail.message.AnymailMessage.send_at`,
|
||||
it will keep working unchanged with Anymail.
|
||||
|
||||
If you are using a date (without a time), or a naive datetime,
|
||||
@@ -211,7 +267,8 @@ Changes to EmailMessage attributes
|
||||
|
||||
``message.mandrill_response``
|
||||
Anymail normalizes ESP responses, so you don't have to be familiar
|
||||
with the format of Mandrill's JSON. See :attr:`anymail_status`.
|
||||
with the format of Mandrill's JSON.
|
||||
See :attr:`~anymail.message.AnymailMessage.anymail_status`.
|
||||
|
||||
The *raw* ESP response is attached to a sent message as
|
||||
``anymail_status.esp_response``, so the direct replacement
|
||||
@@ -221,14 +278,16 @@ Changes to EmailMessage attributes
|
||||
|
||||
mandrill_response = message.anymail_status.esp_response.json()
|
||||
|
||||
**Templates and merge variables**
|
||||
Coming to Anymail soon.
|
||||
``message.template_name``
|
||||
Anymail renames this to :attr:`~anymail.message.AnymailMessage.template_id`.
|
||||
|
||||
However, no other ESPs support MailChimp's templating language, so
|
||||
you'll need to rewrite your templates as you switch ESPs.
|
||||
``message.merge_vars`` and ``message.global_merge_vars``
|
||||
Anymail renames these to :attr:`~anymail.message.AnymailMessage.merge_data`
|
||||
and :attr:`~anymail.message.AnymailMessage.merge_global_data`, respectively.
|
||||
|
||||
Consider converting to :ref:`Django templates <django-templates>`
|
||||
instead, as these can be used with any email backend.
|
||||
``message.use_template_from`` and ``message.use_template_subject``
|
||||
With Anymail, set ``message.from_email = None`` or ``message.subject = None``
|
||||
to use the values from the stored template.
|
||||
|
||||
**Other Mandrill-specific attributes**
|
||||
Are currently still supported by Anymail's Mandrill backend,
|
||||
|
||||
@@ -115,6 +115,52 @@ see :ref:`unsupported-features`.
|
||||
Postmark does not support :attr:`~anymail.message.AnymailMessage.send_at`.
|
||||
|
||||
|
||||
.. _postmark-templates:
|
||||
|
||||
Batch sending/merge and ESP templates
|
||||
-------------------------------------
|
||||
|
||||
Postmark supports :ref:`ESP stored templates <esp-stored-templates>`
|
||||
populated with global merge data for all recipients, but does not
|
||||
offer :ref:`batch sending <batch-send>` with per-recipient merge data.
|
||||
Anymail's :attr:`~anymail.message.AnymailMessage.merge_data`
|
||||
message attribute is not supported with the Postmark backend.
|
||||
|
||||
To use a Postmark template, set the message's
|
||||
:attr:`~anymail.message.AnymailMessage.template_id` to the numeric
|
||||
Postmark "TemplateID" and supply the "TemplateModel" using
|
||||
the :attr:`~anymail.message.AnymailMessage.merge_global_data`
|
||||
message attribute:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
message = EmailMessage(
|
||||
...
|
||||
subject=None, # use template subject
|
||||
to=["alice@example.com"] # single recipient...
|
||||
# ...multiple to emails would all get the same message
|
||||
# (and would all see each other's emails in the "to" header)
|
||||
)
|
||||
message.template_id = 80801 # use this Postmark template
|
||||
message.merge_global_data = {
|
||||
'name': "Alice",
|
||||
'order_no': "12345",
|
||||
'ship_date': "May 15",
|
||||
'items': [
|
||||
{'product': "Widget", 'price': "9.99"},
|
||||
{'product': "Gadget", 'price': "17.99"},
|
||||
],
|
||||
}
|
||||
|
||||
Set the EmailMessage's subject to `None` to use the subject from
|
||||
your Postmark template, or supply a subject with the message to override
|
||||
the template value.
|
||||
|
||||
See this `Postmark blog post on templates`_ for more information.
|
||||
|
||||
.. _Postmark blog post on templates:
|
||||
https://postmarkapp.com/blog/special-delivery-postmark-templates
|
||||
|
||||
|
||||
.. _postmark-webhooks:
|
||||
|
||||
|
||||
@@ -89,6 +89,34 @@ Default ``True``. You can set to ``False`` to disable this behavior.
|
||||
See :ref:`Message-ID quirks <sendgrid-message-id>` below.
|
||||
|
||||
|
||||
.. setting:: ANYMAIL_SENDGRID_MERGE_FIELD_FORMAT
|
||||
|
||||
.. rubric:: SENDGRID_MERGE_FIELD_FORMAT
|
||||
|
||||
If you use :ref:`merge data <merge-data>`, set this to a :meth:`str.format`
|
||||
formatting string that indicates how merge fields are delimited
|
||||
in your SendGrid templates.
|
||||
For example, if your templates use the ``-field-`` hyphen delimiters
|
||||
suggested in some SendGrid docs, you would set:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ANYMAIL = {
|
||||
...
|
||||
"SENDGRID_MERGE_FIELD_FORMAT": "-{}-",
|
||||
}
|
||||
|
||||
The placeholder `{}` will become the merge field name. If you need to include
|
||||
a literal brace character, double it up. (For example, Handlebars-style
|
||||
``{{field}}`` delimiters would take the format string `"{{{{{}}}}}"`.)
|
||||
|
||||
The default `None` requires you include the delimiters directly in your
|
||||
:attr:`~anymail.message.AnymailMessage.merge_data` keys.
|
||||
You can also override this setting for individual messages.
|
||||
See the notes on SendGrid :ref:`templates and merge <sendgrid-templates>`
|
||||
below.
|
||||
|
||||
|
||||
.. setting:: ANYMAIL_SENDGRID_API_URL
|
||||
|
||||
.. rubric:: SENDGRID_API_URL
|
||||
@@ -185,6 +213,84 @@ Limitations and quirks
|
||||
(Tested March, 2016)
|
||||
|
||||
|
||||
.. _sendgrid-templates:
|
||||
|
||||
Batch sending/merge and ESP templates
|
||||
-------------------------------------
|
||||
|
||||
SendGrid offers both :ref:`ESP stored templates <esp-stored-templates>`
|
||||
and :ref:`batch sending <batch-send>` with per-recipient merge data.
|
||||
|
||||
You can use a SendGrid stored template by setting a message's
|
||||
:attr:`~anymail.message.AnymailMessage.template_id` to the
|
||||
template's unique id. Alternatively, you can refer to merge fields
|
||||
directly in an EmailMessage's subject and body---the message itself
|
||||
is used as an on-the-fly template.
|
||||
|
||||
In either case, supply the merge data values with Anymail's
|
||||
normalized :attr:`~anymail.message.AnymailMessage.merge_data`
|
||||
and :attr:`~anymail.message.AnymailMessage.merge_global_data`
|
||||
message attributes.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
message = EmailMessage(
|
||||
...
|
||||
subject="", # don't add any additional subject content to the template
|
||||
body="", # (same thing for additional body content)
|
||||
to=["alice@example.com", "Bob <bob@example.com>"]
|
||||
)
|
||||
message.template_id = "5997fcf6-2b9f-484d-acd5-7e9a99f0dc1f" # SendGrid id
|
||||
message.merge_data = {
|
||||
'alice@example.com': {'name': "Alice", 'order_no': "12345"},
|
||||
'bob@example.com': {'name': "Bob", 'order_no': "54321"},
|
||||
}
|
||||
message.merge_global_data = {
|
||||
'ship_date': "May 15",
|
||||
}
|
||||
message.esp_extra = {
|
||||
# Tell Anymail this SendGrid template uses "-field-" to refer to merge fields.
|
||||
# (We could also just set SENDGRID_MERGE_FIELD_FORMAT in our ANYMAIL settings.)
|
||||
'merge_field_format': "-{}-"
|
||||
}
|
||||
|
||||
SendGrid doesn't have a pre-defined merge field syntax, so you
|
||||
must tell Anymail how substitution fields are delimited in your templates.
|
||||
There are three ways you can do this:
|
||||
|
||||
* Set `'merge_field_format'` in the message's
|
||||
:attr:`~anymail.message.AnymailMessage.esp_extra` to a python :meth:`str.format`
|
||||
string, as shown in the example above. (This applies only to that
|
||||
particular EmailMessage.)
|
||||
* *Or* set :setting:`SENDGRID_MERGE_FIELD_FORMAT <ANYMAIL_SENDGRID_MERGE_FIELD_FORMAT>`
|
||||
in your Anymail settings. This is usually the best approach, and will apply to all messages
|
||||
sent through SendGrid. (You can still use esp_extra to override for individual messages.)
|
||||
* *Or* include the field delimiters directly in *all* your
|
||||
:attr:`~anymail.message.AnymailMessage.merge_data` and
|
||||
:attr:`~anymail.message.AnymailMessage.merge_global_data` keys.
|
||||
E.g.: ``{'-name-': "Alice", '-order_no-': "12345"}``.
|
||||
(This can be error-prone, and difficult to move to other ESPs.)
|
||||
|
||||
When you supply per-recipient :attr:`~anymail.message.AnymailMessage.merge_data`,
|
||||
Anymail automatically changes how it communicates the "to" list to SendGrid, so that
|
||||
so that each recipient sees only their own email address. (Anymail moves the recipients
|
||||
from top-level "to" and "toname" API parameters into the "x-smtpapi" section "to" list.)
|
||||
|
||||
SendGrid templates allow you to mix your EmailMessage's `subject` and `body`
|
||||
with the template subject and body (by using `<%subject%>` and `<%body%>` in
|
||||
your SendGrid template definition where you want the message-specific versions
|
||||
to appear). If you don't want to supply any additional subject or body content
|
||||
from your Django app, set those EmailMessage attributes to empty strings.
|
||||
|
||||
See the `SendGrid's template overview`_ and `transactional template docs`_
|
||||
for more information.
|
||||
|
||||
.. _SendGrid's template overview:
|
||||
https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html
|
||||
.. _transactional template docs:
|
||||
https://sendgrid.com/docs/API_Reference/Web_API_v3/Transactional_Templates/smtpapi.html
|
||||
|
||||
|
||||
.. _sendgrid-webhooks:
|
||||
|
||||
Status tracking webhooks
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.. _anymail-send-features:
|
||||
|
||||
.. module:: anymail.message
|
||||
|
||||
.. _anymail-send-features:
|
||||
|
||||
Anymail additions
|
||||
=================
|
||||
|
||||
|
||||
@@ -1,120 +1,216 @@
|
||||
.. _merge-vars:
|
||||
|
||||
Mail merge and ESP templates
|
||||
============================
|
||||
|
||||
Anymail has some features to simplify using your ESP's email
|
||||
templates and merge-variable features in a portable way.
|
||||
|
||||
However, ESP templating languages are generally proprietary,
|
||||
which makes them inherently non-portable. Although Anymail
|
||||
can normalize the Django code you write to supply merge
|
||||
variables to your ESP, it can't help you avoid needing
|
||||
to rewrite your email templates if you switch ESPs.
|
||||
|
||||
:ref:`Using Django templates <django-templates>` can be a
|
||||
better, portable and maintainable option.
|
||||
|
||||
|
||||
.. currentmodule:: anymail.message
|
||||
|
||||
.. _esp-templates:
|
||||
.. _templates-and-merge:
|
||||
|
||||
ESP templates
|
||||
-------------
|
||||
Batch sending/merge and ESP templates
|
||||
=====================================
|
||||
|
||||
.. warning::
|
||||
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.
|
||||
|
||||
These normalized ESP-template attributes aren't implemented yet.
|
||||
but are planned for a future Anymail update. If you are using
|
||||
your ESP's transactional templates,
|
||||
`your input <https://github.com/anymail/django-anymail/issues/5>`_
|
||||
would be appreciated.
|
||||
Here's an example using both an ESP stored template and merge data:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.core.mail import EmailMessage
|
||||
|
||||
message = EmailMessage(
|
||||
subject=None, # use the subject in our stored template
|
||||
from_email="marketing@example.com",
|
||||
to=["Wile E. <wile@example.com>", "rr@example.com"])
|
||||
message.template_id = "after_sale_followup_offer" # use this ESP stored template
|
||||
message.merge_data = { # per-recipient data to merge into the template
|
||||
'wile@example.com': {'NAME': "Wile E.",
|
||||
'OFFER': "15% off anvils"},
|
||||
'rr@example.com': {'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 :attr:`~AnymailMessage.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 :attr:`~AnymailMessage.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 :ref:`batch sending <batch-send>`
|
||||
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 :ref:`your specific ESP <supported-esps>`,
|
||||
and test carefully with a small recipient list before
|
||||
launching a gigantic batch send.
|
||||
|
||||
Although related and often used together, :ref:`esp-stored-templates`
|
||||
and :ref:`merge data <merge-data>` are actually independent features.
|
||||
For example, some ESPs will let you use merge field syntax
|
||||
directly in your :class:`~django.core.mail.EmailMessage`
|
||||
body, so you can do customized batch sending without needing
|
||||
to define a stored template at the ESP.
|
||||
|
||||
|
||||
.. To use a *Mandrill* (MailChimp) template stored in your Mandrill account,
|
||||
.. set a :attr:`template_name` and (optionally) :attr:`template_content`
|
||||
.. on your :class:`~django.core.mail.EmailMessage` object::
|
||||
..
|
||||
.. from django.core.mail import EmailMessage
|
||||
..
|
||||
.. msg = EmailMessage(subject="Shipped!", from_email="store@example.com",
|
||||
.. to=["customer@example.com", "accounting@example.com"])
|
||||
.. msg.template_name = "SHIPPING_NOTICE" # A Mandrill template name
|
||||
.. msg.template_content = { # Content blocks to fill in
|
||||
.. 'TRACKING_BLOCK': "<a href='.../*|TRACKINGNO|*'>track it</a>"
|
||||
.. }
|
||||
.. msg.global_merge_vars = { # Merge tags in your template
|
||||
.. 'ORDERNO': "12345", 'TRACKINGNO': "1Z987"
|
||||
.. }
|
||||
.. msg.merge_vars = { # Per-recipient merge tags
|
||||
.. 'accounting@example.com': {'NAME': "Pat"},
|
||||
.. 'customer@example.com': {'NAME': "Kim"}
|
||||
.. }
|
||||
.. msg.send()
|
||||
..
|
||||
.. If :attr:`template_name` is set, Djrill will use Mandrill's
|
||||
.. `messages/send-template API <https://mandrillapp.com/api/docs/messages.html#method=send-template>`_,
|
||||
.. and will ignore any `body` text set on the `EmailMessage`.
|
||||
..
|
||||
.. All of Djrill's other :ref:`Mandrill-specific options <anymail-send-features>`
|
||||
.. can be used with templates.
|
||||
.. _esp-stored-templates:
|
||||
|
||||
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 :attr:`~AnymailMessage.template_id`
|
||||
on an :class:`~django.core.mail.EmailMessage`.
|
||||
|
||||
.. attribute:: AnymailMessage.template_id
|
||||
|
||||
(not yet implemented)
|
||||
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 :ref:`specific ESP <supported-esps>`.)
|
||||
|
||||
.. attribute:: AnymailMessage.global_merge_vars
|
||||
.. code-block:: python
|
||||
|
||||
(not yet implemented)
|
||||
message.template_id = "after_sale_followup_offer"
|
||||
|
||||
.. ``dict``: merge variables to use for all recipients (most useful with :ref:`mandrill-templates`). ::
|
||||
..
|
||||
.. message.global_merge_vars = {'company': "ACME", 'offer': "10% off"}
|
||||
..
|
||||
.. Merge data must be strings or other JSON-serializable types.
|
||||
.. (See :ref:`formatting-merge-data` for details.)
|
||||
With most ESPs, using a stored template will ignore any
|
||||
body (plain-text or HTML) from the :class:`~django.core.mail.EmailMessage`
|
||||
object.
|
||||
|
||||
.. attribute:: AnymailMessage.merge_vars
|
||||
A few ESPs also allow you to define the message's subject as part of the template,
|
||||
but any subject you set on the :class:`~django.core.mail.EmailMessage`
|
||||
will override the template subject. To use the subject stored with the ESP template,
|
||||
set the message's `subject` to `None`:
|
||||
|
||||
(not yet implemented)
|
||||
.. code-block:: python
|
||||
|
||||
.. ``dict``: per-recipient merge variables (most useful with :ref:`mandrill-templates`). The keys
|
||||
.. in the dict are the recipient email addresses, and the values are dicts of merge vars for
|
||||
.. each recipient::
|
||||
..
|
||||
.. message.merge_vars = {
|
||||
.. 'wiley@example.com': {'offer': "15% off anvils"},
|
||||
.. 'rr@example.com': {'offer': "instant tunnel paint"}
|
||||
.. }
|
||||
..
|
||||
.. Merge data must be strings or other JSON-serializable types.
|
||||
.. (See :ref:`formatting-merge-data` for details.)
|
||||
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
|
||||
:class:`~django.core.mail.EmailMessage` object; passing
|
||||
`from_email=None` to the constructor will use Django's
|
||||
:setting:`DEFAULT_FROM_EMAIL` setting, overriding your template value.)
|
||||
|
||||
|
||||
.. _batch-send:
|
||||
.. _merge-data:
|
||||
|
||||
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 :ref:`ESP stored template <esp-stored-templates>`
|
||||
referenced by :attr:`~AnymailMessage.template_id`,
|
||||
or with some ESPs you can use merge fields directly in your
|
||||
:class:`~django.core.mail.EmailMessage` (meaning the message itself
|
||||
is treated as an on-the-fly template).
|
||||
|
||||
* Set the message's :attr:`~AnymailMessage.merge_data` attribute to define merge field
|
||||
substitutions for each recipient, and optionally set :attr:`~AnymailMessage.merge_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 :attr:`~AnymailMessage.merge_data` 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
|
||||
:ref:`your ESP <supported-esps>`, and remember you'll need to change
|
||||
the template if you later switch ESPs.)
|
||||
|
||||
|
||||
.. attribute:: 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 a `dict` of merge field
|
||||
names and values to use for that recipient:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
message.merge_data = {
|
||||
'wile@example.com': {'NAME': "Wile E.",
|
||||
'OFFER': "15% off anvils"},
|
||||
'rr@example.com': {'NAME': "Mr. Runner",
|
||||
'OFFER': "instant tunnel paint"},
|
||||
}
|
||||
|
||||
When `merge_data` is set, Anymail will use the ESP's batch sending option,
|
||||
so that each `to` recipient gets an individual message (and doesn't see the
|
||||
other emails on the `to` list).
|
||||
|
||||
.. attribute:: 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:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
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 :ref:`formatting-merge-data` for more information.
|
||||
|
||||
Like all :ref:`anymail-send-features`, you can use these extended template and
|
||||
merge attributes with any :class:`~django.core.mail.EmailMessage` or subclass object.
|
||||
(It doesn't have to be an :class:`AnymailMessage`.)
|
||||
|
||||
Tip: you can add :attr:`~!AnymailMessage.merge_global_data` to your
|
||||
global Anymail :ref:`send defaults <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 :attr:`~!AnymailMessage.merge_global_data`.
|
||||
|
||||
|
||||
.. _formatting-merge-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::
|
||||
than strings and integers, you'll need to format them into strings
|
||||
for use as merge data:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
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" exceptions:
|
||||
msg.global_merge_vars = {
|
||||
# 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:
|
||||
msg.global_merge_vars = {
|
||||
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"
|
||||
@@ -123,32 +219,38 @@ you'll need to format them into strings for use as merge data::
|
||||
These are just examples. You'll need to determine the best way to format
|
||||
your merge data as strings.
|
||||
|
||||
Although floats are allowed in merge vars, you'll generally want to format them
|
||||
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 :exc:`~anymail.exceptions.AnymailSerializationError` if you attempt
|
||||
to send a message with non-json-serializable data.
|
||||
to send a message with merge data (or metadata) that can't be sent to your ESP.
|
||||
|
||||
|
||||
.. How To Use Default Mandrill Subject and From fields
|
||||
.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
..
|
||||
.. To use default Mandrill "subject" or "from" field from your template definition
|
||||
.. (overriding your EmailMessage and Django defaults), set the following attrs:
|
||||
.. :attr:`use_template_subject` and/or :attr:`use_template_from` on
|
||||
.. your :class:`~django.core.mail.EmailMessage` object::
|
||||
..
|
||||
.. msg.use_template_subject = True
|
||||
.. msg.use_template_from = True
|
||||
.. msg.send()
|
||||
..
|
||||
.. .. attribute:: use_template_subject
|
||||
..
|
||||
.. If `True`, Djrill will omit the subject, and Mandrill will
|
||||
.. use the default subject from the template.
|
||||
..
|
||||
.. .. attribute:: use_template_from
|
||||
..
|
||||
.. If `True`, Djrill will omit the "from" field, and Mandrill will
|
||||
.. use the default "from" from the template.
|
||||
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
|
||||
:ref:`using Django templates <django-templates>` can be a more
|
||||
portable and maintainable approach for building transactional email.
|
||||
|
||||
@@ -27,14 +27,14 @@ Example that builds an email from the templates ``message_subject.txt``,
|
||||
from django.template import Context
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
template_data = {
|
||||
merge_data = {
|
||||
'ORDERNO': "12345", 'TRACKINGNO': "1Z987"
|
||||
}
|
||||
|
||||
plaintext_context = Context(autoescape=False) # HTML escaping not appropriate in plaintext
|
||||
subject = render_to_string("message_subject.txt", template_data, plaintext_context)
|
||||
text_body = render_to_string("message_body.txt", template_data, plaintext_context)
|
||||
html_body = render_to_string("message_body.html", template_data)
|
||||
subject = render_to_string("message_subject.txt", merge_data, plaintext_context)
|
||||
text_body = render_to_string("message_body.txt", merge_data, plaintext_context)
|
||||
html_body = render_to_string("message_body.html", merge_data)
|
||||
|
||||
msg = EmailMultiAlternatives(subject=subject, from_email="store@example.com",
|
||||
to=["customer@example.com"], body=text_body)
|
||||
|
||||
Reference in New Issue
Block a user