SendGrid: update to new v3 send API (#50)

SendGrid: update to v3 send API

**SendGrid:** **[possibly-breaking]** Update SendGrid backend to newer Web API v3. This should be a transparent change for most projects. Exceptions: if you use SendGrid username/password auth, esp_extra with "x-smtpapi", or multiple Reply-To addresses, please review the [porting notes](http://anymail.readthedocs.io/en/latest/esps/sendgrid/#sendgrid-v3-upgrade).

Closes #28
This commit is contained in:
Mike Edmunds
2017-01-19 14:29:15 -08:00
committed by GitHub
parent ac8147b0b8
commit e568e50d0c
11 changed files with 1891 additions and 565 deletions

View File

@@ -3,12 +3,25 @@
SendGrid
========
Anymail integrates with the `SendGrid`_ email service,
using their `Web API v2`_. (Their v3 API does not support sending mail,
but the v3 API calls *do* get information about mail sent through v2.)
Anymail integrates with the `SendGrid`_ email service, using their `Web API v3`_.
.. versionchanged:: 0.8
Earlier Anymail releases used SendGrid's v2 API. If you are upgrading,
please review the :ref:`porting notes <sendgrid-v3-upgrade>`.
.. important::
**Troubleshooting:**
If your SendGrid messages aren't being delivered as expected, be sure to look for
"drop" events in your SendGrid `activity feed`_.
SendGrid detects certain types of errors only *after* the send API call appears
to succeed, and reports these errors as drop events.
.. _SendGrid: https://sendgrid.com/
.. _Web API v2: https://sendgrid.com/docs/API_Reference/Web_API/mail.html
.. _Web API v3: https://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/index.html
.. _activity feed: https://app.sendgrid.com/email_activity?events=drops
Settings
@@ -33,8 +46,7 @@ their name with an uppercase "G", so Anymail does too.)
A SendGrid API key with "Mail Send" permission.
(Manage API keys in your `SendGrid API key settings`_.)
Either an API key or both :setting:`SENDGRID_USERNAME <ANYMAIL_SENDGRID_USERNAME>`
and :setting:`SENDGRID_PASSWORD <ANYMAIL_SENDGRID_PASSWORD>` are required.
Required.
.. code-block:: python
@@ -50,34 +62,6 @@ nor ``ANYMAIL_SENDGRID_API_KEY`` is set.
.. _SendGrid API key settings: https://app.sendgrid.com/settings/api_keys
.. setting:: ANYMAIL_SENDGRID_USERNAME
.. setting:: ANYMAIL_SENDGRID_PASSWORD
.. rubric:: SENDGRID_USERNAME and SENDGRID_PASSWORD
SendGrid credentials with the "Mail" permission. You should **not**
use the username/password that you use to log into SendGrid's
dashboard. Create credentials specifically for sending mail in the
`SendGrid credentials settings`_.
.. code-block:: python
ANYMAIL = {
...
"SENDGRID_USERNAME": "<sendgrid credential with Mail permission>",
"SENDGRID_PASSWORD": "<password for that credential>",
}
Either username/password or :setting:`SENDGRID_API_KEY <ANYMAIL_SENDGRID_API_KEY>`
are required (but not both).
Anymail will also look for ``SENDGRID_USERNAME`` and ``SENDGRID_PASSWORD`` at the
root of the settings file if neither ``ANYMAIL["SENDGRID_USERNAME"]``
nor ``ANYMAIL_SENDGRID_USERNAME`` is set.
.. _SendGrid credentials settings: https://app.sendgrid.com/settings/credentials
.. setting:: ANYMAIL_SENDGRID_GENERATE_MESSAGE_ID
.. rubric:: SENDGRID_GENERATE_MESSAGE_ID
@@ -121,9 +105,9 @@ below.
.. rubric:: SENDGRID_API_URL
The base url for calling the SendGrid v2 API.
The base url for calling the SendGrid API.
The default is ``SENDGRID_API_URL = "https://api.sendgrid.com/api/"``
The default is ``SENDGRID_API_URL = "https://api.sendgrid.com/v3/"``
(It's unlikely you would need to change this.)
@@ -134,28 +118,28 @@ esp_extra support
To use SendGrid features not directly supported by Anymail, you can
set a message's :attr:`~anymail.message.AnymailMessage.esp_extra` to
a `dict` of parameters for SendGrid's `mail.send API`_. Any keys in
your :attr:`esp_extra` dict will override Anymail's normal values
for that parameter, except that `'x-smtpapi'` will be merged.
a `dict` of parameters for SendGrid's `v3 Mail Send API`_.
Your :attr:`esp_extra` dict will be deeply merged into the
parameters Anymail has constructed for the send, with `esp_extra`
having precedence in conflicts.
Example:
.. code-block:: python
message.open_tracking = True
message.esp_extra = {
'x-smtpapi': {
"asm_group": 1, # Assign SendGrid unsubscribe group for this message
"asm_groups_to_display": [1, 2, 3],
"filters": {
"subscriptiontrack": { # Insert SendGrid subscription management links
"settings": {
"text/html": "If you would like to unsubscribe <% click here %>.",
"text/plain": "If you would like to unsubscribe click here: <% %>.",
"enable": 1
}
}
}
}
"asm": { # SendGrid subscription management
"group_id": 1,
"groups_to_display": [1, 2, 3],
},
"tracking_settings": {
"open_tracking": {
# Anymail will automatically set `"enable": True` here,
# based on message.open_tracking.
"substitution_tag": "%%OPEN_TRACKING_PIXEL%%",
},
},
}
@@ -164,22 +148,14 @@ Example:
messages.)
.. _mail.send API: https://sendgrid.com/docs/API_Reference/Web_API/mail.html#-send
.. _v3 Mail Send API:
https://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/index.html#-Request-Body-Parameters
Limitations and quirks
----------------------
**Duplicate attachment filenames**
Anymail is not capable of communicating multiple attachments with
the same filename to SendGrid. (This also applies to multiple attachments
with *no* filename, though not to inline images.)
If you are sending multiple attachments on a single message,
make sure each one has a unique, non-empty filename.
.. _sendgrid-message-id:
**Message-ID**
@@ -204,6 +180,14 @@ Limitations and quirks
To disable all of these Message-ID workarounds, set
:setting:`ANYMAIL_SENDGRID_GENERATE_MESSAGE_ID` to False in your settings.
**Single Reply-To**
SendGrid's v3 API only supports a single Reply-To address (and blocks
a workaround that was possible with the v2 API).
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.
**Invalid Addresses**
SendGrid will accept *and send* just about anything as
@@ -236,8 +220,7 @@ message attributes.
message = EmailMessage(
...
subject="", # don't add any additional subject content to the template
body="", # (same thing for additional body content)
# omit subject and body (or set to None) to use template content
to=["alice@example.com", "Bob <bob@example.com>"]
)
message.template_id = "5997fcf6-2b9f-484d-acd5-7e9a99f0dc1f" # SendGrid id
@@ -273,19 +256,15 @@ There are three ways you can do this:
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.)
so that each recipient sees only their own email address. (Anymail creates a separate
"personalization" for each recipient in the "to" list; any cc's or bcc's will be
duplicated for *every* to-recipient.)
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.
(Anymail will convert empty text and HTML bodies to single spaces whenever
:attr:`~anymail.message.AnymailMessage.template_id` is set, to ensure the
plaintext and HTML from your template are present in your outgoing email.
This works around a `limitation in SendGrid's template rendering`_.)
from your Django app, set those EmailMessage attributes to empty strings or `None`.
See the `SendGrid's template overview`_ and `transactional template docs`_
for more information.
@@ -294,8 +273,6 @@ for more information.
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
.. _limitation in SendGrid's template rendering:
https://sendgrid.com/docs/API_Reference/Web_API_v3/Transactional_Templates/smtpapi.html#-Text-or-HTML-Templates
.. _sendgrid-webhooks:
@@ -324,3 +301,186 @@ for each event in the batch.)
.. _SendGrid mail settings: https://app.sendgrid.com/settings/mail_settings
.. _Sendgrid event: https://sendgrid.com/docs/API_Reference/Webhooks/event.html
.. _sendgrid-v3-upgrade:
Upgrading to SendGrid's v3 API
------------------------------
Anymail v0.8 switched to SendGrid's preferred v3 send API.
(Earlier Anymail releases used their v2 API.)
For many Anymail projects, this change will be entirely transparent.
(Anymail's whole reason for existence is abstracting ESP APIs,
so that your own code doesn't need to worry about the details.)
There are three cases where SendGrid has changed features
that would require updates to your code:
1. If you are using SendGrid's username/password auth (your settings
include :setting:`SENDGRID_USERNAME <ANYMAIL_SENDGRID_USERNAME>`
and :setting:`SENDGRID_PASSWORD <ANYMAIL_SENDGRID_PASSWORD>`),
you must switch to an API key.
See :setting:`SENDGRID_API_KEY <ANYMAIL_SENDGRID_API_KEY>`.
(If you are already using a SendGrid API key with v2, it should
work just fine with v3.)
2. If you are using Anymail's
:attr:`~anymail.message.AnymailMessage.esp_extra` attribute
to supply API-specific parameters, the format has changed.
Search your code for "esp_extra" (e.g., `git grep esp_extra`)
to determine whether this affects you. (Anymail's
`"merge_field_format"` is unchanged, so if that's the only
thing you have in esp_extra, no changes are needed.)
The new API format is considerably simpler and more logical.
See :ref:`sendgrid-esp-extra` below for examples of the
new format and a link to relevant SendGrid docs.
Anymail will raise an error if it detects an attempt to use
the v2-only `"x-smtpapi"` settings in esp_extra when sending.
3. If you send messages with multiple Reply-To addresses, SendGrid
no longer supports this. (Multiple reply emails in a single
message are not common.)
Anymail will raise an error if you attempt to send a message with
multiple Reply-To emails. (You can suppress the error with
:setting:`ANYMAIL_IGNORE_UNSUPPORTED_FEATURES`, which will
ignore all but the first reply address.)
As an alternative, Anymail (for the time being) still includes
a copy of the SendGrid v2 backend. See :ref:`sendgrid-v2-backend`
below if you'd prefer to stay on the older SendGrid API.
.. _sendgrid-v2-backend:
Legacy v2 API support
---------------------
.. versionchanged:: 0.8
Anymail v0.8 switched to SendGrid's v3 Web API in its primary SendGrid
email backend. SendGrid `encourages`_ all users to migrate to their v3 API.
For Anymail users who still need it, a legacy backend that calls SendGrid's
earlier `Web API v2 Mail Send`_ remains available. Be aware that v2 support
is considered deprecated and may be removed in a future Anymail release.
.. _encourages:
https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/how_to_migrate_from_v2_to_v3_mail_send.html
.. _Web API v2 Mail Send:
https://sendgrid.com/docs/API_Reference/Web_API/mail.html
To use Anymail's SendGrid v2 backend, edit your settings.py:
.. code-block:: python
EMAIL_BACKEND = "anymail.backends.sendgrid_v2.EmailBackend"
ANYMAIL = {
"SENDGRID_API_KEY": "<your API key>",
}
The same :setting:`SENDGRID_API_KEY <ANYMAIL_SENDGRID_API_KEY>` will work
with either Anymail's v2 or v3 SendGrid backend.
Nearly all of the documentation above for Anymail's v3 SendGrid backend
also applies to the v2 backend, with the following changes:
.. setting:: ANYMAIL_SENDGRID_USERNAME
.. setting:: ANYMAIL_SENDGRID_PASSWORD
.. rubric:: Username/password auth (SendGrid v2 only)
SendGrid v2 allows a username/password instead of an API key
(though SendGrid encourages API keys for all new installations).
If you must use username/password auth, set:
.. code-block:: python
EMAIL_BACKEND = "anymail.backends.sendgrid_v2.EmailBackend"
ANYMAIL = {
"SENDGRID_USERNAME": "<sendgrid credential with Mail permission>",
"SENDGRID_PASSWORD": "<password for that credential>",
# And leave out "SENDGRID_API_KEY"
}
This is **not** the username/password that you use to log into SendGrid's
dashboard. Create credentials specifically for sending mail in the
`SendGrid credentials settings`_.
Either username/password or :setting:`SENDGRID_API_KEY <ANYMAIL_SENDGRID_API_KEY>`
are required (but not both).
Anymail will also look for ``SENDGRID_USERNAME`` and ``SENDGRID_PASSWORD`` at the
root of the settings file if neither ``ANYMAIL["SENDGRID_USERNAME"]``
nor ``ANYMAIL_SENDGRID_USERNAME`` is set.
.. _SendGrid credentials settings: https://app.sendgrid.com/settings/credentials
.. rubric:: Duplicate attachment filenames (SendGrid v2 limitation)
Anymail is not capable of communicating multiple attachments with
the same filename to the SendGrid v2 API. (This also applies to multiple
attachments with *no* filename, though not to inline images.)
If you are sending multiple attachments on a single message,
make sure each one has a unique, non-empty filename.
.. rubric:: Message bodies with ESP templates (SendGrid v2 quirk)
Anymail's SendGrid v2 backend will convert empty text and HTML bodies to single spaces whenever
:attr:`~anymail.message.AnymailMessage.template_id` is set, to ensure the
plaintext and HTML from your template are present in your outgoing email.
This works around a `limitation in SendGrid's template rendering`_.
.. _limitation in SendGrid's template rendering:
https://sendgrid.com/docs/API_Reference/Web_API_v3/Transactional_Templates/smtpapi.html#-Text-or-HTML-Templates
.. rubric:: Multiple Reply-To addresses (SendGrid v2 only)
Unlike SendGrid's v3 API, Anymail is able to support multiple
Reply-To addresses with their v2 API.
.. rubric:: esp_extra with SendGrid v2
Anymail's :attr:`~anymail.message.AnymailMessage.esp_extra` attribute
is merged directly with the API parameters, so the format varies
between SendGrid's v2 and v3 APIs. With the v2 API, most interesting
settings appear beneath `'x-smtpapi'`. Example:
.. code-block:: python
message.esp_extra = {
'x-smtpapi': { # for SendGrid v2 API
"asm_group": 1, # Assign SendGrid unsubscribe group for this message
"asm_groups_to_display": [1, 2, 3],
"filters": {
"subscriptiontrack": { # Insert SendGrid subscription management links
"settings": {
"text/html": "If you would like to unsubscribe <% click here %>.",
"text/plain": "If you would like to unsubscribe click here: <% %>.",
"enable": 1
}
}
}
}
}
The value of :attr:`esp_extra` should be a `dict` of parameters for SendGrid's
`v2 mail.send API`_. Any keys in the dict will override Anymail's normal values
for that parameter, except that `'x-smtpapi'` will be merged.
.. _v2 mail.send API:
https://sendgrid.com/docs/API_Reference/Web_API/mail.html#-send