Skip to content

Commit fe35864

Browse files
committed
SparkPost: call HTTP API directly [breaking]
Switch from the (now unmaintained) python-sparkpost client library to a requests-based backend that calls SparkPost's Transmissions API directly. Also adds support for text/x-amp-html alternative parts (which are supported by the SparkPost API, but weren't by the client library). Closes #203
1 parent 4dbd342 commit fe35864

10 files changed

+527
-467
lines changed

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ jobs:
5555
# Install without optional extras (don't need to cover entire matrix)
5656
- { env: TOXENV=django31-py37-none, python: 3.7 }
5757
- { env: TOXENV=django31-py37-amazon_ses, python: 3.7 }
58-
- { env: TOXENV=django31-py37-sparkpost, python: 3.7 }
5958
# Test some specific older package versions
6059
- { env: TOXENV=django22-py37-all-old_urllib3, python: 3.7 }
6160

CHANGELOG.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,18 @@ Breaking changes
4343
need to update it for compatibility with the new API. (See
4444
`docs <https://anymail.readthedocs.io/en/latest/esps/mailjet/#esp-extra-support>`__.)
4545

46+
* **SparkPost:** Switch away from the (now unmaintained) Python SparkPost library to
47+
calling the SparkPost API directly. The "sparkpost" package is no longer necessary and
48+
can be removed from your project requirements. Most SparkPost users will not be
49+
affected by this change, with two exceptions: (1) You must provide a
50+
``SPARKPOST_API_KEY`` in your Anymail settings (Anymail does not check environment
51+
variables); and (2) if you use Anymail's `esp_extra` you will need to update it with
52+
SparkPost Transmissions API parameters.
53+
54+
As part of this change esp_extra now allows use of several SparkPost features, such
55+
as A/B testing, that were unavailable through the Python SparkPost library. (See
56+
`docs <https://anymail.readthedocs.io/en/latest/esps/sparkpost/>`__.)
57+
4658
* Remove Anymail internal code related to supporting Python 2 and older Django
4759
versions. This does not change the documented API, but may affect you if your
4860
code borrowed from Anymail's undocumented internals. (You should be able to switch
@@ -54,6 +66,12 @@ Breaking changes
5466
inheritance. (For some helpful background, see this comment about
5567
`mixin superclass ordering <https://nedbatchelder.com/blog/201210/multiple_inheritance_is_hard.html#comment_13805>`__.)
5668

69+
Features
70+
~~~~~~~~
71+
72+
* **SparkPost:** Add support for AMP for Email, via
73+
``message.attach_alternative("...AMPHTML content...", "text/x-amp-html")``.
74+
5775

5876
v7.2.1
5977
------

anymail/backends/sparkpost.py

Lines changed: 161 additions & 169 deletions
Large diffs are not rendered by default.

docs/contributing.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ Or:
9191

9292
.. code-block:: console
9393
94-
$ pip install mock boto3 sparkpost # install test dependencies
94+
$ pip install mock boto3 # install test dependencies
9595
$ python runtests.py
9696
9797
## this command can also run just a few test cases, e.g.:

docs/esps/sparkpost.rst

Lines changed: 56 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,20 @@ SparkPost
44
=========
55

66
Anymail integrates with the `SparkPost`_ email service, using their
7-
Python :pypi:`sparkpost` API client package.
7+
`Transmissions API`_.
88

9-
.. _SparkPost: https://www.sparkpost.com/
10-
11-
12-
Installation
13-
------------
9+
.. versionchanged:: 8.0
1410

