* 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.)
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.)
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
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.
Within an EmailAddress (previously ParsedEmail object), properties
now match Python 3.6 email.headerregistry.Address naming:
* .email --> .addr_spec
* .name --> .display_name
* .localpart --> .username
(Completes work started in 386668908423d1d4eade90cf7a21a546a1e96514;
this updates remaining uses of old names and removes them.)
SendGrid requires extra headers and metadata values be strings.
Anymail has always coerced int and float; this treats Python 2's
`long` integer type the same.
Fixes#74
[RFC-5322 allows](https://tools.ietf.org/html/rfc5322#section-3.6.2)
multiple addresses in the From header.
Django's SMTP backend supports this, as a single comma-separated
string (*not* a list of strings like the recipient params):
from_email='one@example.com, two@example.com'
to=['one@example.com', 'two@example.com']
Both Mailgun and SparkPost support multiple From addresses
(and Postmark accepts them, though truncates to the first one
on their end). For compatibility with Django -- and because
Anymail attempts to support all ESP features -- Anymail now
allows multiple From addresses, too, for ESPs that support it.
Note: as a practical matter, deliverability with multiple
From addresses is pretty bad. (Google outright rejects them.)
This change also reworks Anymail's internal ParsedEmail object,
and approach to parsing addresses, for better consistency with
Django's SMTP backend and improved error messaging.
In particular, Django (and now Anymail) allows multiple email
addresses in a single recipient string:
to=['one@example.com', 'two@example.com, three@example.com']
len(to) == 2 # but there will be three recipients
Fixes#60
* **Future breaking change:**
Rename all Anymail backends to just `EmailBackend`,
matching Django's naming convention.
(E.g., switch to "anymail.backends.mailgun.EmailBackend"
rather than "anymail.backends.mailgun.MailgunBackend".)
The old names still work, but will issue a DeprecationWarning
and will be removed in some future release.
(Apologies for this change; the old naming convention was
a holdover from Djrill, and I wanted consistency with
other Django EmailBackends before hitting 1.0.)
Fixes#49.
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
* 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
* 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
* 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.
Simplify install to just `pip install django-anymail`.
(Rather than `... django-anymail[mailgun]`
All of the ESPs so far require requests, so just move
that into the base requirements. (Chances are your
Django app already needs requests for some other
reason, anyway.)
Truly unique ESP dependencies (e.g., boto for
AWS-SES) could still use the setup extra features
mechanism.