mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 11:51:05 -05:00
Use new SES v2 SendBulkEmail ReplacementHeaders param to support features that require custom headers, including `extra_headers`, `metadata`, `merge_metadata` and `tags`. Update integration tests and docs Closes #375
841 lines
36 KiB
ReStructuredText
841 lines
36 KiB
ReStructuredText
.. _amazon-ses-backend:
|
|
|
|
Amazon SES
|
|
==========
|
|
|
|
Anymail integrates with the `Amazon Simple Email Service`_ (SES) using the `Boto 3`_
|
|
AWS SDK for Python, and supports sending, tracking, and inbound receiving capabilities.
|
|
|
|
.. versionchanged:: 11.0
|
|
|
|
Anymail supports only the newer Amazon SES v2 API. (Anymail 10.x supported both
|
|
SES v1 and v2, and used v2 by default. Anymail 9.x and earlier used SES v1.)
|
|
See :ref:`amazon-ses-v2` below if you are upgrading from an earlier Anymail version.
|
|
|
|
|
|
.. sidebar:: Alternatives
|
|
|
|
At least two other packages offer Django integration with
|
|
Amazon SES: :pypi:`django-amazon-ses` and :pypi:`django-ses`.
|
|
Depending on your needs, one of them may be more appropriate than Anymail.
|
|
|
|
|
|
.. _Amazon Simple Email Service: https://aws.amazon.com/ses/
|
|
.. _Boto 3: https://boto3.readthedocs.io/en/stable/
|
|
|
|
|
|
Installation
|
|
------------
|
|
|
|
You must ensure the :pypi:`boto3` package is installed to use Anymail's Amazon SES
|
|
backend. Either include the ``amazon-ses`` option when you install Anymail:
|
|
|
|
.. code-block:: console
|
|
|
|
$ pip install "django-anymail[amazon-ses]"
|
|
|
|
or separately run ``pip install boto3``.
|
|
|
|
.. versionchanged:: 10.0
|
|
|
|
In earlier releases, the "extra name" could use an underscore
|
|
(``django-anymail[amazon_ses]``). That now causes pip to warn
|
|
that "django-anymail does not provide the extra 'amazon_ses',"
|
|
and may result in a broken installation that is missing boto3.
|
|
|
|
To send mail with Anymail's Amazon SES backend, set:
|
|
|
|
.. code-block:: python
|
|
|
|
EMAIL_BACKEND = "anymail.backends.amazon_ses.EmailBackend"
|
|
|
|
in your settings.py.
|
|
|
|
In addition, you must make sure boto3 is configured with AWS credentials having the
|
|
necessary :ref:`amazon-ses-iam-permissions`.
|
|
There are several ways to do this; see `Credentials`_ in the Boto docs for options.
|
|
Usually, an IAM role for EC2 instances, standard Boto environment variables,
|
|
or a shared AWS credentials file will be appropriate. For more complex cases,
|
|
use Anymail's :setting:`AMAZON_SES_CLIENT_PARAMS <ANYMAIL_AMAZON_SES_CLIENT_PARAMS>`
|
|
setting to customize the Boto session.
|
|
|
|
|
|
.. _Credentials: https://boto3.readthedocs.io/en/stable/guide/configuration.html#configuring-credentials
|
|
|
|
|
|
.. _amazon-ses-quirks:
|
|
|
|
Limitations and quirks
|
|
----------------------
|
|
|
|
.. versionchanged:: 11.0
|
|
|
|
Anymail's :attr:`~anymail.message.AnymailMessage.merge_metadata`
|
|
is now supported.
|
|
|
|
**Hard throttling**
|
|
Like most ESPs, Amazon SES `throttles sending`_ for new customers. But unlike
|
|
most ESPs, SES does not queue and slowly release throttled messages. Instead, it
|
|
hard-fails the send API call. A strategy for :ref:`retrying errors <transient-errors>`
|
|
is required with any ESP; you're likely to run into it right away with Amazon SES.
|
|
|
|
**Tags limitations**
|
|
Amazon SES's handling for tags is a bit different from other ESPs.
|
|
Anymail tries to provide a useful, portable default behavior for its
|
|
:attr:`~anymail.message.AnymailMessage.tags` feature. See :ref:`amazon-ses-tags`
|
|
below for more information and additional options.
|
|
|
|
**Open and click tracking overrides**
|
|
Anymail's :attr:`~anymail.message.AnymailMessage.track_opens` and
|
|
:attr:`~anymail.message.AnymailMessage.track_clicks` are not supported.
|
|
Although Amazon SES *does* support open and click tracking, it doesn't offer
|
|
a simple mechanism to override the settings for individual messages. If you
|
|
need this feature, provide a custom ``ConfigurationSetName`` in Anymail's
|
|
:ref:`esp_extra <amazon-ses-esp-extra>`.
|
|
|
|
**No delayed sending**
|
|
Amazon SES does not support :attr:`~anymail.message.AnymailMessage.send_at`.
|
|
|
|
**No global send defaults for non-Anymail options**
|
|
With the Amazon SES backend, Anymail's :ref:`global send defaults <send-defaults>`
|
|
are only supported for Anymail's added message options (like
|
|
:attr:`~anymail.message.AnymailMessage.metadata` and
|
|
:attr:`~anymail.message.AnymailMessage.esp_extra`), not for standard EmailMessage
|
|
attributes like `bcc` or `from_email`.
|
|
|
|
**Arbitrary alternative parts allowed**
|
|
Amazon SES is one of the few ESPs that *does* support sending arbitrary alternative
|
|
message parts (beyond just a single text/plain and text/html part).
|
|
|
|
**AMP for Email**
|
|
Amazon SES supports sending AMPHTML email content. To include it, use
|
|
``message.attach_alternative("...AMPHTML content...", "text/x-amp-html")``
|
|
(and be sure to also include regular HTML and text bodies, too).
|
|
|
|
**Envelope-sender is forwarded**
|
|
Anymail's :attr:`~anymail.message.AnymailMessage.envelope_sender` becomes
|
|
Amazon SES's ``FeedbackForwardingEmailAddress``. That address will receive bounce and other
|
|
delivery notifications, but will not appear in the message sent to the recipient.
|
|
SES always generates its own anonymized envelope sender (mailfrom) for each outgoing
|
|
message, and then forwards that address to your envelope-sender. See
|
|
`Email feedback forwarding destination`_ in the SES docs.
|
|
|
|
**Spoofed To header allowed**
|
|
Amazon SES is one of the few ESPs that supports spoofing the :mailheader:`To` header
|
|
(see :ref:`message-headers`). (But be aware that most ISPs consider this a strong spam
|
|
signal, and using it will likely prevent delivery of your email.)
|
|
|
|
**Template limitations**
|
|
Messages sent with templates have some additional limitations, such as not
|
|
supporting attachments. See :ref:`amazon-ses-templates` below.
|
|
|
|
|
|
.. _throttles sending:
|
|
https://docs.aws.amazon.com/ses/latest/DeveloperGuide/manage-sending-limits.html
|
|
|
|
.. _Email feedback forwarding destination:
|
|
https://docs.aws.amazon.com/ses/latest/dg/monitor-sending-activity-using-notifications-email.html#monitor-sending-activity-using-notifications-email-destination
|
|
|
|
|
|
.. _amazon-ses-tags:
|
|
|
|
Tags and metadata
|
|
-----------------
|
|
|
|
Amazon SES provides two mechanisms for associating additional data with sent messages,
|
|
which Anymail uses to implement its :attr:`~anymail.message.AnymailMessage.tags`
|
|
and :attr:`~anymail.message.AnymailMessage.metadata` features:
|
|
|
|
* **SES Message Tags** can be used for filtering or segmenting CloudWatch metrics and
|
|
dashboards, and are available to Kinesis Firehose streams. (See "How do message
|
|
tags work?" in the Amazon blog post `Introducing Sending Metrics`_.)
|
|
|
|
By default, Anymail does *not* use SES Message Tags. They have strict limitations
|
|
on characters allowed, and are not consistently available to tracking webhooks.
|
|
(They may be included in `SES Event Publishing`_ but not `SES Notifications`_.)
|
|
|
|
* **Custom Email Headers** are available to all SNS notifications (webhooks), but
|
|
not to CloudWatch or Kinesis.
|
|
|
|
These are ordinary extension headers included in the sent message (and visible to
|
|
recipients who view the full headers). There are no restrictions on characters allowed.
|
|
|
|
By default, Anymail uses only custom email headers. A message's
|
|
:attr:`~anymail.message.AnymailMessage.metadata` is sent JSON-encoded in a custom
|
|
:mailheader:`X-Metadata` header, and a message's :attr:`~anymail.message.AnymailMessage.tags`
|
|
are sent in custom :mailheader:`X-Tag` headers. Both are available in Anymail's
|
|
:ref:`tracking webhooks <amazon-ses-webhooks>`.
|
|
|
|
Because Anymail :attr:`~anymail.message.AnymailMessage.tags` are often used for
|
|
segmenting reports, Anymail has an option to easily send an Anymail tag
|
|
as an SES Message Tag that can be used in CloudWatch. Set the Anymail setting
|
|
:setting:`AMAZON_SES_MESSAGE_TAG_NAME <ANYMAIL_AMAZON_SES_MESSAGE_TAG_NAME>`
|
|
to the name of an SES Message Tag whose value will be the *single* Anymail tag
|
|
on the message. For example, with this setting:
|
|
|
|
.. code-block:: python
|
|
|
|
ANYMAIL = {
|
|
...
|
|
"AMAZON_SES_MESSAGE_TAG_NAME": "Type",
|
|
}
|
|
|
|
this send will appear in CloudWatch with the SES Message Tag `"Type": "Marketing"`:
|
|
|
|
.. code-block:: python
|
|
|
|
message = EmailMessage(...)
|
|
message.tags = ["Marketing"]
|
|
message.send()
|
|
|
|
Anymail's :setting:`AMAZON_SES_MESSAGE_TAG_NAME <ANYMAIL_AMAZON_SES_MESSAGE_TAG_NAME>`
|
|
setting is disabled by default. If you use it, then only a single tag is supported,
|
|
and both the tag and the name must be limited to alphanumeric, hyphen, and underscore
|
|
characters.
|
|
|
|
For more complex use cases, set the SES ``EmailTags`` parameter (or ``DefaultEmailTags``
|
|
for template sends) directly in Anymail's :ref:`esp_extra <amazon-ses-esp-extra>`. See
|
|
the example below.
|
|
|
|
.. _Introducing Sending Metrics:
|
|
https://aws.amazon.com/blogs/ses/introducing-sending-metrics/
|
|
.. _SES Event Publishing:
|
|
https://docs.aws.amazon.com/ses/latest/DeveloperGuide/monitor-using-event-publishing.html
|
|
.. _SES Notifications:
|
|
https://docs.aws.amazon.com/ses/latest/DeveloperGuide/monitor-sending-using-notifications.html
|
|
|
|
|
|
.. _amazon-ses-esp-extra:
|
|
|
|
esp_extra support
|
|
-----------------
|
|
|
|
To use Amazon SES features not directly supported by Anymail, you can
|
|
set a message's :attr:`~anymail.message.AnymailMessage.esp_extra` to
|
|
a `dict` that will be shallow-merged into the params for the `SendEmail`_
|
|
or `SendBulkEmail`_ SES v2 API call.
|
|
|
|
Examples (for a non-template send):
|
|
|
|
.. code-block:: python
|
|
|
|
message.esp_extra = {
|
|
# Override AMAZON_SES_CONFIGURATION_SET_NAME for this message:
|
|
'ConfigurationSetName': 'NoOpenOrClickTrackingConfigSet',
|
|
# Authorize a custom sender:
|
|
'FromEmailAddressIdentityArn': 'arn:aws:ses:us-east-1:123456789012:identity/example.com',
|
|
# Set SES Message Tags (change to 'DefaultEmailTags' for template sends):
|
|
'EmailTags': [
|
|
# (Names and values must be A-Z a-z 0-9 - and _ only)
|
|
{'Name': 'UserID', 'Value': str(user_id)},
|
|
{'Name': 'TestVariation', 'Value': 'Subject-Emoji-Trial-A'},
|
|
],
|
|
# Set options for unsubscribe links:
|
|
'ListManagementOptions': {
|
|
'ContactListName': 'RegisteredUsers',
|
|
'TopicName': 'DailyUpdates',
|
|
},
|
|
}
|
|
|
|
|
|
(You can also set `"esp_extra"` in Anymail's :ref:`global send defaults <send-defaults>`
|
|
to apply it to all messages.)
|
|
|
|
.. _SendEmail:
|
|
https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_SendEmail.html
|
|
.. _SendBulkEmail:
|
|
https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_SendBulkEmail.html
|
|
|
|
|
|
.. _amazon-ses-templates:
|
|
|
|
Batch sending/merge and ESP templates
|
|
-------------------------------------
|
|
|
|
Amazon SES offers :ref:`ESP stored templates <esp-stored-templates>`
|
|
and :ref:`batch sending <batch-send>` with per-recipient merge data.
|
|
See Amazon's `Sending personalized email`_ guide for more information.
|
|
|
|
When you set a message's :attr:`~anymail.message.AnymailMessage.template_id`
|
|
to the name of one of your SES templates, Anymail will use the SES v2 `SendBulkEmail`_
|
|
call to send template messages personalized with data
|
|
from Anymail's normalized :attr:`~anymail.message.AnymailMessage.merge_data`,
|
|
:attr:`~anymail.message.AnymailMessage.merge_global_data`,
|
|
:attr:`~anymail.message.AnymailMessage.merge_metadata`, and
|
|
:attr:`~anymail.message.AnymailMessage.merge_headers` message attributes.
|
|
|
|
.. code-block:: python
|
|
|
|
message = EmailMessage(
|
|
from_email="shipping@example.com",
|
|
# you must omit subject and body (or set to None) with Amazon SES templates
|
|
to=["alice@example.com", "Bob <bob@example.com>"]
|
|
)
|
|
message.template_id = "MyTemplateName" # Amazon SES TemplateName
|
|
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",
|
|
}
|
|
|
|
Amazon's templated email APIs don't support a few features available for regular email.
|
|
When :attr:`~anymail.message.AnymailMessage.template_id` is used:
|
|
|
|
* Attachments and inline images are not supported
|
|
* Alternative parts (including AMPHTML) are not supported
|
|
* Overriding the template's subject or body is not supported
|
|
|
|
.. versionchanged:: 11.0
|
|
|
|
Extra headers, :attr:`~anymail.message.AnymailMessage.metadata`,
|
|
:attr:`~anymail.message.AnymailMessage.merge_metadata`, and
|
|
:attr:`~anymail.message.AnymailMessage.tags` are now fully supported
|
|
when using :attr:`~anymail.message.AnymailMessage.template_id`.
|
|
(This requires :pypi:`boto3` v1.34.98 or later, which enables the
|
|
ReplacementHeaders parameter for SendBulkEmail.)
|
|
|
|
.. _Sending personalized email:
|
|
https://docs.aws.amazon.com/ses/latest/DeveloperGuide/send-personalized-email-api.html
|
|
|
|
|
|
.. _amazon-ses-webhooks:
|
|
|
|
Status tracking webhooks
|
|
------------------------
|
|
|
|
Anymail can provide normalized :ref:`status tracking <event-tracking>` notifications
|
|
for messages sent through Amazon SES. SES offers two (confusingly) similar kinds of
|
|
tracking, and Anymail supports both:
|
|
|
|
* `SES Notifications`_ include delivered, bounced, and complained (spam) Anymail
|
|
:attr:`~anymail.signals.AnymailTrackingEvent.event_type`\s. (Enabling these
|
|
notifications may allow you to disable SES "email feedback forwarding.")
|
|
|
|
* `SES Event Publishing`_ also includes delivered, bounced and complained events,
|
|
as well as sent, rejected, opened, clicked, and (template rendering) failed.
|
|
|
|
Both types of tracking events are delivered to Anymail's webhook URL through
|
|
Amazon Simple Notification Service (SNS) subscriptions.
|
|
|
|
Amazon's naming here can be really confusing. We'll try to be clear about "SES Notifications"
|
|
vs. "SES Event Publishing" as the two different kinds of SES tracking events.
|
|
And then distinguish all of that from "SNS"---the publish/subscribe service
|
|
used to notify Anymail's tracking webhooks about *both* kinds of SES tracking event.
|
|
|
|
To use Anymail's status tracking webhooks with Amazon SES:
|
|
|
|
1. First, :ref:`configure Anymail webhooks <webhooks-configuration>` and deploy your
|
|
Django project. (Deploying allows Anymail to confirm the SNS subscription for you
|
|
in step 3.)
|
|
|
|
Then in Amazon's **Simple Notification Service** console:
|
|
|
|
2. `Create an SNS Topic`_ to receive Amazon SES tracking events.
|
|
The exact topic name is up to you; choose something meaningful like *SES_Tracking_Events*.
|
|
|
|
3. Subscribe Anymail's tracking webhook to the SNS Topic you just created. In the SNS
|
|
console, click into the topic from step 2, then click the "Create subscription" button.
|
|
For protocol choose HTTPS. For endpoint enter:
|
|
|
|
:samp:`https://{random}:{random}@{yoursite.example.com}/anymail/amazon_ses/tracking/`
|
|
|
|
* *random:random* is an :setting:`ANYMAIL_WEBHOOK_SECRET` shared secret
|
|
* *yoursite.example.com* is your Django site
|
|
|
|
Anymail will automatically confirm the SNS subscription. (For other options, see
|
|
:ref:`amazon-ses-confirm-sns-subscriptions` below.)
|
|
|
|
Finally, switch to Amazon's **Simple Email Service** console:
|
|
|
|
4. **If you want to use SES Notifications:** Follow Amazon's guide to
|
|
`configure SES notifications through SNS`_, using the SNS Topic you created above.
|
|
Choose any event types you want to receive. Be sure to choose "Include original headers"
|
|
if you need access to Anymail's :attr:`~anymail.message.AnymailMessage.metadata` or
|
|
:attr:`~anymail.message.AnymailMessage.tags` in your webhook handlers.
|
|
|
|
5. **If you want to use SES Event Publishing:**
|
|
|
|
a. Follow Amazon's guide to `create an SES "Configuration Set"`_. Name it something meaningful,
|
|
like *TrackingConfigSet.*
|
|
|
|
b. Follow Amazon's guide to `add an SNS event destination for SES event publishing`_, using the
|
|
SNS Topic you created above. Choose any event types you want to receive.
|
|
|
|
c. Update your Anymail settings to send using this Configuration Set by default:
|
|
|
|
.. code-block:: python
|
|
|
|
ANYMAIL = {
|
|
# ... other settings ...
|
|
# Use the name from step 5a above:
|
|
"AMAZON_SES_CONFIGURATION_SET_NAME": "TrackingConfigSet",
|
|
}
|
|
|
|
.. caution::
|
|
|
|
The delivery, bounce, and complaint event types are available in both SES Notifications
|
|
*and* SES Event Publishing. If you're using both, don't enable the same events in both
|
|
places, or you'll receive duplicate notifications with *different*
|
|
:attr:`~anymail.signals.AnymailTrackingEvent.event_id`\s.
|
|
|
|
|
|
Note that Amazon SES's open and click tracking does not distinguish individual recipients.
|
|
If you send a single message to multiple recipients, Anymail will call your tracking handler
|
|
with the "opened" or "clicked" event for *every* original recipient of the message, including
|
|
all to, cc and bcc addresses. (Amazon recommends avoiding multiple recipients with SES.)
|
|
|
|
In your tracking signal receiver, the normalized AnymailTrackingEvent's
|
|
:attr:`~anymail.signals.AnymailTrackingEvent.esp_event` will be set to the
|
|
the parsed, top-level JSON event object from SES: either `SES Notification contents`_
|
|
or `SES Event Publishing contents`_. (The two formats are nearly identical.)
|
|
You can use this to obtain SES Message Tags (see :ref:`amazon-ses-tags`) from
|
|
SES Event Publishing notifications:
|
|
|
|
.. code-block:: python
|
|
|
|
from anymail.signals import tracking
|
|
from django.dispatch import receiver
|
|
|
|
@receiver(tracking) # add weak=False if inside some other function/class
|
|
def handle_tracking(sender, event, esp_name, **kwargs):
|
|
if esp_name == "Amazon SES":
|
|
try:
|
|
message_tags = {
|
|
name: values[0]
|
|
for name, values in event.esp_event["mail"]["tags"].items()}
|
|
except KeyError:
|
|
message_tags = None # SES Notification (not Event Publishing) event
|
|
print("Message %s to %s event %s: Message Tags %r" % (
|
|
event.message_id, event.recipient, event.event_type, message_tags))
|
|
|
|
|
|
Anymail does *not* currently check `SNS signature verification`_, because Amazon has not
|
|
released a standard way to do that in Python. Instead, Anymail relies on your
|
|
:setting:`WEBHOOK_SECRET <ANYMAIL_WEBHOOK_SECRET>` to verify SNS notifications are from an
|
|
authorized source.
|
|
|
|
.. _amazon-ses-sns-retry-policy:
|
|
|
|
.. note::
|
|
|
|
Amazon SNS's default policy for handling HTTPS notification failures is to retry
|
|
three times, 20 seconds apart, and then drop the notification. That means
|
|
**if your webhook is ever offline for more than one minute, you may miss events.**
|
|
|
|
For most uses, it probably makes sense to `configure an SNS retry policy`_ with more
|
|
attempts over a longer period. E.g., 20 retries ranging from 5 seconds minimum
|
|
to 600 seconds (5 minutes) maximum delay between attempts, with geometric backoff.
|
|
|
|
Also, SNS does *not* guarantee notifications will be delivered to HTTPS subscribers
|
|
like Anymail webhooks. The longest SNS will ever keep retrying is one hour total. If you need
|
|
retries longer than that, or guaranteed delivery, you may need to implement your own queuing
|
|
mechanism with something like Celery or directly on Amazon Simple Queue Service (SQS).
|
|
|
|
|
|
.. _Create an SNS Topic:
|
|
https://docs.aws.amazon.com/sns/latest/dg/CreateTopic.html
|
|
.. _configure SES notifications through SNS:
|
|
https://docs.aws.amazon.com/ses/latest/DeveloperGuide/configure-sns-notifications.html
|
|
.. _create an SES "Configuration Set":
|
|
https://docs.aws.amazon.com/ses/latest/DeveloperGuide/event-publishing-create-configuration-set.html
|
|
.. _add an SNS event destination for SES event publishing:
|
|
https://docs.aws.amazon.com/ses/latest/DeveloperGuide/event-publishing-add-event-destination-sns.html
|
|
.. _SES Notification contents:
|
|
https://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html
|
|
.. _SES Event Publishing contents:
|
|
https://docs.aws.amazon.com/ses/latest/DeveloperGuide/event-publishing-retrieving-sns-contents.html
|
|
.. _SNS signature verification:
|
|
https://docs.aws.amazon.com/sns/latest/dg/SendMessageToHttp.verify.signature.html
|
|
.. _configure an SNS retry policy:
|
|
https://docs.aws.amazon.com/sns/latest/dg/DeliveryPolicies.html
|
|
|
|
|
|
.. _amazon-ses-inbound:
|
|
|
|
Inbound webhook
|
|
---------------
|
|
|
|
You can receive email through Amazon SES with Anymail's normalized :ref:`inbound <inbound>`
|
|
handling. See `Receiving email with Amazon SES`_ for background.
|
|
|
|
Configuring Anymail's inbound webhook for Amazon SES is similar to installing the
|
|
:ref:`tracking webhook <amazon-ses-webhooks>`. You must use a different SNS Topic
|
|
for inbound.
|
|
|
|
To use Anymail's inbound webhook with Amazon SES:
|
|
|
|
1. First, if you haven't already, :ref:`configure Anymail webhooks <webhooks-configuration>`
|
|
and deploy your Django project. (Deploying allows Anymail to confirm the SNS subscription
|
|
for you in step 3.)
|
|
|
|
2. `Create an SNS Topic`_ to receive Amazon SES inbound events.
|
|
The exact topic name is up to you; choose something meaningful like *SES_Inbound_Events*.
|
|
(If you are also using Anymail's tracking events, this must be a *different* SNS Topic.)
|
|
|
|
3. Subscribe Anymail's inbound webhook to the SNS Topic you just created. In the SNS
|
|
console, click into the topic from step 2, then click the "Create subscription" button.
|
|
For protocol choose HTTPS. For endpoint enter:
|
|
|
|
:samp:`https://{random}:{random}@{yoursite.example.com}/anymail/amazon_ses/inbound/`
|
|
|
|
* *random:random* is an :setting:`ANYMAIL_WEBHOOK_SECRET` shared secret
|
|
* *yoursite.example.com* is your Django site
|
|
|
|
Anymail will automatically confirm the SNS subscription. (For other options, see
|
|
:ref:`amazon-ses-confirm-sns-subscriptions` below.)
|
|
|
|
4. Next, follow Amazon's guide to `Setting up Amazon SES email receiving`_.
|
|
There are several steps. Come back here when you get to "Action Options"
|
|
in the last step, "Creating Receipt Rules."
|
|
|
|
5. Anymail supports two SES receipt actions: S3 and SNS. (Both actually use SNS.)
|
|
You can choose either one: the SNS action is easier to set up, but the S3 action
|
|
allows you to receive larger messages and can be more robust.
|
|
(You can change at any time, but don't use both simultaneously.)
|
|
|
|
* **For the SNS action:** choose the SNS Topic you created in step 2. Anymail will handle
|
|
either Base64 or UTF-8 encoding; use Base64 if you're not sure.
|
|
|
|
* **For the S3 action:** choose or create any S3 bucket that Boto will be able to read.
|
|
(See :ref:`amazon-ses-iam-permissions`; *don't* use a world-readable bucket!)
|
|
"Object key prefix" is optional. Anymail does *not* currently support the
|
|
"Encrypt message" option. Finally, choose the SNS Topic you created in step 2.
|
|
|
|
Amazon SES will likely deliver a test message to your Anymail inbound handler immediately
|
|
after you complete the last step.
|
|
|
|
If you are using the S3 receipt action, note that Anymail does not delete the S3 object.
|
|
You can delete it from your code after successful processing, or set up S3 bucket policies
|
|
to automatically delete older messages. In your inbound handler, you can retrieve the S3
|
|
object key by prepending the "object key prefix" (if any) from your receipt rule to Anymail's
|
|
:attr:`event.event_id <anymail.signals.AnymailInboundEvent.event_id>`.
|
|
|
|
Amazon SNS imposes a 15 second limit on all notifications. This includes time to download
|
|
the message (if you are using the S3 receipt action) and any processing in your
|
|
signal receiver. If the total takes longer, SNS will consider the notification failed
|
|
and will make several repeat attempts. To avoid problems, it's essential any lengthy
|
|
operations are offloaded to a background task.
|
|
|
|
Amazon SNS's default retry policy times out after one minute of failed notifications.
|
|
If your webhook is ever unreachable for more than a minute, **you may miss inbound mail.**
|
|
You'll probably want to adjust your SNS topic settings to reduce the chances of that.
|
|
See the note about :ref:`retry policies <amazon-ses-sns-retry-policy>` in the tracking
|
|
webhooks discussion above.
|
|
|
|
In your inbound signal receiver, the normalized AnymailTrackingEvent's
|
|
:attr:`~anymail.signals.AnymailTrackingEvent.esp_event` will be set to the
|
|
the parsed, top-level JSON object described in `SES Email Receiving contents`_.
|
|
|
|
.. _Receiving email with Amazon SES:
|
|
https://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email.html
|
|
.. _Setting up Amazon SES email receiving:
|
|
https://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email-setting-up.html
|
|
.. _SES Email Receiving contents:
|
|
https://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email-notifications-contents.html
|
|
|
|
|
|
.. _amazon-ses-confirm-sns-subscriptions:
|
|
|
|
Confirming SNS subscriptions
|
|
----------------------------
|
|
|
|
Amazon SNS requires HTTPS endpoints (webhooks) to confirm they actually want to subscribe
|
|
to an SNS Topic. See `Sending SNS messages to HTTPS endpoints`_ in the Amazon SNS docs
|
|
for more information.
|
|
|
|
(This has nothing to do with verifying email identities in Amazon *SES*,
|
|
and is not related to email recipients confirming subscriptions to your content.)
|
|
|
|
Anymail will automatically handle SNS endpoint confirmation for you, for both tracking and inbound
|
|
webhooks, if both:
|
|
|
|
1. You have deployed your Django project with :ref:`Anymail webhooks enabled <webhooks-configuration>`
|
|
and an Anymail :setting:`WEBHOOK_SECRET <ANYMAIL_WEBHOOK_SECRET>` set, **before** subscribing the SNS Topic
|
|
to the webhook URL.
|
|
|
|
.. caution::
|
|
|
|
If you create the SNS subscription *before* deploying your Django project with the webhook secret
|
|
set, confirmation will fail and you will need to **re-create the subscription** by entering the
|
|
full URL and webhook secret into the SNS console again.
|
|
|
|
You **cannot** use the SNS console's "Request confirmation" button to re-try confirmation.
|
|
(That will fail due to an `SNS console bug`_ that sends authentication as asterisks,
|
|
rather than the username:password secret you originally entered.)
|
|
|
|
2. The SNS endpoint URL includes the correct Anymail :setting:`WEBHOOK_SECRET <ANYMAIL_WEBHOOK_SECRET>`
|
|
as HTTP basic authentication. (Amazon SNS only allows this with https urls, not plain http.)
|
|
|
|
Anymail requires a valid secret to ensure the subscription request is coming from you, not some other
|
|
AWS user.
|
|
|
|
If you do not want Anymail to automatically confirm SNS subscriptions for its webhook URLs, set
|
|
:setting:`AMAZON_SES_AUTO_CONFIRM_SNS_SUBSCRIPTIONS <ANYMAIL_AMAZON_SES_AUTO_CONFIRM_SNS_SUBSCRIPTIONS>`
|
|
to `False` in your ANYMAIL settings.
|
|
|
|
When auto-confirmation is disabled (or if Anymail receives an unexpected confirmation request),
|
|
it will raise an :exc:`AnymailWebhookValidationFailure`, which should show up in your Django error
|
|
logging. The error message will include the Token you can use to manually confirm the subscription
|
|
in the Amazon SNS console or through the SNS API.
|
|
|
|
|
|
.. _Sending SNS messages to HTTPS endpoints:
|
|
https://docs.aws.amazon.com/sns/latest/dg/SendMessageToHttp.html
|
|
.. _SNS console bug:
|
|
https://github.com/anymail/django-anymail/issues/194#issuecomment-665350148
|
|
|
|
|
|
.. _amazon-ses-settings:
|
|
|
|
Settings
|
|
--------
|
|
|
|
Additional Anymail settings for use with Amazon SES:
|
|
|
|
.. setting:: ANYMAIL_AMAZON_SES_CLIENT_PARAMS
|
|
|
|
.. rubric:: AMAZON_SES_CLIENT_PARAMS
|
|
|
|
Optional. Additional `client parameters`_ Anymail should use to create the boto3 session client. Example:
|
|
|
|
.. code-block:: python
|
|
|
|
ANYMAIL = {
|
|
...
|
|
"AMAZON_SES_CLIENT_PARAMS": {
|
|
# example: override normal Boto credentials specifically for Anymail
|
|
"aws_access_key_id": os.getenv("AWS_ACCESS_KEY_FOR_ANYMAIL_SES"),
|
|
"aws_secret_access_key": os.getenv("AWS_SECRET_KEY_FOR_ANYMAIL_SES"),
|
|
"region_name": "us-west-2",
|
|
# override other default options
|
|
"config": {
|
|
"connect_timeout": 30,
|
|
"read_timeout": 30,
|
|
}
|
|
},
|
|
}
|
|
|
|
In most cases, it's better to let Boto obtain its own credentials through one of its other
|
|
mechanisms: an IAM role for EC2 instances, standard AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
|
|
and AWS_SESSION_TOKEN environment variables, or a shared AWS credentials file.
|
|
|
|
.. _client parameters:
|
|
https://boto3.readthedocs.io/en/stable/reference/core/session.html#boto3.session.Session.client
|
|
|
|
|
|
.. setting:: ANYMAIL_AMAZON_SES_SESSION_PARAMS
|
|
|
|
.. rubric:: AMAZON_SES_SESSION_PARAMS
|
|
|
|
Optional. Additional `session parameters`_ Anymail should use to create the boto3 Session. Example:
|
|
|
|
.. code-block:: python
|
|
|
|
ANYMAIL = {
|
|
...
|
|
"AMAZON_SES_SESSION_PARAMS": {
|
|
"profile_name": "anymail-testing",
|
|
},
|
|
}
|
|
|
|
.. _session parameters:
|
|
https://boto3.readthedocs.io/en/stable/reference/core/session.html#boto3.session.Session
|
|
|
|
|
|
.. setting:: ANYMAIL_AMAZON_SES_CONFIGURATION_SET_NAME
|
|
|
|
.. rubric:: AMAZON_SES_CONFIGURATION_SET_NAME
|
|
|
|
Optional. The name of an Amazon SES `Configuration Set`_ Anymail should use when sending messages.
|
|
The default is to send without any Configuration Set. Note that a Configuration Set is
|
|
required to receive SES Event Publishing tracking events. See :ref:`amazon-ses-webhooks` above.
|
|
|
|
You can override this for individual messages with :ref:`esp_extra <amazon-ses-esp-extra>`.
|
|
|
|
.. _Configuration Set:
|
|
https://docs.aws.amazon.com/ses/latest/DeveloperGuide/using-configuration-sets.html
|
|
|
|
|
|
.. setting:: ANYMAIL_AMAZON_SES_MESSAGE_TAG_NAME
|
|
|
|
.. rubric:: AMAZON_SES_MESSAGE_TAG_NAME
|
|
|
|
Optional, default `None`. The name of an Amazon SES "Message Tag" whose value is set
|
|
from a message's Anymail :attr:`~anymail.message.AnymailMessage.tags`.
|
|
See :ref:`amazon-ses-tags` above.
|
|
|
|
|
|
.. setting:: ANYMAIL_AMAZON_SES_AUTO_CONFIRM_SNS_SUBSCRIPTIONS
|
|
|
|
.. rubric:: AMAZON_SES_AUTO_CONFIRM_SNS_SUBSCRIPTIONS
|
|
|
|
Optional boolean, default `True`. Set to `False` to prevent Anymail webhooks from automatically
|
|
accepting Amazon SNS subscription confirmation requests.
|
|
See :ref:`amazon-ses-confirm-sns-subscriptions` above.
|
|
|
|
|
|
.. _amazon-ses-iam-permissions:
|
|
|
|
IAM permissions
|
|
---------------
|
|
|
|
Anymail requires IAM permissions that will allow it to use these actions:
|
|
|
|
* To send mail:
|
|
|
|
* Ordinary (non-templated) sends: ``ses:SendEmail``
|
|
* Template/merge sends: ``ses:SendBulkEmail``
|
|
|
|
* To :ref:`automatically confirm <amazon-ses-confirm-sns-subscriptions>`
|
|
webhook SNS subscriptions: ``sns:ConfirmSubscription``
|
|
|
|
* For status tracking webhooks: no special permissions
|
|
|
|
* To receive inbound mail:
|
|
|
|
* With an "SNS action" receipt rule: no special permissions
|
|
* With an "S3 action" receipt rule: ``s3:GetObject`` on the S3 bucket
|
|
and prefix used (or S3 Access Control List read access for inbound
|
|
messages in that bucket)
|
|
|
|
|
|
This IAM policy covers all of those:
|
|
|
|
.. code-block:: json
|
|
|
|
{
|
|
"Version": "2012-10-17",
|
|
"Statement": [{
|
|
"Effect": "Allow",
|
|
"Action": ["ses:SendEmail", "ses:SendBulkEmail"],
|
|
"Resource": "*"
|
|
}, {
|
|
"Effect": "Allow",
|
|
"Action": ["sns:ConfirmSubscription"],
|
|
"Resource": ["arn:aws:sns:*:*:*"]
|
|
}, {
|
|
"Effect": "Allow",
|
|
"Action": ["s3:GetObject"],
|
|
"Resource": ["arn:aws:s3:::MY-PRIVATE-BUCKET-NAME/MY-INBOUND-PREFIX/*"]
|
|
}]
|
|
}
|
|
|
|
(Anymail does not need access to ``ses:SendRawEmail``
|
|
or ``ses:SendBulkTemplatedEmail``. Those are SES v1 actions.)
|
|
|
|
|
|
.. _amazon-ses-iam-errors:
|
|
|
|
.. note:: **Misleading IAM error messages**
|
|
|
|
Permissions errors for the SES v2 API often refer to the equivalent SES v1 API name,
|
|
which can be confusing. For example, this error (emphasis added):
|
|
|
|
.. parsed-literal::
|
|
|
|
An error occurred (AccessDeniedException) when calling the **SendEmail** operation:
|
|
User 'arn:...' is not authorized to perform **'ses:SendRawEmail'** on resource 'arn:...'
|
|
|
|
actually indicates problems with IAM policies for the v2 ``ses:SendEmail`` action,
|
|
*not* the v1 ``ses:SendRawEmail`` action. (The correct action appears as the "operation"
|
|
in the first line of the error message.)
|
|
|
|
Following the principle of `least privilege`_, you should omit permissions
|
|
for any features you aren't using, and you may want to add additional restrictions:
|
|
|
|
* For Amazon SES sending, you can add conditions to restrict senders, recipients, times,
|
|
or other properties. See Amazon's `Controlling access to Amazon SES`_ guide.
|
|
(Be aware that the SES v2 ``SendBulkEmail`` API does not support condition keys
|
|
that restrict email addresses, and using them can cause misleading error messages.
|
|
All other SES APIs used by Anymail *do* support address restrictions, including
|
|
the SES v2 ``SendEmail`` API used for non-template sends.)
|
|
|
|
* For auto-confirming webhooks, you might limit the resource to SNS topics owned
|
|
by your AWS account, and/or specific topic names or patterns. E.g.,
|
|
``"arn:aws:sns:*:0000000000000000:SES_*_Events"`` (replacing the zeroes with
|
|
your numeric AWS account id). See Amazon's guide to `Amazon SNS ARNs`_.
|
|
|
|
* For inbound S3 delivery, there are multiple ways to control S3 access and data
|
|
retention. See Amazon's `Managing access permissions to your Amazon S3 resources`_.
|
|
(And obviously, you should *never store incoming emails to a public bucket!*)
|
|
|
|
Also, you may need to grant Amazon SES (but *not* Anymail) permission to *write*
|
|
to your inbound bucket. See Amazon's `Giving permissions to Amazon SES for email receiving`_.
|
|
|
|
* For all operations, you can limit source IP, allowable times, user agent, and more.
|
|
(Requests from Anymail will include "django-anymail/*version*" along with Boto's user-agent.)
|
|
See Amazon's guide to `IAM condition context keys`_.
|
|
|
|
|
|
.. _least privilege:
|
|
https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege
|
|
.. _Controlling access to Amazon SES:
|
|
https://docs.aws.amazon.com/ses/latest/DeveloperGuide/control-user-access.html
|
|
.. _Amazon SNS ARNs:
|
|
https://docs.aws.amazon.com/sns/latest/dg/UsingIAMwithSNS.html#SNS_ARN_Format
|
|
.. _Managing access permissions to your Amazon S3 resources:
|
|
https://docs.aws.amazon.com/AmazonS3/latest/dev/s3-access-control.html
|
|
.. _Giving permissions to Amazon SES for email receiving:
|
|
https://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email-permissions.html
|
|
.. _IAM condition context keys:
|
|
https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html
|
|
|
|
|
|
.. _amazon-ses-v1:
|
|
.. _amazon-ses-v2:
|
|
|
|
Migrating to the SES v2 API
|
|
---------------------------
|
|
|
|
.. versionchanged:: 10.0
|
|
|
|
Anymail 10.0 and later use Amazon's updated SES v2 API to send email. Earlier Anymail releases
|
|
used the original Amazon SES API (v1) by default. Although the capabilities of the two
|
|
SES versions are virtually identical, Amazon is implementing improvements (such as
|
|
increased maximum message size) only in the v2 API.
|
|
|
|
(The upgrade for SES v2 affects only sending email. There are no changes required
|
|
for status tracking webhooks or receiving inbound email.)
|
|
|
|
Migrating to SES v2 requires minimal code changes:
|
|
|
|
1. Update your :ref:`IAM permissions <amazon-ses-iam-permissions>` to grant Anymail
|
|
access to the SES v2 sending actions: ``ses:SendEmail`` for ordinary sends, and/or
|
|
``ses:SendBulkEmail`` to send using SES templates. (The IAM action
|
|
prefix is just ``ses`` for both the v1 and v2 APIs.)
|
|
|
|
Access to ``ses:SendRawEmail`` or ``ses:SendBulkTemplatedEmail`` can be removed.
|
|
(Those actions are only needed for SES v1.)
|
|
|
|
If you run into unexpected IAM authorization failures, see the note about
|
|
:ref:`misleading IAM permissions errors <amazon-ses-iam-errors>` above.
|
|
|
|
2. If your code uses Anymail's :attr:`~anymail.message.AnymailMessage.esp_extra`
|
|
to pass additional SES API parameters, or examines the raw
|
|
:attr:`~anymail.message.AnymailStatus.esp_response` after sending a message,
|
|
you may need to update it for the v2 API. Many parameters have different names
|
|
in the v2 API compared to the equivalent v1 calls, and the response formats are
|
|
slightly different.
|
|
|
|
Among v1 parameters commonly used, ``ConfigurationSetName`` is unchanged in v2,
|
|
but v1's ``Tags`` and most ``*Arn`` parameters have been renamed in v2.
|
|
See AWS's docs for SES v1 `SendRawEmail`_ vs. v2 `SendEmail`_, or if you are sending
|
|
with SES templates, compare v1 `SendBulkTemplatedEmail`_ to v2 `SendBulkEmail`_.
|
|
|
|
(If you do not use :attr:`!esp_extra` or :attr:`!esp_response`, you can
|
|
safely ignore this.)
|
|
|
|
3. If your settings.py :setting:`!EMAIL_BACKEND` setting refers to ``amazon_sesv1``
|
|
or ``amazon_sesv2``, change that to just ``amazon_ses``:
|
|
|
|
.. code-block:: python
|
|
|
|
EMAIL_BACKEND = "anymail.backends.amazon_ses.EmailBackend"
|
|
|
|
.. _SendRawEmail:
|
|
https://docs.aws.amazon.com/ses/latest/APIReference/API_SendRawEmail.html
|
|
.. _SendBulkTemplatedEmail:
|
|
https://docs.aws.amazon.com/ses/latest/APIReference/API_SendBulkTemplatedEmail.html
|