* Rework and simplify personalizations code (that had grown convoluted
through several feature additions).
* Stop putting merge_global_data in legacy template "sections"; instead
just merge it into individual personalization substitutions like we
do for dynamic templates. (The "sections" version didn't add any
functionality, had the potential for conflicts with the user's own
template section tags, and was needlessly complex.)
Support merge_metadata in Mailgun, Mailjet, Mandrill, Postmark,
SparkPost, and Test backends. (SendGrid covered in earlier PR.)
Also:
* Add `merge_metadata` to AnymailMessage, AnymailMessageMixin
* Add `is_batch()` logic to BasePayload, for consistent handling
* Docs
Note: Mailjet implementation switches *all* batch sending from their
"Recipients" field to to the "Messages" array bulk sending option.
This allows an independent payload for each batch recipient.
In addition to supporting merge_metadata, this also removes the
prior limitation on mixing Cc/Bcc with merge_data.
Closes#141.
Tox:
* Add Tox factor for extras (all, none, individual ESP).
For now, only break out ESPs that have specific extra
dependencies (amazon_ses, sparkpost).
* Install most package dependencies (including extras)
through the package itself.
* Use new runtests.py environment vars to limit test tags
when Tox isn't installing all extras.
Travis:
* Rework matrix to request specific TOXENVs directly;
drop tox-travis.
Test runner (runtests.py):
* Centralize RUN_LIVE_TESTS logic in runtests.py
* Add ANYMAIL_ONLY_TEST and ANYMAIL_SKIP_TESTS env vars
(comma-separated lists of tags)
Test implementations:
* Tag all ESP-specific tests with ESP
* Tag live tests with "live"
* Don't import ESP-specific packages at test module level.
(Test discovery imports test modules before tag-based filtering.)
Closes#104
Postmark docs notwithstanding, Postmark allows sending mail without a
To field, as long as there is some recipient in Cc or Bcc. The API
response has a slightly different shape in this case, and Anymail now
handles that.
Also updates related recipient status parsing. Previously, Anymail's
Postmark backend converted all recipient emails to lowercase for status
reporting, and omitted Cc or Bcc recipients from
`message.anymail_status.recipients[email]`. Now, the backend preserves
the case of each recipient email as originally sent, and includes Cc
and Bcc status.
Because client code may have been relying on lowercasing recipient
emails to check status, this is a potentially breaking change.
Fixes#135
Map Mailgun severity: temporary failure event to Anymail "deferred" event, to distinguish it from severity: permanent failures which will show up as Anymail "bounced".
Also remap Mailgun reason: generic failure to Anymail "other" reject reason (rather than "bounced").
Closes#130
Update SparkPost backend tests to follow 1/2019 changes to Transmissions
API error reporting format. (No changes are required in the Anymail
backend.)
See #123
Async template rendering errors will result in "generation_failure"
webhook events. Anymail handled these, but didn't have a specific test
for them. Since they'll become more common when SparkPost switches all
template rendering to async in 1/2019, good to verify they work as
expected.
See #123
Mailgun's API silently drops attachments without filenames (and inline
attachments without Content-IDs). Raise an AnymailUnsupportedFeature
error on attempts to send these attachments.
Fixes#128
Handle MIME attachments with Content-ID as inline by default.
Treat MIME attachments that have a *Content-ID* but no explicit *Content-Disposition*
header as inline, matching the behavior of many email clients.
Workaround requests/requests#4652 (urllib3/urllib3#303), where
uploaded files in multipart/form-data are improperly given RFC 2231
encoded filenames. That format is not accepted by Mailgun's API (and is
prohibited by RFC 7578), resulting in the attachments being silently
dropped.
Fix is to patch up the multipart/form-data before posting to remove
the RFC 2231 encoding.
Fixes#125
Optionally dump API requests and responses to stdout, to simplify
debugging of the raw API communications. Currently implemented only
for Requests-based backends.
This (undocumented) setting can log things like API keys, so is not
appropriate for use in production.
Use Postmark /email/batch or /email/batchWithTemplates APIs when
merge_data provided.
Parse Postmark batch-send API responses, and improve accuracy of
parsing individual recipient status from all responses.
Closes#122
Postmark issues an error if Django's default empty strings are used
with template sends.
Include template send in Postmark integration tests. (Requires real
Postmark API token -- templates aren't testable with Postmark's
sandbox token.)
Fixes#121
If you are using an SES ConfigurationSet with open or click tracking
enabled, SES replaces non-ASCII characters with question marks as it
rewrites the message to add tracking, if the bodies are sent with
`Content-Transfer-Encoding: 8bit` (which is Django's default for utf8
body parts).
Force potentially problematic parts to use CTE: quoted-printable
as a workaround.
Fixes#115.
This avoids problems with ESPs that don't distinguish *Content-ID*
from attachment filename, where a local hostname ending in ".com" could
cause Gmail to block messages sent with inline attachments.
(Mailgun, Mailjet, Mandrill and SparkPost have APIs affected by this.)
Fixes#112.
SendGrid does not always correctly provide the sent Message-ID header value
to a tracking webhook's smtp-id field, making it unreliable to use for Anymail's
`message_id`.
Instead, generate a UUID `message_id` for Anymail tracking, and pass it from
send to webhooks in SendGrid custom args as anymail_id.
Webhooks will fall back to smtp-id for compatibility with previously-sent
messages that didn't have an anymail_id custom arg.
Fixes#108
Delay raising AnymailImproperlyInstalled from webhooks.amazon_ses
until an SES webhook view is instantiated. Allows anymail.urls
to import webhooks.amazon_ses without error.
Fixes#103
Include the name of the field with the the unparsable email address
in AnymailInvalidAddress error messages.
Should help tracking down problems like in #98.
Simplify Postmark tracking webhook code by using new "RecordType"
field introduced with Postmark "modular webhooks". (Rather than
looking for fields that are probably only in certain events.)
Also issue configuration error on inbound url installed as tracking
webhook (and vice versa).
Existing tracking webhook code works fine with updated event payloads.
(So older Anymail versions will work, unmodified, with new Postmark
webhooks.)
Also update older doc links into Postmark docs.
Closes#101
Work around Python 3 email parser change that can turn Unicode
characters into \u escape sequences when parsing a message (or
attachment) that uses "Content-Transfer-Encoding: 8bit".
Useful for cases where ESP could send raw 8bit message
(and its charset is something other than utf-8).
Also reworks earlier Python 2.7 workaround email.parser.Parser header
unfolding bugs to handle any text-like, file-like IO stream, without
trying to manipulate the entire message as a single string.
Make `AnymailInboundMessage.text`, `.html` and `.get_content_text()`
usually do the right thing for non-UTF-8 messages/attachments. Fixes
an incorrect UnicodeDecodeError when receiving an (e.g.,) ISO-8859-1
encoded message, and improves handling for inbound messages that were
not properly encoded by the sender.
* Decode using the message's (or attachments's) declared charset
by default (rather than always defaulting to 'utf-8'; you can
still override with `get_content_text(charset=...)`
* Add `errors` param to `get_content_text()`, defaulting to 'replace'.
Mis-encoded messages will now use the Unicode replacement character
rather than raising errors. (Use `get_content_text(errors='strict')`
for the previous behavior.)
And decide not to work around a Python 3.3 bug accessing MIME headers
that have non-ASCII characters in params. The bug is fixed in the
Python 3.4 email package (and didn't exist in Python 2.7). Python 3.3
was only supported with Django 1.8.