Anymail was requiring Mandrill's webhook authentication key for the initial webhook url validation request from Mandrill, but Mandrill doesn't issue the key until that validation request succeeds.
* Defer complaining about missing Mandrill webhook key until actual event post.
* Document the double-deploy process required to set up Mandrill webhooks.
Fixes#46.
Add support for Postmark's recently-released [delivery tracking webhook] to Anymail's normailized status event handling. The existing Anymail tracking webhook URL can be copied to "Delivery webhook" in your Postmark outbound server settings.
Closes#45.
A message's `from_email` and each address in its `to`, `cc`, and `bcc` lists must contain exactly one email address. Previous code would silently ignore additional addresses, leading to unusual behavior. Now, raises new `AnymailInvalidAddress` exception.
Example: `from_email='Widgets, Inc. <widgets@example.com>'` is invalid: it needs double-quotes around the "Widgets, Inc." display-name portion. In earlier versions, this probably would have sent the message from something like "From: Widgets <@localhost>". Now, it will raise an exception.
**Potentially-breaking change:** If your code is using an unquoted display-name containing a comma in an email address, it will now raise an error. In earlier versions, this may have appeared to succeed, but was almost certainly not doing what you intended.
Fixes#44.
Compatibility with Python 2.7 versions older than 2.7.7
* Use Django's constant_time_compare method
* Include sparkpost in test requirements
* Don't use non-public `EnvironmentVarGuard` in tests
Fixes#41
Drop `from __future__ import unicode_literals`;
it was there for Python 3.2 compatibility (which
Anymail doesn't support). Ensures tests use normal
strs in Python 2.x.
Allow custom MAILGUN_SENDER_DOMAIN in Anymail
settings. (Replaces need to use global esp_extra.)
Improve docs to cover cases where this is needed.
(esp_extra sender_domain is still supported for
overriding individual messages.)
Fixes#26.
When using a stored template, SparkPost disallows
subject, text, and html. Django's EmailMessage default
empty strings are enough to provoke "Both content
object and template_id are specified" from SparkPost,
so remove them (if empty) when using stored templates.
Update docs and tests; add integration test for template_id.
Fixes#24
To conserve our ESP test accounts' send quotas, don't run
the live API integration tests 13 times in every Travis run.
Instead, just run them twice, on a representative set
of Python/Django combinations:
* Once on Python 2.7 (currently with Django 1.8)
* Once on Python 3.x (currently 3.5 with Django 1.9)
(Prep for running weekly tests on Travis cron.)
The *non*-integration tests still run on all combos.
* Introduce RUN_LIVE_TESTS environment var to control
whether live API integration test cases should run.
Default True, except in Travis-CI runs default False.
* Enable RUN_LIVE_TESTS in .travis.yml matrix for the
Python/Django combos listed above.
python-sparkpost generates a transmissions.send
payload which is now considered invalid by the API,
if you try to use both `cc` (or `bcc`) and the
`recipients` dict structure required for merge_data.
[Anymail had been generating that recipients dict
structure in all cases, for simplicity. Sometime
between 2016-06-07 and 2016-06-22, SparkPost
began rejecting that if it appeared in the `header_to`
constructed by python-sparkpost.]
Convert readthedocs links for their .org -> .io migration for hosted projects
As per [their blog post of the 27th April](https://blog.readthedocs.com/securing-subdomains/) ‘Securing subdomains’:
> Starting today, Read the Docs will start hosting projects from subdomains on the domain readthedocs.io, instead of on readthedocs.org. This change addresses some security concerns around site cookies while hosting user generated data on the same domain as our dashboard.
Test Plan: Manually visited all the links I’ve modified.
Catch and re-raise requests.RequestException in
AnymailRequestsBackend.post_to_esp.
* AnymailRequestsAPIError is needed for proper
fail_silently handling.
* Retain original requests exception type, to avoid
breaking existing code that might look for specific
requests exceptions.
Closes#16
* csrf_exempt must be applied to View.dispatch,
not View.post.
* In base WebhookTestCase, enable Django test Client
enforce_csrf_checks. (Test Client by default disables
CSRF protection.)
Closes#19
* Restructure runtests.py as suggested in
https://docs.djangoproject.com/en/1.9/topics/testing/advanced/#using-the-django-test-runner-to-test-reusable-applications
* Load version-specific Django settings modules
(rather than trying to make a single settings.configure()
work with all supported versions).
* Use `django-admin startproject` defaults for all
settings modules. (Tests compatibility with typical
apps, middleware, and other settings.)
* Set up tests-specific url config; switch to literal
urls in test cases. (Eliminates url `reverse` from
tests.)
* Make runtests.py executable
Closes#18
Also includes:
* Change AnymailTestMixin.assertDoesNotWarn
to filter specific warning classes.
* Look specifically for AnymailInsecureWebhookWarning
in WebhookBasicAuthTestsMixin.test_warns_if_no_auth
(because we don't care *in that test case* about
DeprecatedInDjango10 warnings).
* Set to field when using merge_data
The `to` field is required even if providing recipient addresses in x-smtpapi. See https://sendgrid.com/docs/API_Reference/Web_API/mail.html#-send.
* Check data['to'] contains expected emails
* Add space for toname check
* Make `to` expected data contain email only
* Create generic TestBackend that simply collects
send parameters
* Change BackendSettingsTests to TestBackend,
and add some missing cases
* Add UnsupportedFeatureTests
* Replace repetitive per-backend SEND_DEFAULTS
test cases with single (and more comprehensive)
SendDefaultsTests
* Merge esp_extra with Mandrill send payload
* Handle pythonic forms of `recipient_metadata`
and `template_content` in esp_extra
* DeprecationWarning for Mandrill EmailMessage
attributes inherited from Djrill
* message.template_id to use ESP stored templates
* message.merge_data and merge_global_data
to supply per-recipient/global merge variables
(with or without an ESP stored template)
* When using per-recipient merge_data, tell ESP to use
batch send: individual message per "to" address.
(Mailgun does this automatically; SendGrid requires
using a different "to" field; Mandrill requires
`preserve_recipients=False`; Postmark doesn't
support *this type* of batch sending with merge data.)
* Allow message.from_email=None (must be set after
init) and message.subject=None to suppress those
fields in API calls (for ESPs that allow "From" and
"Subject" in their template definitions).
Mailgun:
* Emulate merge_global_data by copying to
recipient-variables for each recipient.
SendGrid:
* Add delimiters to merge field names via
esp_extra['merge_field_format'] or
ANYMAIL_SENDGRID_MERGE_FIELD_FORMAT setting.
Mandrill:
* Remove Djrill versions of these features;
update migration notes.
Closes#5.
Previously, setting esp_extra['x-smtpapi']['filters']
would override the entire filters setting, potentially
undoing other Anymail options that use SendGrid
filters (like track_opens).
Now, 'filters' is special-cased, and merged with
any other Anymail filter options.
(We don't do a fully deep merge, because otherwise
there would be no way to use esp_extra to *clear*
Anymail settings.)
* Update utils.get_anymail_setting to support
kwargs override of django.conf.settings values
* Use the updated version everywhere
* Switch from ImproperlyConfigured to
AnymailConfigurationError exception
(anticipates feature_wehooks change)
Closes#12
Treat Mailgun metadata like all other ESPs: simple
key-value dict, where values are strings. If you want
to store JSON in metadata, you should serialize and
deserialize it yourself.
* Add smtp-id in unique_args (metadata), to ensure
it shows up in click and open events.
* Add SENDGRID_GENERATE_MESSAGE_ID setting,
default True, to control auto-Message-ID behavior.
* Document it.