mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 03:41:05 -05:00
@@ -13,6 +13,7 @@ and notes about any quirks or limitations:
|
||||
:maxdepth: 1
|
||||
|
||||
amazon_ses
|
||||
mailersend
|
||||
mailgun
|
||||
mailjet
|
||||
mandrill
|
||||
@@ -32,35 +33,35 @@ The table below summarizes the Anymail features supported for each ESP.
|
||||
|
||||
.. rst-class:: sticky-left
|
||||
|
||||
============================================ ============ =========== ========== =========== ========== ========== ========== ============ ===========
|
||||
Email Service Provider |Amazon SES| |Mailgun| |Mailjet| |Mandrill| |Postal| |Postmark| |SendGrid| |Sendinblue| |SparkPost|
|
||||
============================================ ============ =========== ========== =========== ========== ========== ========== ============ ===========
|
||||
============================================ ============ ============ =========== ========== =========== ========== ========== ========== ============ ===========
|
||||
Email Service Provider |Amazon SES| |MailerSend| |Mailgun| |Mailjet| |Mandrill| |Postal| |Postmark| |SendGrid| |Sendinblue| |SparkPost|
|
||||
============================================ ============ ============ =========== ========== =========== ========== ========== ========== ============ ===========
|
||||
.. rubric:: :ref:`Anymail send options <anymail-send-options>`
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
:attr:`~AnymailMessage.envelope_sender` Yes Domain only Yes Domain only Yes No No No Yes
|
||||
:attr:`~AnymailMessage.metadata` Yes Yes Yes Yes No Yes Yes Yes Yes
|
||||
:attr:`~AnymailMessage.merge_metadata` No Yes Yes Yes No Yes Yes No Yes
|
||||
:attr:`~AnymailMessage.send_at` No Yes No Yes No No Yes Yes Yes
|
||||
:attr:`~AnymailMessage.tags` Yes Yes Max 1 tag Yes Max 1 tag Max 1 tag Yes Yes Max 1 tag
|
||||
:attr:`~AnymailMessage.track_clicks` No Yes Yes Yes No Yes Yes No Yes
|
||||
:attr:`~AnymailMessage.track_opens` No Yes Yes Yes No Yes Yes No Yes
|
||||
:ref:`amp-email` Yes Yes No No No No Yes No Yes
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
:attr:`~AnymailMessage.envelope_sender` Yes No Domain only Yes Domain only Yes No No No Yes
|
||||
:attr:`~AnymailMessage.metadata` Yes No Yes Yes Yes No Yes Yes Yes Yes
|
||||
:attr:`~AnymailMessage.merge_metadata` No No Yes Yes Yes No Yes Yes No Yes
|
||||
:attr:`~AnymailMessage.send_at` No Yes Yes No Yes No No Yes Yes Yes
|
||||
:attr:`~AnymailMessage.tags` Yes Yes Yes Max 1 tag Yes Max 1 tag Max 1 tag Yes Yes Max 1 tag
|
||||
:attr:`~AnymailMessage.track_clicks` No Yes Yes Yes Yes No Yes Yes No Yes
|
||||
:attr:`~AnymailMessage.track_opens` No Yes Yes Yes Yes No Yes Yes No Yes
|
||||
:ref:`amp-email` Yes No Yes No No No No Yes No Yes
|
||||
|
||||
.. rubric:: :ref:`templates-and-merge`
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
:attr:`~AnymailMessage.template_id` Yes Yes Yes Yes No Yes Yes Yes Yes
|
||||
:attr:`~AnymailMessage.merge_data` Yes Yes Yes Yes No Yes Yes No Yes
|
||||
:attr:`~AnymailMessage.merge_global_data` Yes (emulated) Yes Yes No Yes Yes Yes Yes
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
:attr:`~AnymailMessage.template_id` Yes Yes Yes Yes Yes No Yes Yes Yes Yes
|
||||
:attr:`~AnymailMessage.merge_data` Yes Yes Yes Yes Yes No Yes Yes No Yes
|
||||
:attr:`~AnymailMessage.merge_global_data` Yes (emulated) (emulated) Yes Yes No Yes Yes Yes Yes
|
||||
|
||||
.. rubric:: :ref:`Status <esp-send-status>` and :ref:`event tracking <event-tracking>`
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
:attr:`~AnymailMessage.anymail_status` Yes Yes Yes Yes Yes Yes Yes Yes Yes
|
||||
|AnymailTrackingEvent| from webhooks Yes Yes Yes Yes Yes Yes Yes Yes Yes
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
:attr:`~AnymailMessage.anymail_status` Yes Yes Yes Yes Yes Yes Yes Yes Yes Yes
|
||||
|AnymailTrackingEvent| from webhooks Yes Yes Yes Yes Yes Yes Yes Yes Yes Yes
|
||||
|
||||
.. rubric:: :ref:`Inbound handling <inbound>`
|
||||
---------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|AnymailInboundEvent| from webhooks Yes Yes Yes Yes Yes Yes Yes No Yes
|
||||
============================================ ============ =========== ========== =========== ========== ========== ========== ============ ===========
|
||||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|AnymailInboundEvent| from webhooks Yes Yes Yes Yes Yes Yes Yes Yes No Yes
|
||||
============================================ ============ ============ =========== ========== =========== ========== ========== ========== ============ ===========
|
||||
|
||||
|
||||
Trying to choose an ESP? Please **don't** start with this table. It's far more
|
||||
@@ -69,6 +70,7 @@ and support for developers. The *number* of extra features an ESP offers is almo
|
||||
meaningless. (And even specific features don't matter if you don't plan to use them.)
|
||||
|
||||
.. |Amazon SES| replace:: :ref:`amazon-ses-backend`
|
||||
.. |MailerSend| replace:: :ref:`mailersend-backend`
|
||||
.. |Mailgun| replace:: :ref:`mailgun-backend`
|
||||
.. |Mailjet| replace:: :ref:`mailjet-backend`
|
||||
.. |Mandrill| replace:: :ref:`mandrill-backend`
|
||||
|
||||
546
docs/esps/mailersend.rst
Normal file
546
docs/esps/mailersend.rst
Normal file
@@ -0,0 +1,546 @@
|
||||
.. _mailersend-backend:
|
||||
|
||||
MailerSend
|
||||
==========
|
||||
|
||||
Anymail integrates Django with the `MailerSend`_ transactional
|
||||
email service, using their `email API`_ endpoint.
|
||||
|
||||
.. _MailerSend: https://www.mailersend.com/
|
||||
.. _email API: https://developers.mailersend.com/api/v1/email.html
|
||||
|
||||
|
||||
Settings
|
||||
--------
|
||||
|
||||
.. rubric:: EMAIL_BACKEND
|
||||
|
||||
To use Anymail's MailerSend backend, set:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
EMAIL_BACKEND = "anymail.backends.mailersend.EmailBackend"
|
||||
|
||||
in your settings.py.
|
||||
|
||||
|
||||
.. setting:: ANYMAIL_MAILERSEND_API_TOKEN
|
||||
|
||||
.. rubric:: MAILERSEND_API_TOKEN
|
||||
|
||||
Required for sending. A MailerSend API token generated in your MailerSend
|
||||
`Email domains settings`_. For the token permission level, "custom access"
|
||||
is recommended, with full access to email and *no access* for all other features.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ANYMAIL = {
|
||||
...
|
||||
"MAILERSEND_API_TOKEN": "<your API token>",
|
||||
}
|
||||
|
||||
Anymail will also look for ``MAILERSEND_API_TOKEN`` at the
|
||||
root of the settings file if neither ``ANYMAIL["MAILERSEND_API_TOKEN"]``
|
||||
nor ``ANYMAIL_MAILERSEND_API_TOKEN`` is set.
|
||||
|
||||
If your Django project sends email from multiple MailerSend domains,
|
||||
you will need a separate API token for each domain. Use the token matching
|
||||
your :setting:`DEFAULT_FROM_EMAIL` domain in settings.py, and then override
|
||||
where necessary for individual emails by setting ``"api_token"`` in the
|
||||
message's :ref:`esp_extra <mailersend-esp-extra>`. (You could centralize
|
||||
this logic using Anymail's :ref:`pre-send-signal`.)
|
||||
|
||||
|
||||
.. setting:: ANYMAIL_MAILERSEND_BATCH_SEND_MODE
|
||||
|
||||
.. rubric:: MAILERSEND_BATCH_SEND_MODE
|
||||
|
||||
If you are using Anymail's :attr:`~anymail.message.AnymailMessage.merge_data`
|
||||
with multiple recipients (":ref:`batch sending <batch-send>`"), set this to
|
||||
indicate how to handle the batch. See :ref:`mailersend-batch-send` below
|
||||
for more information.
|
||||
|
||||
Choices are ``"use-bulk-email"`` or ``"expose-to-list"``. The default ``None``
|
||||
will raise an error if :attr:`~!anymail.message.AnymailMessage.merge_data` is
|
||||
used with more than one ``to`` recipient.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ANYMAIL = {
|
||||
...
|
||||
"MAILERSEND_BATCH_SEND_MODE": "use-bulk-email",
|
||||
}
|
||||
|
||||
|
||||
.. setting:: ANYMAIL_MAILERSEND_SIGNING_SECRET
|
||||
|
||||
.. rubric:: MAILERSEND_SIGNING_SECRET
|
||||
|
||||
The MailerSend webhook signing secret needed to verify webhook posts.
|
||||
Required if you are using activity tracking, otherwise not necessary.
|
||||
(This is separate from Anymail's
|
||||
:setting:`WEBHOOK_SECRET <ANYMAIL_WEBHOOK_SECRET>` setting.)
|
||||
|
||||
Find this in your MailerSend `Email domains settings`_: after adding
|
||||
a webhook, look for the "signing secret" on the webhook's management page.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ANYMAIL = {
|
||||
...
|
||||
"MAILERSEND_SIGNING_SECRET": "<secret from webhook management page>",
|
||||
}
|
||||
|
||||
MailerSend generates a unique secret for each webhook; if you edit
|
||||
your webhook you will need to update this setting with the new signing secret.
|
||||
(Also, inbound routes use a *different* secret, with a different setting---see
|
||||
below.)
|
||||
|
||||
|
||||
.. setting:: ANYMAIL_MAILERSEND_INBOUND_SECRET
|
||||
|
||||
.. rubric:: MAILERSEND_INBOUND_SECRET
|
||||
|
||||
The MailerSend inbound route secret needed to verify inbound notifications.
|
||||
Required if you are using inbound routing, otherwise not necessary.
|
||||
|
||||
Find this in your MailerSend `Email domains settings`_: after you have
|
||||
added an inbound route, look for the "secret" immediately below the route url
|
||||
on the management page for that inbound route.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ANYMAIL = {
|
||||
...
|
||||
"MAILERSEND_INBOUND_SECRET": "<secret from inbound management page>",
|
||||
}
|
||||
|
||||
MailerSend generates a unique secret for each inbound route url; if you edit
|
||||
your route you will need to update this setting with the new secret.
|
||||
(Also, activity tracking webhooks use a *different* secret, with a different
|
||||
setting---see above.)
|
||||
|
||||
|
||||
.. setting:: ANYMAIL_MAILERSEND_API_URL
|
||||
|
||||
.. rubric:: MAILERSEND_API_URL
|
||||
|
||||
The base url for calling the MailerSend API.
|
||||
|
||||
The default is ``MAILERSEND_API_URL = "https://api.mailersend.com/v1/"``.
|
||||
(It's unlikely you would need to change this.)
|
||||
|
||||
|
||||
.. _Email domains settings: https://app.mailersend.com/domains
|
||||
|
||||
|
||||
.. _mailersend-esp-extra:
|
||||
|
||||
exp_extra support
|
||||
-----------------
|
||||
|
||||
Anymail's MailerSend backend will pass :attr:`~anymail.message.AnymailMessage.esp_extra`
|
||||
values directly to MailerSend's `email API`_.
|
||||
|
||||
In addition, you can override the
|
||||
:setting:`MAILERSEND_API_TOKEN <ANYMAIL_MAILERSEND_API_TOKEN>` for an individual
|
||||
message by providing ``"api-token"``, and
|
||||
:setting:`MAILERSEND_BATCH_SEND_MODE <ANYMAIL_MAILERSEND_BATCH_SEND_MODE>`
|
||||
by providing ``"batch-send-mode"`` in the
|
||||
:attr:`~!anymail.message.AnymailMessage.esp_extra` dict.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
message = AnymailMessage(...)
|
||||
message.esp_extra = {
|
||||
# override your MailerSend domain's content tracking default:
|
||||
"settings": {"track_content": False},
|
||||
|
||||
# use a different MAILERSEND_API_TOKEN for this message:
|
||||
"api_token": MAILERSEND_API_TOKEN_FOR_MARKETING_DOMAIN,
|
||||
|
||||
# override the MAILERSEND_BATCH_SEND_MODE setting
|
||||
# just for this message:
|
||||
"batch_send_mode": "use-bulk-email",
|
||||
}
|
||||
|
||||
Nested values are merged deeply. When sending using MailerSend's bulk-email API
|
||||
endpoint, the :attr:`~!anymail.message.AnymailMessage.esp_extra` params are merged
|
||||
into the payload for every individual message in the batch.
|
||||
|
||||
|
||||
.. _mailersend-quirks:
|
||||
|
||||
Limitations and quirks
|
||||
----------------------
|
||||
|
||||
MailerSend does not support a few features offered by some other ESPs.
|
||||
|
||||
Anymail normally raises an :exc:`~anymail.exceptions.AnymailUnsupportedFeature`
|
||||
error when you try to send a message using features that MailerSend doesn't support
|
||||
You can tell Anymail to suppress these errors and send the messages anyway --
|
||||
see :ref:`unsupported-features`.
|
||||
|
||||
**Attachments require filenames, ignore content type**
|
||||
MailerSend requires every attachment (even inline ones) to have a filename.
|
||||
And it determines the content type of the attachment from the filename extension.
|
||||
|
||||
If you try to send an attachment without a filename, Anymail will substitute
|
||||
"attachment*.ext*" using an appropriate *.ext* for the content type.
|
||||
|
||||
If you try to send an attachment whose content type doesn't match its filename
|
||||
extension, MailerSend will change the content type to match the extension.
|
||||
(E.g., the filename "data.txt" will always be sent as "text/plain",
|
||||
even if you specified a "text/csv" content type.)
|
||||
|
||||
**Single Reply-To**
|
||||
MailerSend only supports a single Reply-To address.
|
||||
|
||||
If your message has multiple reply addresses, you'll get an
|
||||
:exc:`~anymail.exceptions.AnymailUnsupportedFeature` error---or
|
||||
if you've enabled :setting:`ANYMAIL_IGNORE_UNSUPPORTED_FEATURES`,
|
||||
Anymail will use only the first one.
|
||||
|
||||
**Limited extra headers**
|
||||
MailerSend does not allow most extra headers. There are two exceptions:
|
||||
|
||||
* You can include :mailheader:`In-Reply-To` in extra headers, set to
|
||||
a message-id (without the angle brackets).
|
||||
|
||||
* You can include :mailheader:`Precedence` in extra headers to override
|
||||
the "Add precedence bulk header" option from your MailerSend domain
|
||||
advanced settings (look under "More settings").
|
||||
Anymail will set MailerSend's ``precedence_bulk`` param to ``true``
|
||||
if your extra headers have :mailheader:`Precedence` set to ``"bulk"`` or
|
||||
``"list"`` or ``"junk"``, or ``false`` for any other value.
|
||||
|
||||
Any other extra headers will raise an
|
||||
:exc:`~anymail.exceptions.AnymailUnsupportedFeature` error.
|
||||
|
||||
**No metadata support**
|
||||
MailerSend does not support Anymail's
|
||||
:attr:`~anymail.message.AnymailMessage.metadata` or
|
||||
:attr:`~anymail.message.AnymailMessage.merge_metadata` features.
|
||||
|
||||
**No envelope sender overrides**
|
||||
MailerSend does not support overriding
|
||||
:attr:`~anymail.message.AnymailMessage.envelope_sender` on individual messages.
|
||||
(To use a `MailerSend sender identity`_, set the verified identity's
|
||||
email address as the message's
|
||||
:attr:`~!django.core.email.message.EmailMessage.from_email`.)
|
||||
|
||||
.. _MailerSend sender identity:
|
||||
https://www.mailersend.com/help/send-email-on-behalf-of-clients
|
||||
|
||||
**API rate limits**
|
||||
MailerSend provides `rate limit headers`_ with each API call response.
|
||||
To access them after a successful send, use (e.g.,)
|
||||
``message.anymail_status.esp_response.headers["x-ratelimit-remaining"]``.
|
||||
|
||||
If you exceed a rate limit, you'll get an :exc:`~anymail.exceptions.AnymailAPIError`
|
||||
with ``error.status_code == 429``, and can determine how many seconds to wait
|
||||
from ``error.response.headers["retry-after"]``.
|
||||
|
||||
.. _rate limit headers:
|
||||
https://developers.mailersend.com/general.html#rate-limits
|
||||
|
||||
|
||||
|
||||
.. _mailersend-templates:
|
||||
|
||||
Batch sending/merge and ESP templates
|
||||
-------------------------------------
|
||||
|
||||
MailerSend supports :ref:`ESP stored templates <esp-stored-templates>`, on-the-fly
|
||||
templating, and :ref:`batch sending <batch-send>` with per-recipient merge data.
|
||||
MailerSend's approaches to batch sending don't align perfectly with Anymail's;
|
||||
be sure to read :ref:`mailersend-batch-send` below to understand the options.
|
||||
|
||||
MailerSend offers two different syntaxes for substituting data into templates:
|
||||
"`simple personalization`_" and "`advanced personalization`_." Anymail supports
|
||||
*only* the more flexible advanced personalization syntax. If you have MailerSend
|
||||
templates using the "simple" syntax (``{$variable_name}``), you'll need to convert
|
||||
them to the "advanced" syntax (``{{ variable_name }}``) for use with Anymail's
|
||||
:attr:`~anymail.message.AnymailMessage.merge_data` and
|
||||
:attr:`~anymail.message.AnymailMessage.merge_global_data`.
|
||||
|
||||
Here's an example defining an on-the-fly template that uses MailerSend advanced
|
||||
personalization variables:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
message = EmailMessage(
|
||||
from_email="shipping@example.com",
|
||||
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 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
|
||||
}
|
||||
# (see discussion of batch-send-mode below)
|
||||
message.esp_extra = {
|
||||
"batch-send-mode": "use-bulk-email"
|
||||
}
|
||||
|
||||
To send the same message with a `MailerSend stored template`_ from your account,
|
||||
set :attr:`~anymail.message.AnymailMessage.template_id`, and omit any plain-text
|
||||
or html `~!django.core.mail.EmailMessage.body`. If you've set a subject in your
|
||||
MailerSend template's default settings, you can omit
|
||||
`~!django.core.mail.EmailMessage.subject` (otherwise you must include it).
|
||||
And if your template default settings specify the *From* email, that will override
|
||||
`~!django.core.mail.EmailMessage.from_email`. Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
message = EmailMessage(
|
||||
from_email="shipping@example.com",
|
||||
# (subject and body from template)
|
||||
to=["alice@example.com", "Bob <bob@example.com>"]
|
||||
)
|
||||
message.template_id = "vzq12345678" # id of template in our account
|
||||
# ... set merge_data and merge_global_data as above
|
||||
|
||||
MailerSend does not natively support global merge data. Anymail emulates
|
||||
the capability by copying any :attr:`~anymail.message.AnymailMessage.merge_global_data`
|
||||
values to every recipient.
|
||||
|
||||
.. _simple personalization:
|
||||
https://www.mailersend.com/help/how-to-use-variables#simple-personalization
|
||||
.. _advanced personalization:
|
||||
https://www.mailersend.com/help/how-to-use-variables#advanced-personalization
|
||||
.. _MailerSend stored template:
|
||||
https://www.mailersend.com/help/how-to-create-a-template
|
||||
|
||||
|
||||
.. _mailersend-batch-send:
|
||||
|
||||
Batch send mode
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Anymail's model for :ref:`batch sending <batch-send>` is that each recipient
|
||||
receives a separate email personalized for them, and that each recipient sees
|
||||
*only their own email address* in the message's :mailheader:`To` header.
|
||||
|
||||
MailerSend has a `bulk-email API`_ that matches Anymail's batch sending model,
|
||||
but operates completely asynchronously, which can complicate status tracking
|
||||
and error handling.
|
||||
|
||||
MailerSend also supports batch sending personalized emails through
|
||||
its regular `email API`_, which avoids the bulk-email limitations but
|
||||
exposes the entire :mailheader:`To` list to all recipients.
|
||||
|
||||
If you want to use Anymail's :attr:`~anymail.message.AnymailMessage.merge_data`
|
||||
for batch sending to multiple `~!django.core.mail.EmailMessage.to` recipients,
|
||||
you must select one of these two approaches by specifying either ``"use-bulk-email"``
|
||||
or ``"expose-to-list"`` in your Anymail
|
||||
:setting:`MAILERSEND_BATCH_SEND_MODE <ANYMAIL_MAILERSEND_BATCH_SEND_MODE>` setting---or
|
||||
as ``"batch-send-mode"`` in the message's :ref:`esp_extra <mailersend-esp-extra>`.
|
||||
|
||||
.. caution::
|
||||
|
||||
Using the ``"expose-to-list"`` MailerSend batch send mode will reveal
|
||||
*all* of the message's :mailheader:`To` email addresses to *every*
|
||||
recipient of the message.
|
||||
|
||||
If you use the ``"use-bulk-email"`` MailerSend batch send mode:
|
||||
|
||||
* The
|
||||
:attr:`message.anymail_status.status <anymail.message.AnymailStatus.status>`
|
||||
will be ``{"unknown"}``, because MailerSend detects errors and rejected
|
||||
recipients at a later time.
|
||||
|
||||
* The
|
||||
:attr:`message.anymail_status.message_id <anymail.message.AnymailStatus.message_id>`
|
||||
will be a MailerSend ``bulk_email_id``, prefixed with ``"bulk:"`` to
|
||||
distinguish it from a regular ``message_id``.
|
||||
|
||||
* You will need to poll MailerSend's `bulk-email status API`_ to determine
|
||||
whether the send was successful, partially successful, or failed,
|
||||
and to determine the
|
||||
:attr:`event.message_id <anymail.signals.AnymailTrackingEvent.message_id>`
|
||||
that will be sent to status tracking webhooks.
|
||||
|
||||
* Be aware that rate limits for the bulk-email API are significantly lower
|
||||
than MailerSend's regular email API.
|
||||
|
||||
Rather than one of these batch sending options, an often-simpler approach is
|
||||
to loop over your recipient list and send a separate message for each.
|
||||
You can still use templates and :attr:`~!anymail.message.AnymailMessage.merge_data`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# How to "manually" send a batch of emails to one recipient at a time.
|
||||
# (There's no need to specify a MailerSend "batch-send-mode".)
|
||||
to_list = ["alice@example.com", "bob@example.com"]
|
||||
merge_data = {
|
||||
"alice@example.com": {"name": "Alice", "order_no": "12345"},
|
||||
"bob@example.com": {"name": "Bob", "order_no": "54321"},
|
||||
}
|
||||
merge_global_data = {
|
||||
"ship_date": "May 15",
|
||||
}
|
||||
for to_email in to_list:
|
||||
message = AnymailMessage(
|
||||
# just one recipient per message:
|
||||
to=[to_email],
|
||||
# provide template variables for this one recipient:
|
||||
merge_global_data = merge_global_data | merge_data[to_email],
|
||||
# any other attributes you want:
|
||||
template_id = "vzq12345678",
|
||||
from_email="shipping@example.com",
|
||||
)
|
||||
try:
|
||||
message.send()
|
||||
except AnymailAPIError:
|
||||
# Handle error -- e.g., schedule for retry later.
|
||||
else:
|
||||
# Either successful send or to_email is rejected.
|
||||
# message.anymail_status will be {"queued"} or {"rejected"}.
|
||||
# message.anymail_status.message_id can be stored to match
|
||||
# with event.message_id in a status tracking signal receiver.
|
||||
|
||||
|
||||
.. _bulk-email API:
|
||||
https://developers.mailersend.com/api/v1/email.html#send-bulk-emails
|
||||
.. _bulk-email status API:
|
||||
https://developers.mailersend.com/api/v1/email.html#get-bulk-email-status
|
||||
|
||||
|
||||
.. _mailersend-webhooks:
|
||||
|
||||
Status tracking webhooks
|
||||
------------------------
|
||||
|
||||
If you are using Anymail's normalized :ref:`status tracking <event-tracking>`,
|
||||
follow MailerSend's instructions to `add a webhook to your domain`_.
|
||||
|
||||
* Enter this Anymail tracking URL as the webhook's "Endpoint URL"
|
||||
(where *yoursite.example.com* is your Django site):
|
||||
|
||||
:samp:`https://{yoursite.example.com}/anymail/mailersend/tracking/`
|
||||
|
||||
Because MailerSend implements webhook signing, it's not necessary to use Anymail's
|
||||
shared webhook secret for security with MailerSend webhooks. However, it doesn't
|
||||
hurt to use both. If you *have* set an Anymail
|
||||
:setting:`WEBHOOK_SECRET <ANYMAIL_WEBHOOK_SECRET>`, include that *random:random*
|
||||
shared secret in the webhook endpoint URL:
|
||||
|
||||
:samp:`https://{random}:{random}@{yoursite.example.com}/anymail/mailersend/tracking/`
|
||||
|
||||
* For "Events to send", select any or all events you want to track.
|
||||
|
||||
* After you have saved the webhook, go back into MailerSend's webhook
|
||||
management page, and reveal and copy the MailerSend "webhook signing secret".
|
||||
Provide that in your settings.py ``ANYMAIL`` settings as
|
||||
:setting:`MAILERSEND_SIGNING_SECRET <ANYMAIL_MAILERSEND_SIGNING_SECRET>`
|
||||
so that Anymail can verify calls to the webhook:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ANYMAIL = {
|
||||
# ...
|
||||
MAILERSEND_SIGNING_SECRET = "<secret you copied>"
|
||||
}
|
||||
|
||||
For troubleshooting, MailerSend provides a helpful log of calls to the webhook.
|
||||
See "`About webhook attempts`_" in their documentation for more details.
|
||||
|
||||
.. note::
|
||||
|
||||
MailerSend has a relatively short three second timeout for webhook calls.
|
||||
Be sure to avoid any lengthy operations in your Anymail tracking signal
|
||||
receiver function, or MailerSend will consider the notification failed
|
||||
at retry it. The event's :attr:`~anymail.signals.AnymailTrackingEvent.event_id`
|
||||
field can help identify duplicate notifications.
|
||||
|
||||
MailerSend retries webhook notifications only twice, with delays of 10
|
||||
and then 100 seconds. If your webhook is ever offline for more than
|
||||
a couple minutes, you many miss some tracking events. You can use
|
||||
MailerSend's activity API to query for events that may have been missed.
|
||||
|
||||
MailerSend will report these Anymail
|
||||
:attr:`~anymail.signals.AnymailTrackingEvent.event_type`\s:
|
||||
sent, delivered, bounced, complained, unsubscribed, opened, and clicked.
|
||||
|
||||
The event's :attr:`~anymail.signals.AnymailTrackingEvent.esp_event` field will be
|
||||
the *complete* parsed MailerSend webhook payload, including an additional wrapper
|
||||
object not shown in their documentation. The activity data in MailerSend's
|
||||
`webhook payload example`_ is available as ``event.esp_event["data"]``.
|
||||
|
||||
.. _add a webhook to your domain:
|
||||
https://www.mailersend.com/help/webhooks#adding-webhooks
|
||||
.. _About webhook attempts:
|
||||
https://www.mailersend.com/help/webhooks#webhook-attempts
|
||||
.. _webhook payload example:
|
||||
https://developers.mailersend.com/api/v1/webhooks.html#payload-example
|
||||
|
||||
|
||||
.. _mailersend-inbound:
|
||||
|
||||
Inbound routing
|
||||
---------------
|
||||
|
||||
If you want to receive email from MailerSend through Anymail's normalized
|
||||
:ref:`inbound <inbound>` handling, follow MailerSend's guide to
|
||||
`How to set up an inbound route`_.
|
||||
|
||||
* For "Route to" (in their step 8), enter this Anymail inbound route endpoint URL
|
||||
(where *yoursite.example.com* is your Django site):
|
||||
|
||||
:samp:`https://{yoursite.example.com}/anymail/mailersend/inbound/`
|
||||
|
||||
Because MailerSend signs its inbound notifications, it's not necessary to use Anymail's
|
||||
shared webhook secret for security with MailerSend inbound routing. However, it doesn't
|
||||
hurt to use both. If you *have* set an Anymail
|
||||
:setting:`WEBHOOK_SECRET <ANYMAIL_WEBHOOK_SECRET>`, include that *random:random*
|
||||
shared secret in the inbound route endpoint URL:
|
||||
|
||||
:samp:`https://{random}:{random}@{yoursite.example.com}/anymail/mailersend/inbound/`
|
||||
|
||||
* After you have saved the inbound route, go back into MailerSend's inbound route
|
||||
management page, and copy the "Secret" displayed immediately below the "Route to" URL.
|
||||
Provide that in your settings.py ``ANYMAIL`` settings as
|
||||
:setting:`MAILERSEND_INBOUND_SECRET <ANYMAIL_MAILERSEND_INBOUND_SECRET>`
|
||||
so that Anymail can verify calls to the inbound endpoint:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ANYMAIL = {
|
||||
# ...
|
||||
MAILERSEND_INBOUND_SECRET = "<secret you copied>"
|
||||
}
|
||||
|
||||
Note that this is a *different* secret from the
|
||||
:setting:`MAILERSEND_SIGNING_SECRET <ANYMAIL_MAILERSEND_SIGNING_SECRET>`
|
||||
used to verify activity tracking webhooks. If you are using both features,
|
||||
be sure to include both settings.
|
||||
|
||||
For troubleshooting, MailerSend provides a helpful inbound activity log
|
||||
near the end of the route management page. See `Where to find inbound emails`_
|
||||
in their docs for more details.
|
||||
|
||||
.. note::
|
||||
|
||||
MailerSend imposes a three second limit on all notifications.
|
||||
If your inbound signal receiver function takes too long,
|
||||
MailerSend may think the notification failed. To avoid problems,
|
||||
it's essential you offload any lengthy operations to a background task.
|
||||
|
||||
MailerSend does not retry failed inbound notifications.
|
||||
If your Django app is ever unreachable for any reason,
|
||||
**you will miss inbound mail** that arrives during that time.
|
||||
|
||||
.. _How to set up an inbound route:
|
||||
https://www.mailersend.com/help/inbound-route
|
||||
.. _Where to find inbound emails:
|
||||
https://www.mailersend.com/help/inbound-route#where
|
||||
Reference in New Issue
Block a user