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
Python 3.3 moved various collections abstract base classes from
`collections` to `collections.abc`, but also kept them available in
`collections` for compatibility with Python 2. Python 3.8 will allow
importing only from `collections.abc`.
(`collections.abc` hasn't yet been added to six.moves; see
https://github.com/benjaminp/six/issues/155.)
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.
AnymailInternalEvent had some properties that were actually implemented
in AnymailInboundMessage. The event versions were never documented,
and never contained useful data (they were always set to None).
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).
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.)
In AnymailInboundMessage, work around Python 2 email.parser.Parser's
lack of handling for RFC2047-encoded email headers. (The Python 3 email
package already decodes these automatically.)
Improves inbound handling on Python 2 for all ESPs that provide raw
MIME email or raw headers with inbound events. (Mailgun, Mandrill,
SendGrid, SparkPost.)
Work around Python 2 email.parser.Parser bug handling RFC5322 folded
headers. Fixes problems where long headers in inbound mail (e.g.,
Subject) get truncated or have unexpected spaces.
This change also updates AnymailInboundMessage.parse_raw_mime to use
the improved "default" email.policy on Python 3 (rather than the
default "compat32" policy). This likely fixes several other parsing
bugs that will still affect code running on Python 2.
Improves inbound parsing for all ESPs that provide raw MIME email.
(Mailgun, Mandrill, SendGrid, SparkPost)
Drop support for the WEBHOOK_AUTHORIZATION setting deprecated in v1.4.
Only the WEBHOOK_SECRET replacement is allowed now.
Most Django management commands will now issue a system check error
if the old name is still used in settings.py
* Follow current setup.py recommendations from the pypa sample project
(utf-8 encoding on file reads, ensure files are read relative to
own location)
* Add/update some missing classifiers and other metadata
* Read _version.py constants into an isolated dict (rather than
the global setup.py context)
* Add setup.cfg specifying universal bdist_wheel
Django's SMTP EmailBackend allows spoofing the To header by setting
`message.extra_headers["To"]`` different from `message.to`.
No current Anymail ESP supports this. Treat extra_headers["To"] as
an unsupported ESP feature, to flag attempts to use it.
Also document Anymail's special header handling that replicates
Django's SMTP EmailBackend behavior.
New EmailMessage attribute `envelope_sender` controls ESP's sender,
sending domain, or return path where supported:
* Mailgun: overrides SENDER_DOMAIN on individual message
(domain portion only)
* Mailjet: becomes `Sender` API param
* Mandrill: becomes `return_path_domain` API param
(domain portion only)
* SparkPost: becomes `return_path` API param
* Other ESPs: not believed to be supported
Also support undocumented Django SMTP backend behavior, where envelope
sender is given by `message.from_email` when
`message.extra_headers["From"]` is set. Fixes#91.
Django allows setting the reply address with either message.reply_to
or message.extra_headers["Reply-To"]. If both are supplied, the extra
headers version takes precedence. (See EmailMessage.message().)
Several Anymail backends had duplicate logic to handle conflicting
properties. Move that logic into the base Payload.
(Also prepares for common handling of extra_headers['From'], later.)
Related changes:
* Use CaseInsensitiveDict for processing extra_headers.
This is potentially a breaking change, but any code that was trying
to send multiple headers differing only in case was likely already
broken. (Email header field names are case-insensitive, per RFC-822.)
* Handle CaseInsensitiveDict in RequestsPayload.serialize_json().
(Several backends had duplicate code for handling this, too.)
* Fixes SparkPost backend, which had been incorrectly treating
message.reply_to and message.extra_headers['Reply-To'] differently.
Add support for sending transactional email through SendinBlue. (Thanks to @RignonNoel.)
Partially implements #84. (Tracking webhooks will be a separate PR. SendinBlue doesn't support inbound handling.)
This fixes a low severity security issue affecting Anymail v0.2--v1.3.
Django error reporting includes the value of your Anymail
WEBHOOK_AUTHORIZATION setting. In a properly-configured deployment,
this should not be cause for concern. But if you have somehow exposed
your Django error reports (e.g., by mis-deploying with DEBUG=True or by
sending error reports through insecure channels), anyone who gains
access to those reports could discover your webhook shared secret. An
attacker could use this to post fabricated or malicious Anymail
tracking/inbound events to your app, if you are using those Anymail
features.
The fix renames Anymail's webhook shared secret setting so that
Django's error reporting mechanism will [sanitize][0] it.
If you are using Anymail's event tracking and/or inbound webhooks, you
should upgrade to this release and change "WEBHOOK_AUTHORIZATION" to
"WEBHOOK_SECRET" in the ANYMAIL section of your settings.py. You may
also want to [rotate the shared secret][1] value, particularly if you
have ever exposed your Django error reports to untrusted individuals.
If you are only using Anymail's EmailBackends for sending email and
have not set up Anymail's webhooks, this issue does not affect you.
The old WEBHOOK_AUTHORIZATION setting is still allowed in this release,
but will issue a system-check warning when running most Django
management commands. It will be removed completely in a near-future
release, as a breaking change.
Thanks to Charlie DeTar (@yourcelf) for responsibly reporting this
security issue through private channels.
[0]: https://docs.djangoproject.com/en/stable/ref/settings/#debug
[1]: https://anymail.readthedocs.io/en/1.4/tips/securing_webhooks/#use-a-shared-authorization-secret
Anymail's webhook validation was vulnerable to a timing attack.
An attacker could have used this to recover your WEBHOOK_AUTHORIZATION
shared secret, potentially allowing them to post fabricated or malicious
email tracking events to your app.
There have not been any reports of attempted exploit in the wild. (The
vulnerability was discovered through code review.) Attempts would be
visible in http logs as a very large number of 400 responses on
Anymail's webhook urls, or in Python error monitoring as a very large
number of AnymailWebhookValidationFailure exceptions.
If you are using Anymail's webhooks, you should upgrade to this release.
In addition, you may want to rotate to a new WEBHOOK_AUTHORIZATION
secret ([docs](http://anymail.readthedocs.io/en/stable/tips/securing_webhooks/#use-a-shared-authorization-secret)),
particularly if your logs indicate attempted exploit.