15-
You must ensure the :pypi:`sparkpost` package is installed to use Anymail's SparkPost
16-
backend. Either include the "sparkpost" option when you install Anymail:
11+
Earlier Anymail versions used the official Python :pypi:`sparkpost` API client.
12+
That library is no longer maintained, and Anymail now calls SparkPost's HTTP API
13+
directly. This change should not affect most users, but you should make sure you
14+
provide :setting:`SPARKPOST_API_KEY <ANYMAIL_SPARKPOST_API_KEY>` in your
15+
Anymail settings (Anymail doesn't check environment variables), and if you are
16+
using Anymail's :ref:`esp_extra <sparkpost-esp-extra>` you will need to update that
17+
to use Transmissions API parameters.
1718

18-
.. code-block:: console
19-
20-
$ pip install "django-anymail[sparkpost]"
21-
22-
or separately run `pip install sparkpost`.
19+
.. _SparkPost: https://www.sparkpost.com/
20+
.. _Transmissions API: https://developers.sparkpost.com/api/transmissions/
2321

2422

2523
Settings
@@ -44,9 +42,6 @@ in your settings.py.
4442
A SparkPost API key with at least the "Transmissions: Read/Write" permission.
4543
(Manage API keys in your `SparkPost account API keys`_.)
4644

47-
This setting is optional; if not provided, the SparkPost API client will attempt
48-
to read your API key from the `SPARKPOST_API_KEY` environment variable.
49-
5045
.. code-block:: python
5146
5247
ANYMAIL = {
@@ -58,15 +53,21 @@ Anymail will also look for ``SPARKPOST_API_KEY`` at the
5853
root of the settings file if neither ``ANYMAIL["SPARKPOST_API_KEY"]``
5954
nor ``ANYMAIL_SPARKPOST_API_KEY`` is set.
6055

56+
.. versionchanged:: 8.0
57+
58+
This setting is required. If you store your API key in an environment variable, load
59+
it into your Anymail settings: ``"SPARKPOST_API_KEY": os.environ["SPARKPOST_API_KEY"]``.
60+
(Earlier Anymail releases used the SparkPost Python library, which would look for
61+
the environment variable.)
62+
6163
.. _SparkPost account API keys: https://app.sparkpost.com/account/credentials
6264

6365

6466
.. setting:: ANYMAIL_SPARKPOST_API_URL
6567

6668
.. rubric:: SPARKPOST_API_URL
6769

68-
The `SparkPost API Endpoint`_ to use. This setting is optional; if not provided, Anymail will
69-
use the :pypi:`python-sparkpost` client default endpoint (``"https://api.sparkpost.com/api/v1"``).
70+
The `SparkPost API Endpoint`_ to use. The default is ``"https://api.sparkpost.com/api/v1"``.
7071

7172
Set this to use a SparkPost EU account, or to work with any other API endpoint including
7273
SparkPost Enterprise API and SparkPost Labs.
@@ -79,8 +80,6 @@ SparkPost Enterprise API and SparkPost Labs.
7980
}
8081
8182
You must specify the full, versioned API endpoint as shown above (not just the base_uri).
82-
This setting only affects Anymail's calls to SparkPost, and will not apply to other code
83-
using :pypi:`python-sparkpost`.
8483

8584
.. _SparkPost API Endpoint: https://developers.sparkpost.com/api/index.html#header-api-endpoints
8685

@@ -90,28 +89,47 @@ using :pypi:`python-sparkpost`.
9089
esp_extra support
9190
-----------------
9291

93-
To use SparkPost features not directly supported by Anymail, you can
94-
set a message's :attr:`~anymail.message.AnymailMessage.esp_extra` to
95-
a `dict` of parameters for python-sparkpost's `transmissions.send method`_.
96-
Any keys in your :attr:`esp_extra` dict will override Anymail's normal
97-
values for that parameter.
92+
To use SparkPost features not directly supported by Anymail, you can set
93+
a message's :attr:`~anymail.message.AnymailMessage.esp_extra` to a `dict`
94+
of `transmissions API request body`_ data. Anymail will deeply merge your overrides
95+
into the normal API payload it has constructed, with esp_extra taking precedence
96+
in conflicts.
9897

99-
Example:
98+
Example (you probably wouldn't combine all of these options at once):
10099

101100
.. code-block:: python
102101
103102
message.esp_extra = {
104-
'transactional': True, # treat as transactional for unsubscribe and suppression
105-
'description': "Marketing test-run for new templates",
106-
'use_draft_template': True,
103+
"options": {
104+
# Treat as transactional for unsubscribe and suppression:
105+
"transactional": True,
106+
# Override your default dedicated IP pool:
107+
"ip_pool": "transactional_pool",
108+
},
109+
# Add a description:
110+
"description": "Test-run for new templates",
111+
"content": {
112+
# Use draft rather than published template:
113+
"use_draft_template": True,
114+
# Use an A/B test:
115+
"ab_test_id": "highlight_support_links",
116+
},
117+
# Use a stored recipients list (overrides message to/cc/bcc):
118+
"recipients": {
119+
"list_id": "design_team"
120+
},
107121
}
108122
123+
Note that including ``"recipients"`` in esp_extra will *completely* override the
124+
recipients list Anymail generates from your message's to/cc/bcc fields, along with any
125+
per-recipient :attr:`~anymail.message.AnymailMessage.merge_data` and
126+
:attr:`~anymail.message.AnymailMessage.merge_metadata`.
109127

110128
(You can also set `"esp_extra"` in Anymail's :ref:`global send defaults <send-defaults>`
111129
to apply it to all messages.)
112130

113-
.. _transmissions.send method:
114-
https://python-sparkpost.readthedocs.io/en/latest/api/transmissions.html#sparkpost.transmissions.Transmissions.send
131+
.. _transmissions API request body:
132+
https://developers.sparkpost.com/api/transmissions/#header-request-body
115133

116134

117135

@@ -151,6 +169,13 @@ Limitations and quirks
151169
(SparkPost's "recipient tags" are not available for tagging *messages*.
152170
They're associated with individual *addresses* in stored recipient lists.)
153171

172+
**AMP for Email**
173+
SparkPost supports sending AMPHTML email content. To include it, use
174+
``message.attach_alternative("...AMPHTML content...", "text/x-amp-html")``
175+
(and be sure to also include regular HTML and/or text bodies, too).
176+
177+
.. versionadded:: 8.0
178+
154179
**Envelope sender may use domain only**
155180
Anymail's :attr:`~anymail.message.AnymailMessage.envelope_sender` is used to
156181
populate SparkPost's `'return_path'` parameter. Anymail supplies the full

docs/tips/performance.rst

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,6 @@ used with Django's batch-sending functions :func:`~django.core.mail.send_mass_ma
1212
:meth:`connection.send_messages`. See :ref:`django:topics-sending-multiple-emails`
1313
in the Django docs for more info and an example.
1414

15-
(The exception is when Anymail wraps an ESP's official Python package, and that
16-
package doesn't support connection reuse. Django's batch-sending functions will
17-
still work, but will incur the overhead of creating a separate connection for each
18-
message sent. Currently, only SparkPost has this limitation.)
19-
2015
If you need even more performance, you may want to consider your ESP's batch-sending
2116
features. When supported by your ESP, Anymail can send multiple messages with a single
2217
API call. See :ref:`batch-send` for details, and be sure to check the

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ def long_description_from_readme(rst):
5454
"postmark": [],
5555
"sendgrid": [],
5656
"sendinblue": [],
57-
"sparkpost": ["sparkpost"],
57+
"sparkpost": [],
5858
},
5959
include_package_data=True,
6060
test_suite="runtests.runtests",
61-
tests_require=["mock", "boto3", "sparkpost"],
61+
tests_require=["mock", "boto3"],
6262
classifiers=[
6363
"Development Status :: 5 - Production/Stable",
6464
"Programming Language :: Python",

0 commit comments

Comments
 (0)