Update (almost) all the docs

This commit is contained in:
medmunds
2016-03-09 18:37:11 -08:00
parent 8f0f2d3d83
commit 20c6350140
28 changed files with 1741 additions and 1192 deletions

View File

@@ -1,5 +1,5 @@
Djrill: Mandrill Transactional Email for Django Anymail: Multi-ESP transactional email for Django
=============================================== =================================================
.. This README is reused in multiple places: .. This README is reused in multiple places:
* Github: project page, exactly as it appears here * Github: project page, exactly as it appears here
@@ -17,53 +17,63 @@ Djrill: Mandrill Transactional Email for Django
.. This shared-intro section is also included in docs/index.rst .. This shared-intro section is also included in docs/index.rst
Djrill integrates the `Mandrill <http://mandrill.com>`_ transactional Anymail integrates several transactional email service providers (ESPs) into Django,
email service into Django. using a consistent API that makes it (relatively) easy to switch between ESPs.
**UPGRADING FROM DJRILL 1.x?** It currently supports Mailgun and Mandrill. Postmark and SendGrid are coming soon.
There are some **breaking changes in Djrill 2.0**. Please see the
`upgrade instructions <http://djrill.readthedocs.org/en/latest/upgrading/>`_. .. attention:: **EARLY DEVELOPMENT**
This project is undergoing rapid development to get to a 1.0 release.
You should expect frequent, possibly-breaking changes until 1.0 alpha.
If you are migrating to Anymail from `Djrill <https://github.com/brack3t/Djrill>`_,
there are `notes on porting <https://anymail.readthedocs.org/en/latest/esps/mandrill/#migrating-from-djrill>`_
In general, Djrill "just works" with Django's built-in `django.core.mail` Anymail normalizes ESP functionality so it "just works" with Django's
package. It includes: built-in `django.core.mail` package. It includes:
* Support for HTML, attachments, extra headers, and other features of * Support for HTML, attachments, extra headers, and other features of
`Django's built-in email <https://docs.djangoproject.com/en/stable/topics/email/>`_ `Django's built-in email <https://docs.djangoproject.com/en/stable/topics/email/>`_
* Mandrill-specific extensions like tags, metadata, tracking, and MailChimp templates * Extensions that make it easy to use extra ESP functionality, like tags, metadata,
* Optional support for Mandrill inbound email and other webhook notifications, and tracking, using code that's portable between ESPs
via Django signals * Optional support for ESP delivery status notification via webhooks and Django signals
* Optional support for inbound email
Djrill is released under the BSD license. It is tested against Django 1.4--1.9 Anymail is released under the BSD license. It is tested against Django 1.8--1.9
(including Python 3 with Django 1.6+, and PyPy support with Django 1.5+). (including Python 3 and PyPy).
Djrill uses `semantic versioning <http://semver.org/>`_. Djrill uses `semantic versioning <http://semver.org/>`_.
.. END shared-intro .. END shared-intro
.. image:: https://travis-ci.org/brack3t/Djrill.png?branch=master .. image:: https://travis-ci.org/anymail/django-anymail.png?branch=master
:target: https://travis-ci.org/brack3t/Djrill :target: https://travis-ci.org/anymail/django-anymail
:alt: build status on Travis-CI :alt: build status on Travis-CI
**Resources** **Resources**
* Full documentation: https://djrill.readthedocs.org/en/latest/ * Full documentation: https://anymail.readthedocs.org/en/latest/
* Package on PyPI: https://pypi.python.org/pypi/djrill * Package on PyPI: https://pypi.python.org/pypi/django-anymail
* Project on Github: https://github.com/brack3t/Djrill * Project on Github: https://github.com/anymail/django-anymail
Djrill 1-2-3 Anymail 1-2-3
------------ -------------
.. _quickstart: .. _quickstart:
.. This quickstart section is also included in docs/quickstart.rst .. This quickstart section is also included in docs/quickstart.rst
1. Install Djrill from PyPI: This example uses Mailgun, but you can substitute Postmark or SendGrid
or any other supported ESP where you see "mailgun":
1. Install Anymail from PyPI, including the ESP(s) you want to use:
.. code-block:: console .. code-block:: console
$ pip install djrill $ pip install anymail[mailgun] # or anymail[postmark,sendgrid] or ...
2. Edit your project's ``settings.py``: 2. Edit your project's ``settings.py``:
@@ -72,43 +82,53 @@ Djrill 1-2-3
INSTALLED_APPS = ( INSTALLED_APPS = (
... ...
"djrill" "anymail"
) )
MANDRILL_API_KEY = "<your Mandrill key>" ANYMAIL = {
EMAIL_BACKEND = "djrill.mail.backends.djrill.DjrillBackend" "MAILGUN_API_KEY": "<your Mailgun key>",
}
EMAIL_BACKEND = "anymail.backends.mailgun.MailgunBackend" # or sendgrid.SendGridBackend, or...
DEFAULT_FROM_EMAIL = "you@example.com" # if you don't already have this in settings DEFAULT_FROM_EMAIL = "you@example.com" # if you don't already have this in settings
3. Now the regular `Django email functions <https://docs.djangoproject.com/en/stable/topics/email/>`_ 3. Now the regular `Django email functions <https://docs.djangoproject.com/en/stable/topics/email/>`_
will send through Mandrill: will send through your chosen ESP:
.. code-block:: python .. code-block:: python
from django.core.mail import send_mail from django.core.mail import send_mail
send_mail("It works!", "This will get sent through Mandrill", send_mail("It works!", "This will get sent through Mailgun",
"Djrill Sender <djrill@example.com>", ["to@example.com"]) "Anymail Sender <from@example.com>", ["to@example.com"])
You could send an HTML message, complete with custom Mandrill tags and metadata: You could send an HTML message, complete with an inline image,
custom tags and metadata:
.. code-block:: python .. code-block:: python
from django.core.mail import EmailMultiAlternatives from django.core.mail import EmailMultiAlternatives
from anymail.message import attach_inline_image
msg = EmailMultiAlternatives( msg = EmailMultiAlternatives(
subject="Djrill Message", subject="Please activate your account",
body="This is the text email body", body="Click to activate your account: http://example.com/activate",
from_email="Djrill Sender <djrill@example.com>", from_email="Example <admin@example.com>",
to=["Recipient One <someone@example.com>", "another.person@example.com"], to=["New User <user1@example.com>", "account.manager@example.com"],
headers={'Reply-To': "Service <support@example.com>"} # optional extra headers reply_to=["Helpdesk <support@example.com>"])
)
msg.attach_alternative("<p>This is the HTML email body</p>", "text/html")
# Optional Mandrill-specific extensions: # Include an inline image in the html:
msg.tags = ["one tag", "two tag", "red tag", "blue tag"] logo_cid = attach_inline_image(msg, open("logo.jpg", "rb").read())
msg.metadata = {'user_id': "8675309"} html = """<img alt="Logo" src="cid:{logo_cid}">
<p>Please <a href="http://example.com/activate">activate</a>
your account</p>""".format(logo_cid=logo_cid)
msg.attach_alternative(html, "text/html")
# Optional Anymail extensions:
msg.metadata = {"user_id": "8675309", "experiment_variation": 1}
msg.tags = ["activation", "onboarding"]
msg.track_clicks = True
# Send it: # Send it:
msg.send() msg.send()
@@ -116,5 +136,5 @@ Djrill 1-2-3
.. END quickstart .. END quickstart
See the `full documentation <https://djrill.readthedocs.org/en/latest/>`_ See the `full documentation <https://anymail.readthedocs.org/en/latest/>`_
for more features and options. for more features and options.

View File

@@ -259,7 +259,7 @@ texinfo_documents = [
# -- Options for Intersphinx ------------------------------------------------ # -- Options for Intersphinx ------------------------------------------------
intersphinx_mapping = { intersphinx_mapping = {
'python': ('http://docs.python.org/2.7', None), 'python': ('http://docs.python.org/3.5', None),
'django': ('http://docs.djangoproject.com/en/stable/', 'http://docs.djangoproject.com/en/stable/_objects/'), 'django': ('http://docs.djangoproject.com/en/stable/', 'http://docs.djangoproject.com/en/stable/_objects/'),
'requests': ('http://docs.python-requests.org/en/latest/', None), 'requests': ('http://docs.python-requests.org/en/latest/', None),
} }

View File

@@ -3,31 +3,45 @@
Contributing Contributing
============ ============
Djrill is maintained by its users. Your contributions are encouraged! Anymail is maintained by its users. Your contributions are encouraged!
The `Djrill source code`_ is on github. See `AUTHORS.txt`_ for a list The `Anymail source code`_ is on GitHub.
of some of the people who have helped improve Djrill.
.. _Djrill source code: https://github.com/brack3t/Djrill .. _Anymail source code: https://github.com/anymail/django-anymail
.. _AUTHORS.txt: https://github.com/brack3t/Djrill/blob/master/AUTHORS.txt
Contributors
------------
See `AUTHORS.txt`_ for a list of some of the people who have helped
improve Anymail.
Anymail evolved from the `Djrill`_ project. Special thanks to the
folks from `brack3t`_ who developed the original version of Djrill.
.. _AUTHORS.txt: https://github.com/anymail/django-anymail/blob/master/AUTHORS.txt
.. _brack3t: http://brack3t.com/
.. _Djrill: https://github.com/brack3t/Djrill
Bugs Bugs
---- ----
You can report problems or request features in You can report problems or request features in `Anymail's GitHub issue tracker`_.
`Djrill's github issue tracker <https://github.com/brack3t/Djrill/issues>`_.
We also have some :ref:`troubleshooting` information that may be helpful. We also have some :ref:`troubleshooting` information that may be helpful.
.. _Anymail's GitHub issue tracker: https://github.com/anymail/django-anymail/issues
Pull Requests
Pull requests
------------- -------------
Pull requests are always welcome to fix bugs and improve support for Mandrill and Django features. Pull requests are always welcome to fix bugs and improve support for ESP and Django features.
* Please include test cases. * Please include test cases.
* We try to follow the `Django coding style`_ (basically, PEP 8 with longer lines OK). * We try to follow the `Django coding style`_
(basically, :pep:`8` with longer lines OK).
* By submitting a pull request, you're agreeing to release your changes under under * By submitting a pull request, you're agreeing to release your changes under under
the same BSD license as the rest of this project. the same BSD license as the rest of this project.
@@ -39,27 +53,31 @@ Pull requests are always welcome to fix bugs and improve support for Mandrill an
Testing Testing
------- -------
Djrill is `tested on Travis <https://travis-ci.org/brack3t/Djrill>`_ against several Anymail is `tested on Travis`_ against several combinations of Django
combinations of Django and Python versions. (Full list in and Python versions. (Full list in `.travis.yml`_.)
`.travis.yml <https://github.com/brack3t/Djrill/blob/master/.travis.yml>`_.)
Most of the included tests verify that Djrill constructs the expected Mandrill API Most of the included tests verify that Anymail constructs the expected ESP API
calls, without actually calling Mandrill or sending any email. So these tests calls, without actually calling the ESP's API or sending any email. So these tests
don't require a Mandrill API key, but they *do* require don't require API keys, but they *do* require `mock`_ (``pip install mock``).
`mock <http://www.voidspace.org.uk/python/mock/index.html>`_
and `six <https://pythonhosted.org/six/>`_ (``pip install mock six``).
To run the tests, either:: To run the tests, either:
python -Wall setup.py test .. code-block:: console
or:: $ python -Wall setup.py test
python -Wall runtests.py or:
.. code-block:: console
If you set the environment variable `MANDRILL_TEST_API_KEY` to a valid Mandrill $ python -Wall runtests.py
`test API key`_, there are also a handful of integration tests which will run against
the live Mandrill API. (Otherwise these live API tests are skipped.)
.. _test API key: https://mandrill.zendesk.com/hc/en-us/articles/205582447#test_key Anymail also includes some integration tests, which do call the live ESP APIs.
These integration tests require API keys (and sometimes other settings) they
get from from environment variables. Look in the ``*_integration_tests.py``
files in the `tests source`_ for specific requirements.
.. _.travis.yml: https://github.com/anymail/django-anymail/blob/master/.travis.yml
.. _tests source: https://github.com/anymail/django-anymail/blob/master/anymail/tests
.. _mock: http://www.voidspace.org.uk/python/mock/index.html
.. _tested on Travis: https://travis-ci.org/anymail/django-anymail

23
docs/esps/index.rst Normal file
View File

@@ -0,0 +1,23 @@
.. _supported-esps:
Supported ESPs
--------------
Anymail supports these ESPs. Click in for the specific
settings required and notes about any quirks or limitations:
.. these are listed in alphabetical order
.. toctree::
:maxdepth: 1
mailgun
mandrill
postmark
sendgrid
Don't see your favorite ESP here? You can suggest that
Anymail add it, or even :ref:`contribute <contributing>`
your own implementation to Anymail.

103
docs/esps/mailgun.rst Normal file
View File

@@ -0,0 +1,103 @@
.. _mailgun-backend:
Mailgun
-------
Anymail integrates with the `Mailgun <https://mailgun.com>`_
transactional email service from Rackspace, using their
REST API.
Settings
========
.. rubric:: EMAIL_BACKEND
To use Anymail's Mailgun backend, set:
.. code-block:: python
EMAIL_BACKEND = "anymail.backends.mailgun.MailgunBackend"
in your settings.py. (Watch your capitalization: Mailgun spells their name with a
lowercase "g", so Anymail does too.)
.. setting:: ANYMAIL_MAILGUN_API_KEY
.. rubric:: MAILGUN_API_KEY
Required. Your Mailgun API key:
.. code-block:: python
ANYMAIL = {
...
"MAILGUN_API_KEY": "<your API key>",
}
Anymail will also look for ``MAILGUN_API_KEY`` at the
root of the settings file if neither ``ANYMAIL["MAILGUN_API_KEY"]``
nor ``ANYMAIL_MAILGUN_API_KEY`` is set.
.. setting:: ANYMAIL_MAILGUN_API_URL
.. rubric:: MAILGUN_API_URL
The base url for calling the Mailgun API. It does not include
the sender domain. (Anymail :ref:`figures this out <mailgun-sender-domain>`
for you.)
The default is ``MAILGUN_API_URL = "https://api.mailgun.net/v3"``
(It's unlikely you would need to change this.)
.. _mailgun-sender-domain:
Email sender domain
===================
Mailgun's API requires a sender domain `in the API url <base-url>`_.
By default, Anymail will use the domain of each email's from address
as the domain for the Mailgun API.
If you need to override this default, you can use Anymail's
:attr:`esp_extra` dict, either on an individual message:
.. code-block:: python
message = EmailMessage(from_email="sales@europe.example.com", ...)
message.esp_extra = {"sender_domain": "example.com"}
... or as a global :ref:`send default <send-defaults>` setting that applies
to all messages:
.. code-block:: python
ANYMAIL = {
...
"MAILGUN_SEND_DEFAULTS": {
"esp_extra": {"sender_domain": "example.com"}
}
}
.. _base-url: https://documentation.mailgun.com/api-intro.html#base-url
Mailgun esp_extra
=================
Anymail's Mailgun backend will pass all :attr:`~anymail.message.AnymailMessage.esp_extra`
values directly to Mailgun. You can use any of the (non-file) parameters listed in the
`Mailgun sending docs`_. Example:
.. code-block:: python
message = AnymailMessage(...)
message.esp_extra = {
'o:testmode': 'yes', # use Mailgun's test mode
}
.. _Mailgun sending docs: https://documentation.mailgun.com/api-sending.html#sending

151
docs/esps/mandrill.rst Normal file
View File

@@ -0,0 +1,151 @@
.. _mandrill-backend:
Mandrill
--------
Anymail integrates with the `Mandrill <http://mandrill.com/>`_
transactional email service from MailChimp.
Settings
========
.. rubric:: EMAIL_BACKEND
To use Anymail's Mandrill backend, set:
.. code-block:: python
EMAIL_BACKEND = "anymail.backends.mandrill.MandrillBackend"
in your settings.py.
.. setting:: ANYMAIL_MANDRILL_API_KEY
.. rubric:: MANDRILL_API_KEY
Required. Your Mandrill API key:
.. code-block:: python
ANYMAIL = {
...
"MANDRILL_API_KEY": "<your API key>",
}
Anymail will also look for ``MANDRILL_API_KEY`` at the
root of the settings file if neither ``ANYMAIL["MANDRILL_API_KEY"]``
nor ``ANYMAIL_MANDRILL_API_KEY`` is set.
.. setting:: ANYMAIL_MANDRILL_API_URL
.. rubric:: MANDRILL_API_URL
The base url for calling the Mandrill API. The default is
``MANDRILL_API_URL = "https://mandrillapp.com/api/1.0"``,
which is the secure, production version of Mandrill's 1.0 API.
(It's unlikely you would need to change this.)
Mandrill esp_extra
==================
Anymail's Mandrill backend does not yet implement the
:attr:`~anymail.message.AnymailMessage.esp_extra` feature.
.. _migrating-from-djrill:
Migrating from Djrill
=====================
Anymail has its origins as a fork of the `Djrill`_
package, which supported only Mandrill. If you are migrating
from Djrill to Anymail -- e.g., because you are thinking
of switching ESPs -- you'll need to make a few changes
to your code.
.. _Djrill: https://github.com/brack3t/Djrill
Changes to settings
~~~~~~~~~~~~~~~~~~~
``MANDRILL_API_KEY``
Will still work, but consider moving it into the :setting:`ANYMAIL`
settings dict, or changing it to :setting:`ANYMAIL_MANDRILL_API_KEY`.
``MANDRILL_SETTINGS``
Use :setting:`ANYMAIL_SEND_DEFAULTS` and/or :setting:`ANYMAIL_MANDRILL_SEND_DEFAULTS`
(see :ref:`send-defaults`).
There is one slight behavioral difference between :setting:`ANYMAIL_SEND_DEFAULTS`
and Djrill's ``MANDRILL_SETTINGS``: in Djrill, setting :attr:`tags` or
:attr:`merge_vars` on a message would completely override any global
settings defaults. In Anymail, those message attributes are merged with
the values from :setting:`ANYMAIL_SEND_DEFAULTS`.
``MANDRILL_SUBACCOUNT``
Use :attr:`esp_extra` in :setting:`ANYMAIL_MANDRILL_SEND_DEFAULTS`:
.. code-block:: python
ANYMAIL = {
...
"MANDRILL_SEND_DEFAULTS": {
"esp_extra": {"subaccount": "<your subaccount>"}
}
}
``MANDRILL_IGNORE_RECIPIENT_STATUS``
Renamed to :setting:`ANYMAIL_IGNORE_RECIPIENT_STATUS`
(or just `IGNORE_RECIPIENT_STATUS` in the :setting:`ANYMAIL`
settings dict).
Changes to EmailMessage attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``message.send_at``
If you are using an aware datetime for :attr:`send_at`,
it will keep working unchanged with Anymail.
If you are using a date (without a time), or a naive datetime,
be aware that these now default to Django's current_timezone,
rather than UTC as in Djrill.
(As with Djrill, it's best to use an aware datetime
that says exactly when you want the message sent.)
``message.mandrill_response``
Anymail normalizes ESP responses, so you don't have to be familiar
with the format of Mandrill's JSON. See :attr:`anymail_status`.
The *raw* ESP response is attached to a sent message as
``anymail_status.esp_response``, so the direct replacement
for message.mandrill_response is:
.. code-block:: python
mandrill_response = message.anymail_status.esp_response.json()
**Templates and merge variables**
Coming to Anymail soon.
However, no other ESPs support MailChimp's templating language, so
you'll need to rewrite your templates as you switch ESPs.
Consider converting to :ref:`Django templates <django-templates>`
instead, as these can be used with any email backend.
**Other Mandrill-specific attributes**
Are currently still supported by Anymail's Mandrill backend,
but will be ignored by other Anymail backends.
It's best to eliminate them if they're not essential
to your code. In the future, the Mandrill-only attributes
will be moved into the
:attr:`~anymail.message.AnymailMessage.esp_extra` dict.

17
docs/esps/postmark.rst Normal file
View File

@@ -0,0 +1,17 @@
.. _postmark:
Postmark
--------
.. note::
Postmark support coming soon
Settings
========
EMAIL_BACKEND = "anymail.backends.postmark.PostmarkBackend"
(Watch your capitalization: Postmark spells their name with a
lowercase "m", so Anymail does too.)

17
docs/esps/sendgrid.rst Normal file
View File

@@ -0,0 +1,17 @@
.. _sendgrid:
SendGrid
--------
.. note::
SendGrid support is being developed now
Settings
========
EMAIL_BACKEND = "anymail.backends.sendgrid.SendGridBackend"
(Watch your capitalization: SendGrid spells their name with an
uppercase "G", so Anymail does too.)

View File

@@ -1,146 +0,0 @@
.. _history:
Release Notes
=============
Djrill practices `semantic versioning <semver>`_.
Among other things, this means that minor updates
(1.x to 1.y) should always be backwards-compatible,
and breaking changes will always increment the
major version number (1.x to 2.0).
Djrill 2.x
----------
Version 2.1 (in development):
* Handle Mandrill rejection whitelist/blacklist sync event webhooks
Version 2.0:
* **Breaking Changes:** please see the :ref:`upgrade guide <upgrading>`.
* Add Django 1.9 support; drop Django 1.3, Python 2.6, and Python 3.2 support
* Add global :setting:`MANDRILL_SETTINGS` dict that can provide defaults
for most Djrill message options
* Add :exc:`djrill.NotSerializableForMandrillError`
* Use a single HTTP connection to the Mandrill API to improve performance
when sending multiple messages at once using :func:`~django.core.mail.send_mass_mail`.
(You can also directly manage your own long-lived Djrill connection across multiple sends,
by calling open and close on :ref:`Django's email backend <django:topic-email-backends>`.)
* Add Djrill version to user-agent header when calling Mandrill API
* Improve diagnostics in exceptions from Djrill
* Remove DjrillAdminSite
* Remove unintended date-to-string conversion in JSON encoding
* Remove obsolete DjrillMessage class and DjrillBackendHTTPError
* Refactor Djrill backend and exceptions
Djrill 1.x and Earlier
----------------------
Version 1.4:
* Django 1.8 support
* Support new Django 1.8 EmailMessage reply_to param.
(Specifying a :ref:`Reply-To header <message-headers>`
still works, with any version of Django,
and will override the reply_to param if you use both.)
* Include Mandrill error response in str(MandrillAPIError),
to make errors easier to understand.
* More-helpful exception when using a non-JSON-serializable
type in merge_vars and other Djrill message attributes
* Deprecation warnings for upcoming 2.0 changes (see above)
Version 1.3:
* Use Mandrill secure https API endpoint (rather than http).
* Support :attr:`merge_language` option (for choosing between
Handlebars and Mailchimp templates).
Version 1.2:
* Support Django 1.7; add testing on Python 3.3, 3.4, and PyPy
* Bug fixes
Version 1.1:
* Allow use of Mandrill template default "from" and "subject" fields,
via :attr:`use_template_from` and :attr:`use_template_subject`.
* Fix `UnicodeEncodeError` with unicode attachments
Version 1.0:
* Global :setting:`MANDRILL_SUBACCOUNT` setting
Version 0.9:
* Better handling for "cc" and "bcc" recipients.
* Allow all extra message headers in send.
(Mandrill has relaxed previous API restrictions on headers.)
Version 0.8:
* Expose :ref:`mandrill-response` on sent messages
Version 0.7:
* Support for Mandrill send options :attr:`async`, :attr:`important`,
:attr:`ip_pool`, :attr:`return_path_domain`, :attr:`send_at`,
:attr:`subaccount`, and :attr:`view_content_link`
Version 0.6:
* Support for signed webhooks
Version 0.5:
* Support for incoming mail and other Mandrill webhooks
* Support for Mandrill send options :attr:`auto_html`, :attr:`tracking_domain`
and :attr:`signing_domain`.
Version 0.4:
* Attachments with a Content-ID are now treated as
:ref:`embedded images <sending-attachments>`
* New Mandrill :attr:`inline_css` option is supported
* Remove limitations on attachment types, to track Mandrill change
* Documentation is now available on
`djrill.readthedocs.org <https://djrill.readthedocs.org>`_
Version 0.3:
* :ref:`Attachments <sending-attachments>` are now supported
* :ref:`Mandrill templates <mandrill-templates>` are now supported
* A bcc address is now passed to Mandrill as bcc, rather than being lumped in
with the "to" recipients. Multiple bcc recipients will now raise an exception,
as Mandrill only allows one.
* Python 3 support (with Django 1.5)
* Exceptions should be more useful:
:exc:`djrill.NotSupportedByMandrillError` replaces generic ValueError;
:exc:`djrill.MandrillAPIError` replaces DjrillBackendHTTPError, and is now
derived from requests.HTTPError.
(New exceptions are backwards compatible with old ones for existing code.)
Version 0.2:
* ``MANDRILL_API_URL`` is no longer required in settings.py
* Earlier versions of Djrill required use of a ``DjrillMessage`` class to
specify Mandrill-specific options. This is no longer needed -- Mandrill
options can now be set directly on a Django ``EmailMessage`` object or any
subclass. (Existing code can continue to use ``DjrillMessage``.)
.. _semver: http://semver.org

18
docs/inbound.rst Normal file
View File

@@ -0,0 +1,18 @@
.. _inbound:
Receiving inbound email
=======================
.. note::
Normalized inbound email handling is coming soon to Anymail.
.. _inbound-webhooks:
Configuring inbound webhooks
----------------------------
Inbound email signals
---------------------

View File

@@ -1,10 +1,5 @@
.. Djrill documentation master file, created by Anymail: Multi-ESP transactional email for Django
sphinx-quickstart on Sat Mar 2 13:07:34 2013. =================================================
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Djrill: Mandrill Transactional Email for Django
===============================================
Version |release| Version |release|
@@ -15,6 +10,8 @@ Version |release|
:end-before: END shared-intro :end-before: END shared-intro
.. _main-toc:
Documentation Documentation
------------- -------------
@@ -23,21 +20,10 @@ Documentation
quickstart quickstart
installation installation
upgrading sending/index
usage/sending_mail inbound
usage/templates esps/index
usage/multiple_backends tips/index
usage/webhooks
troubleshooting troubleshooting
contributing contributing
history release_notes
Thanks
------
Thanks to the MailChimp team for asking us to build this nifty little app, and to all of Djrill's
:doc:`contributors <contributing>`.
Oh, and, of course, Kenneth Reitz for the awesome requests_ library.
.. _requests: http://docs.python-requests.org

View File

@@ -1,117 +1,167 @@
Installation Installation and configuration
============ ==============================
It's easiest to install Djrill from `PyPI <https://pypi.python.org/pypi/djrill>`_: .. _installation:
Installing Anymail
------------------
Install Anymail from PyPI using pip.
Anymail uses python setuptools' "extra features" to pull in dependencies
for specific ESPs. (This avoids installing packages needed by ESPs
you aren't using.)
You'll want to include at least one ESP as an extra in your pip command.
E.g., for Anymail with Mailgun support:
.. code-block:: console .. code-block:: console
$ pip install djrill $ pip install anymail[mailgun]
If you decide to install Djrill some other way, you'll also need to install its ...or with both Postmark and SendGrid support:
one dependency (other than Django, of course): the `requests <http://docs.python-requests.org>`_
library from Kenneth Reitz. .. code-block:: console
$ pip install anymail[postmark,sendgrid]
Configuration .. _backend-configuration:
-------------
.. setting:: MANDRILL_API_KEY Configuring Django's email backend
----------------------------------
In your project's :file:`settings.py`: To use Anymail for sending email, edit your Django project's :file:`settings.py`:
1. Add :mod:`djrill` to your :setting:`INSTALLED_APPS`:: 1. Add :mod:`anymail` to your :setting:`INSTALLED_APPS`:
.. code-block:: python
INSTALLED_APPS = ( INSTALLED_APPS = (
... ...
"djrill" "anymail",
) )
2. Add the following line, substituting your own :setting:`MANDRILL_API_KEY`:: 2. Add an :setting:`ANYMAIL` settings dict, substituting the appropriate settings for
your ESP:
MANDRILL_API_KEY = "brack3t-is-awesome" .. code-block:: python
3. Override your existing :setting:`EMAIL_BACKEND` with the following line:: ANYMAIL = {
"MAILGUN_API_KEY" = "<your Mailgun key>",
EMAIL_BACKEND = "djrill.mail.backends.djrill.DjrillBackend"
Also, if you don't already have a :setting:`DEFAULT_FROM_EMAIL` in settings,
this is a good time to add one. (Django's default is "webmaster@localhost",
which won't work with Mandrill.)
Mandrill Webhooks (Optional)
----------------------------
Djrill includes optional support for Mandrill webhooks, including inbound email.
See the Djrill :ref:`webhooks <webhooks>` section for configuration details.
Other Optional Settings
-----------------------
You can optionally add any of these Djrill settings to your :file:`settings.py`.
.. setting:: MANDRILL_IGNORE_RECIPIENT_STATUS
MANDRILL_IGNORE_RECIPIENT_STATUS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Set to ``True`` to disable :exc:`djrill.MandrillRecipientsRefused` exceptions
on invalid or rejected recipients. (Default ``False``.)
.. versionadded:: 2.0
.. setting:: MANDRILL_SETTINGS
MANDRILL_SETTINGS
~~~~~~~~~~~~~~~~~
You can supply global default options to apply to all messages sent through Djrill.
Set :setting:`!MANDRILL_SETTINGS` to a dict of these options. Example::
MANDRILL_SETTINGS = {
'subaccount': 'client-347',
'tracking_domain': 'example.com',
'track_opens': True,
} }
See :ref:`mandrill-send-support` for a list of available options. (Everything 3. Change your existing Django :setting:`EMAIL_BACKEND` to the Anymail backend
*except* :attr:`merge_vars`, :attr:`recipient_metadata`, and :attr:`send_at` for your ESP. For example, to send using Mailgun by default:
can be used with :setting:`!MANDRILL_SETTINGS`.)
Attributes set on individual EmailMessage objects will override the global .. code-block:: python
:setting:`!MANDRILL_SETTINGS` for that message. :attr:`global_merge_vars`
on an EmailMessage will be merged with any ``global_merge_vars`` in
:setting:`!MANDRILL_SETTINGS` (with the ones on the EmailMessage taking
precedence if there are conflicting var names).
.. versionadded:: 2.0 EMAIL_BACKEND = "anymail.backends.mailgun.MailgunBackend"
(:setting:`EMAIL_BACKEND` sets Django's default for sending emails; you can also
use :ref:`multiple Anymail backends <multiple-backends>` to send particular
messages through different ESPs.)
The exact backend name and required settings vary by ESP.
See the :ref:`supported ESPs <supported-esps>` section for specifics.
Also, if you don't already have a :setting:`DEFAULT_FROM_EMAIL` in your settings,
this is a good time to add one. (Django's default is "webmaster\@localhost",
which some ESPs will reject.)
.. setting:: MANDRILL_API_URL Configuring status tracking webhooks
------------------------------------
MANDRILL_API_URL Anymail can optionally connect to your ESPs event webhooks to notify your app
~~~~~~~~~~~~~~~~ of status like bounced and rejected emails, successful delivery, message opens
and clicks, and other tracking.
The base url for calling the Mandrill API. The default is If you want to use Anymail's status tracking webhooks, follow the steps above
``MANDRILL_API_URL = "https://mandrillapp.com/api/1.0"``, to :ref:`configure an Anymail backend <backend-configuration>`, and then
which is the secure, production version of Mandrill's 1.0 API. follow the instructions in the :ref:`event-tracking` section to set up
the delivery webhooks.
(It's unlikely you would need to change this.)
.. setting:: MANDRILL_SUBACCOUNT Configuring inbound email
-------------------------
MANDRILL_SUBACCOUNT Anymail can optionally connect to your ESPs inbound webhook to notify your app
~~~~~~~~~~~~~~~~~~~ of inbound messages.
Prior to Djrill 2.0, the :setting:`!MANDRILL_SUBACCOUNT` setting could If you want to use inbound email with Anymail, first follow the first two
be used to globally set the `Mandrill subaccount <subaccounts>`_. :ref:`backend configuration <backend-configuration>` steps above. (You can
Although this is still supported for compatibility with existing code, skip changing your :setting:`EMAIL_BACKEND` if you don't want to us Anymail
new code should set a global subaccount in :setting:`MANDRILL_SETTINGS` for *sending* messages.) Then follow the instructions in the
as shown above. :ref:`inbound-webhooks` section to set up the inbound webhooks.
.. _subaccounts: http://help.mandrill.com/entries/25523278-What-are-subaccounts-
.. setting:: ANYMAIL
Anymail settings reference
--------------------------
You can add Anymail settings to your project's :file:`settings.py` either as
a single ``ANYMAIL`` dict, or by breaking out individual settings prefixed with
``ANYMAIL_``. So this settings dict:
.. code-block:: python
ANYMAIL = {
"MAILGUN_API_KEY": "12345",
"SEND_DEFAULTS": {
"tags": ["myapp"]
},
}
...is equivalent to these individual settings:
.. code-block:: python
ANYMAIL_MAILGUN_API_KEY = "12345"
ANYMAIL_SEND_DEFAULTS = {"tags": ["myapp"]}
In addition, for some ESP settings like API keys, Anymail will look for a setting
without the ``ANYMAIL_`` prefix if it can't find the Anymail one. (This can be helpful
if you are using other Django apps that work with the same ESP.)
.. code-block:: python
MAILGUN_API_KEY = "12345" # used only if neither ANYMAIL["MAILGUN_API_KEY"]
# nor ANYMAIL_MAILGUN_API_KEY have been set
There are specific Anymail settings for each ESP (like API keys and urls).
See the :ref:`supported ESPs <supported-esps>` section for details.
Here are the other settings Anymail supports:
.. setting:: ANYMAIL_IGNORE_RECIPIENT_STATUS
.. rubric:: IGNORE_RECIPIENT_STATUS
Set to `True` to disable :exc:`AnymailRecipientsRefused` exceptions
on invalid or rejected recipients. (Default `False`.)
See :ref:`recipients-refused`.
.. code-block:: python
ANYMAIL = {
...
"IGNORE_RECIPIENT_STATUS": True,
}
.. rubric:: SEND_DEFAULTS and *ESP*\ _SEND_DEFAULTS`
A `dict` of default options to apply to all messages sent through Anymail.
See :ref:`send-defaults`.
.. rubric:: UNSUPPORTED_FEATURE_ERRORS
Whether Anymail should raise :exc:`~anymail.exceptions.AnymailUnsupportedFeature`
errors for email with features that can't be accurately communicated to the ESP.
Set to `False` to ignore these problems and send the email anyway. See
:ref:`unsupported-features`. (Default `True`.)

View File

@@ -1,5 +1,5 @@
Djrill 1-2-3 Anymail 1-2-3
============ =============
.. Quickstart is maintained in README.rst at the source root. .. Quickstart is maintained in README.rst at the source root.
(Docs can include from the readme; the readme can't include anything.) (Docs can include from the readme; the readme can't include anything.)
@@ -7,3 +7,16 @@ Djrill 1-2-3
.. include:: ../README.rst .. include:: ../README.rst
:start-after: _quickstart: :start-after: _quickstart:
:end-before: END quickstart :end-before: END quickstart
Problems? We have some :ref:`troubleshooting` info that may help.
.. rubric:: Now what?
Now that you've got Anymail working, you might be interested in:
* :ref:`Sending email with Anymail <sending-email>`
* :ref:`Receiving inbound email <inbound>`
* :ref:`ESP-specific information <supported-esps>`
* :ref:`All the docs <main-toc>`

23
docs/release_notes.rst Normal file
View File

@@ -0,0 +1,23 @@
.. _release_notes:
Release notes
=============
Complete release notes can be found in the project's
`GitHub releases page`_.
Anymail practices `semantic versioning <semver>`_.
Among other things, this means that minor updates
(1.x to 1.y) should always be backwards-compatible,
and breaking changes will always increment the
major version number (1.x to 2.0).
.. rubric:: PRE-1.0 DEVELOPMENT VERSIONS
Anymail is under active, early development right now.
Prior to a 1.0 alpha, features and APIs may change
rapidly. (Per semver, the 0.x minor version will get
bumped for any breaking changes before 1.0.)
.. _GitHub releases page: https://github.com/anymail/django-anymail/releases
.. _semver: http://semver.org

View File

@@ -0,0 +1,449 @@
.. _anymail-send-features:
.. module:: anymail.message
Anymail additions
=================
Anymail normalizes several common ESP features, like adding
metadata or tags to a message. It also normalizes the response
from the ESP's send API.
There are three ways you can use Anymail's ESP features with
your Django email:
* Just use Anymail's added attributes directly on *any* Django
:class:`~django.core.mail.EmailMessage` object (or any subclass).
* Create your email message using the :class:`AnymailMessage` class,
which exposes extra attributes for the ESP features.
* Use the :class:`AnymailMessageMixin` to add the Anymail extras
to some other EmailMessage-derived class (your own or from
another Django package).
The first approach is usually the simplest. The other two can be
helpful if you are working with Python development tools that
offer type checking or other static code analysis.
ESP send options (AnymailMessage)
---------------------------------
.. class:: AnymailMessage
A subclass of Django's :class:`~django.core.mail.EmailMultiAlternatives`
that exposes additional ESP functionality.
The constructor accepts any of the attributes below, or you can set
them directly on the message at any time before sending:
.. code-block:: python
from anymail.message import AnymailMessage
message = AnymailMessage(
subject="Welcome",
body="Welcome to our site",
to=["New User <user1@example.com>"],
tags=["Onboarding"], # Anymail extra in constructor
)
# Anymail extra attributes:
message.metadata = {"onboarding_experiment": "variation 1"}
message.track_clicks = True
message.send()
status = message.anymail_status # available after sending
status.message_id # e.g., '<12345.67890@example.com>'
status.recipients["user1@example.com"].status # e.g., 'queued'
.. rubric:: Attributes you can add to messages
.. attribute:: metadata
Set this to a `dict` of metadata values the ESP should store
with the message, for later search and retrieval.
.. code-block:: python
message.metadata = {"customer": customer.id,
"order": order.reference_number}
ESPs have differing restrictions on metadata content.
For portability, it's best to stick to alphanumeric keys, and values
that are numbers or strings.
You should format any non-string data into a string before setting it
as metadata. See :ref:`formatting-merge-data`.
.. attribute:: tags
Set this to a `list` of `str` tags to apply to the message (usually
for segmenting ESP reporting).
.. code-block:: python
message.tags = ["Order Confirmation", "Test Variant A"]
ESPs have differing restrictions on tags. For portability,
it's best to stick with strings that start with an alphanumeric
character. (Also, PostMark only allows a single tag per message.)
.. caution::
Some ESPs put :attr:`metadata` and :attr:`tags` in email headers,
which are included with the email when it is delivered. Anything you
put in them **could be exposed to the recipients,** so don't
include sensitive data.
.. attribute:: track_opens
Set this to `True` or `False` to override your ESP account default
setting for tracking when users open a message.
.. code-block:: python
message.track_opens = True
.. attribute:: track_clicks
Set this to `True` or `False` to override your ESP account default
setting for tracking when users click on a link in a message.
.. code-block:: python
message.track_clicks = False
.. attribute:: send_at
Set this to a `~datetime.datetime`, `~datetime.date` to
have the ESP wait until the specified time to send the message.
(You can also use a `float` or `int`, which will be treated
as a POSIX timestamp as in :func:`time.time`.)
.. code-block:: python
from datetime import datetime, timedelta
from django.utils.timezone import utc
message.send_at = datetime.now(utc) + timedelta(hours=1)
To avoid confusion, it's best to provide either an *aware*
`~datetime.datetime` (one that has its tzinfo set), or an
`int` or `float` seconds-since-the-epoch timestamp.
If you set :attr:`!send_at` to a `~datetime.date` or a *naive*
`~datetime.datetime` (without a timezone), Anymail will interpret it in
Django's :ref:`current timezone <django:default-current-time-zone>`.
(Careful: :meth:`datetime.now() <datetime.datetime.now>` returns a *naive*
datetime, unless you call it with a timezone like in the example above.)
The sent message will be held for delivery by your ESP -- not locally by Anymail.
.. attribute:: esp_extra
Set this to a `dict` of additional, ESP-specific settings for the message.
Using this attribute is inherently non-portable between ESPs, and is
intended as an "escape hatch" for accessing functionality that Anymail
doesn't (or doesn't yet) support.
See the notes for each :ref:`specific ESP <supported-esps>` for information
on its :attr:`!esp_extra` handling.
.. rubric:: Status response from the ESP
.. attribute:: anymail_status
Normalized response from the ESP API's send call. Anymail adds this
to each :class:`~django.core.mail.EmailMessage` as it is sent.
The value is an :class:`AnymailStatus`.
See :ref:`esp-send-status` for details.
.. rubric:: Convenience methods
(These methods are only available on :class:`AnymailMessage` or
:class:`AnymailMessageMixin` objects. Unlike the attributes above,
they can't be used on an arbitrary :class:`~django.core.mail.EmailMessage`.)
.. method:: attach_inline_image(content, subtype=None, idstring="img", domain=None)
Attach an inline (embedded) image to the message and return its :mailheader:`Content-ID`.
This calls :func:`attach_inline_image` on the message. See :ref:`inline-images`
for details and an example.
.. _esp-send-status:
ESP send status
---------------
.. class:: AnymailStatus
When you send a message through an Anymail backend, Anymail adds
an :attr:`~AnymailMessage.anymail_status` attribute to the
:class:`~django.core.mail.EmailMessage`, with a normalized version
of the ESP's response.
:attr:`~AnymailMessage.anymail_status` will be an object with these attributes:
.. attribute:: message_id
The message id assigned by the ESP, or `None` if the send call failed.
The exact format varies by ESP. Some use a UUID or similar;
some use an :rfc:`2822` :mailheader:`Message-ID` as the id:
.. code-block:: python
message.anymail_status.message_id
# '<20160306015544.116301.25145@example.org>'
Some ESPs assign a unique message ID for *each recipient* (to, cc, bcc)
of a single message. In that case, :attr:`!message_id` will be a
`set` of all the message IDs across all recipients:
.. code-block:: python
message.anymail_status.message_id
# set(['16fd2706-8baf-433b-82eb-8c7fada847da',
# '886313e1-3b8a-5372-9b90-0c9aee199e5d'])
.. attribute:: status
A `set` of send statuses, across all recipients (to, cc, bcc) of the
message, or `None` if the send call failed.
.. code-block:: python
message1.anymail_status.status
# set(['queued']) # all recipients were queued
message2.anymail_status.status
# set(['rejected', 'sent']) # at least one recipient was sent,
# and at least one rejected
# This is an easy way to check there weren't any problems:
if message3.anymail_status.status.issubset({'queued', 'sent'}):
print("ok!")
Anymail normalizes ESP sent status to one of these values:
* `'sent'` the ESP has sent the message
(though it may or may not end up delivered)
* `'queued'` the ESP has accepted the message
and will try to send it asynchronously
* `'invalid'` the ESP considers the sender or recipient email invalid
* `'rejected'` the recipient is on an ESP blacklist
(unsubscribe, previous bounces, etc.)
* `'failed'` the attempt to send failed for some other reason
* `'unknown'` anything else
Not all ESPs check recipient emails during the send API call -- some
simply queue the message, and report problems later. In that case,
you can use Anymail's :ref:`event-tracking` features to be notified
of delivery status events.
.. attribute:: recipients
A `dict` of per-recipient message ID and status values.
The dict is keyed by each recipient's base email address
(ignoring any display name). Each value in the dict is
an object with `status` and `message_id` properties:
.. code-block:: python
message = EmailMultiAlternatives(
to=["you@example.com", "Me <me@example.com>"],
subject="Re: The apocalypse")
message.send()
message.anymail_status.recipient["you@example.com"].status
# 'sent'
message.anymail_status.recipient["me@example.com"].status
# 'queued'
message.anymail_status.recipient["me@example.com"].message_id
# '886313e1-3b8a-5372-9b90-0c9aee199e5d'
Will be an empty dict if the send call failed.
.. attribute:: esp_response
The raw response from the ESP API call. The exact type varies by
backend. Accessing this is inherently non-portable.
.. code-block:: python
# This will work with a requests-based backend:
message.anymail_status.esp_response.json()
.. _inline-images:
Inline images
-------------
Anymail includes a convenience function to simplify attaching inline images to email.
.. function:: attach_inline_image(message, content, subtype=None, idstring="img", domain=None)
Attach an inline (embedded) image to the message and return its :mailheader:`Content-ID`.
In your HTML message body, prefix the returned id with `cid:` to make an
`<img>` src attribute:
.. code-block:: python
from django.core.mail import EmailMultiAlternatives
from anymail.message import attach_inline_image
# read image content -- be sure to open the file in binary mode:
with f = open("path/to/picture.jpg", "rb"):
raw_image_data = f.read()
message = EmailMultiAlternatives( ... )
cid = attach_inline_image(message, raw_image_data)
html = '... <img alt="Picture" src="cid:%s"> ...' % cid
message.attach_alternative(html, "text/html")
message.send()
`message` must be an :class:`~django.core.mail.EmailMessage` (or subclass) object.
`content` must be the binary image data (e.g., read from a file).
`subtype` is an optional MIME :mimetype:`image` subtype, e.g., `"png"` or `"jpg"`.
By default, this is determined automatically from the content.
`idstring` and `domain` are optional, and are passed to Python's
:func:`~email.utils.make_msgid` to generate the :mailheader:`Content-ID`.
Generally the defaults should be fine.
(But be aware the default `domain` can leak your server's local hostname
in the resulting email.)
This function works with *any* Django :class:`~django.core.mail.EmailMessage` --
it's not specific to Anymail email backends. You can use it with messages sent
through Django's SMTP backend or any other that properly supports MIME attachments.
(This function is also available as the
:meth:`~anymail.message.AnymailMessage.attach_inline_image` method
on Anymail's :class:`~anymail.message.AnymailMessage` and
:class:`~anymail.message.AnymailMessageMixin` classes.)
.. _send-defaults:
Global send defaults
--------------------
.. setting:: ANYMAIL_SEND_DEFAULTS
In your :file:`settings.py`, you can set :setting:`!ANYMAIL_SEND_DEFAULTS`
to a `dict` of default options that will apply to all messages sent through Anymail:
.. code-block:: python
ANYMAIL = {
...
"SEND_DEFAULTS": {
"metadata": {"district": "North", "source": "unknown"},
"tags": ["myapp", "version3"],
"track_clicks": True,
"track_opens": True,
},
}
At send time, the attributes on each :class:`~django.core.mail.EmailMessage`
get merged with the global send defaults. For example, with the
settings above:
.. code-block:: python
message = AnymailMessage(...)
message.tags = ["welcome"]
message.metadata = {"source": "Ads", "user_id": 12345}
message.track_clicks = False
message.send()
# will send with:
# tags: ["myapp", "version3", "welcome"] (merged with defaults)
# metadata: {"district": "North", "source": "Ads", "user_id": 12345} (merged)
# track_clicks: False (message overrides defaults)
# track_opens: True (from the defaults)
To prevent a message from using a particular global default, set that attribute
to `None`. (E.g., ``message.tags = None`` will send the message with no tags,
ignoring the global default.)
Anymail's send defaults actually work for all :class:`!django.core.mail.EmailMessage`
attributes. So you could set ``"bcc": ["always-copy@example.com"]`` to add a bcc
to every message. (You could even attach a file to every message -- though
your recipients would probably find that annoying!)
You can also set ESP-specific global defaults. If there are conflicts,
the ESP-specific value will override the main `SEND_DEFAULTS`:
.. code-block:: python
ANYMAIL = {
...
"SEND_DEFAULTS": {
"tags": ["myapp", "version3"],
},
"POSTMARK_SEND_DEFAULTS": {
# Postmark only supports a single tag
"tags": ["version3"], # overrides SEND_DEFAULTS['tags'] (not merged!)
},
"MAILGUN_SEND_DEFAULTS": {
"esp_extra": {"o:dkim": "no"}, # Disable Mailgun DKIM signatures
},
}
AnymailMessageMixin
-------------------
.. class:: AnymailMessageMixin
Mixin class that adds Anymail's ESP extra attributes and convenience methods
to other :class:`~django.core.mail.EmailMessage` subclasses.
For example, with the `django-mail-templated`_ package's custom EmailMessage:
.. code-block:: python
from anymail.message import AnymailMessageMixin
from mail_templated import EmailMessage
class TemplatedAnymailMessage(AnymailMessageMixin, EmailMessage):
"""
An EmailMessage that supports both Mail-Templated
and Anymail features
"""
pass
msg = TemplatedAnymailMessage(
template_name="order_confirmation.tpl", # Mail-Templated arg
track_opens=True, # Anymail arg
...
)
msg.context = {"order_num": "12345"} # Mail-Templated attribute
msg.tags = ["templated"] # Anymail attribute
.. _django-mail-templated: https://pypi.python.org/pypi/django-mail-templated

View File

@@ -0,0 +1,162 @@
.. currentmodule:: anymail
.. _sending-django-email:
Django email support
====================
Anymail builds on Django's core email functionality. If you are already sending
email using Django's default SMTP :class:`~django.core.mail.backends.smtp.EmailBackend`,
switching to Anymail will be easy. Anymail is designed to "just work" with Django.
If you're not familiar with Django's email functions, please take a look at
":mod:`sending email <django.core.mail>`" in the Django docs first.
Anymail supports most of the functionality of Django's :class:`~django.core.mail.EmailMessage`
and :class:`~django.core.mail.EmailMultiAlternatives` classes.
Anymail handles **all** outgoing email sent through Django's
:mod:`django.core.mail` package, including :func:`~django.core.mail.send_mail`,
:func:`~django.core.mail.send_mass_mail`, the :class:`~django.core.mail.EmailMessage` class,
and even :func:`~django.core.mail.mail_admins`.
If you'd like to selectively send only some messages through Anymail,
or you'd like to use different ESPs for particular messages,
there are ways to use :ref:`multiple email backends <multiple-backends>`.
.. _sending-html:
HTML email
----------
To send an HTML message, you can simply use Django's :func:`~django.core.mail.send_mail`
function with the ``html_message`` parameter:
.. code-block:: python
from django.core.mail import send_mail
send_mail("Subject", "text body", "from@example.com",
["to@example.com"], html_message="<html>html body</html>")
However, many Django email capabilities -- and additional Anymail features --
are only available when working with an :class:`~django.core.mail.EmailMultiAlternatives`
object. Use its :meth:`~django.core.mail.EmailMultiAlternatives.attach_alternative`
method to send HTML:
.. code-block:: python
from django.core.mail import EmailMultiAlternatives
msg = EmailMultiAlternatives("Subject", "text body",
"from@example.com", ["to@example.com"])
msg.attach_alternative("<html>html body</html>", "text/html")
# you can set any other options on msg here, then...
msg.send()
It's good practice to send equivalent content in your plain-text body
and the html version.
.. _sending-attachments:
Attachments
-----------
Anymail will send a message's attachments to your ESP. You can add attachments
with the :meth:`~django.core.mail.EmailMessage.attach` or
:meth:`~django.core.mail.EmailMessage.attach_file` methods
of Django's :class:`~django.core.mail.EmailMessage`.
Note that some ESPs impose limits on the size and type of attachments they
will send.
.. rubric:: Inline images
If your message has any image attachments with :mailheader:`Content-ID` headers,
Anymail will tell your ESP to treat them as inline images rather than ordinary
attached files.
You can construct an inline image attachment yourself with Python's
:class:`python:email.mime.image.MIMEImage`, or you can use the convenience
function :func:`~message.attach_inline_image` included with
Anymail. See :ref:`inline-images` in the "Anymail additions" section.
.. _message-headers:
Additional headers
------------------
Anymail passes additional headers to your ESP. (Some ESPs may limit
which headers they'll allow.)
.. code-block:: python
msg = EmailMessage( ...
headers={
"List-Unsubscribe": unsubscribe_url,
"X-Example-Header": "myapp",
}
)
.. _unsupported-features:
Unsupported features
--------------------
Some email capabilities aren't supported by all ESPs. When you try to send a
message using features Anymail can't communicate to the current ESP, you'll get an
:exc:`~exceptions.AnymailUnsupportedFeature` error, and the message won't be sent.
For example, very few ESPs support alternative message parts added with
:meth:`~django.core.mail.EmailMultiAlternatives.attach_alternative`
(other than a single :mimetype:`text/html` part that becomes the HTML body).
If you try to send a message with other alternative parts, Anymail will
raise :exc:`~exceptions.AnymailUnsupportedFeature`.
.. setting:: ANYMAIL_UNSUPPORTED_FEATURE_ERRORS
If you'd like to silently ignore :exc:`~exceptions.AnymailUnsupportedFeature`
errors and send the messages anyway, set :setting:`!ANYMAIL_UNSUPPORTED_FEATURE_ERRORS`
to `False` in your settings.py:
.. code-block:: python
ANYMAIL = {
...
"UNSUPPORTED_FEATURE_ERRORS": False,
}
.. _recipients-refused:
Refused recipients
------------------
If *all* recipients (to, cc, bcc) of a message are invalid or rejected by
your ESP *at send time,* the send call will raise an
:exc:`~exceptions.AnymailRecipientsRefused` error.
You can examine the message's :attr:`~message.AnymailMessage.anymail_status`
attribute to determine the cause of the error. (See :ref:`esp-send-status`.)
If a single message is sent to multiple recipients, and *any* recipient is valid
(or the message is queued by your ESP because of rate limiting or
:attr:`~message.AnymailMessage.send_at`), then this exception will not be raised.
You can still examine the message's :attr:`~message.AnymailMessage.anymail_status`
property after the send to determine the status of each recipient.
You can disable this exception by setting :setting:`ANYMAIL_IGNORE_RECIPIENT_STATUS`
to `True` in your settings.py, which will cause Anymail to treat any non-API-error response
from your ESP as a successful send.
.. note::
Many ESPs don't check recipient status during the send API call. For example,
Mailgun always queues sent messages, so you'll never catch
:exc:`AnymailRecipientsRefused` with the Mailgun backend.
For those ESPs, use Anymail's :ref:`delivery event tracking <event-tracking>`
if you need to be notified of sends to blacklisted or invalid emails.

View File

@@ -0,0 +1,50 @@
.. _anymail-exceptions:
Exceptions
----------
.. module:: anymail.exceptions
.. exception:: AnymailUnsupportedFeature
If the email tries to use features that aren't supported by the ESP, the send
call will raise an :exc:`!AnymailUnsupportedFeature` error (a subclass
of :exc:`ValueError`), and the message won't be sent.
You can disable this exception (ignoring the unsupported features and
sending the message anyway, without them) by setting
:setting:`ANYMAIL_UNSUPPORTED_FEATURE_ERRORS` to ``False``.
.. exception:: AnymailRecipientsRefused
Raised when *all* recipients (to, cc, bcc) of a message are invalid or rejected by
your ESP *at send time.* See :ref:`recipients-refused`.
You can disable this exception by setting :setting:`ANYMAIL_IGNORE_RECIPIENT_STATUS`
to `True` in your settings.py, which will cause Anymail to treat any
non-:exc:`AnymailAPIError` response from your ESP as a successful send.
.. exception:: AnymailAPIError
If the ESP's API fails or returns an error response, the send call will
raise an :exc:`!AnymailAPIError`.
The exception's :attr:`status_code` and :attr:`response` attributes may
help explain what went wrong. (Tip: you may also be able to check the API log in
your ESP's dashboard. See :ref:`troubleshooting`.)
.. exception:: AnymailSerializationError
The send call will raise a :exc:`!AnymailSerializationError`
if there are message attributes Anymail doesn't know how to represent
to your ESP.
The most common cause of this error is including values other than
strings and numbers in your :attr:`merge_data` or :attr:`metadata`.
(E.g., you need to format `Decimal` and `date` data to
strings before setting them into :attr:`merge_data`.)
See :ref:`formatting-merge-data` for more information.

13
docs/sending/index.rst Normal file
View File

@@ -0,0 +1,13 @@
.. _sending-email:
Sending email
-------------
.. toctree::
:maxdepth: 2
django_email
anymail_additions
templates
tracking
exceptions

146
docs/sending/templates.rst Normal file
View File

@@ -0,0 +1,146 @@
.. _merge-vars:
Mail merge and ESP templates
============================
Anymail has some features to simplify using your ESP's email
templates and merge-variable features in a portable way.
However, ESP templating languages are generally proprietary,
which makes them inherently non-portable. Although Anymail
can normalize the Django code you write to supply merge
variables to your ESP, it can't help you avoid needing
to rewrite your email templates if you switch ESPs.
:ref:`Using Django templates <django-templates>` can be a
better, portable and maintainable option.
.. note::
Normalized merge variables and template identification
are coming to Anymail soon.
.. currentmodule:: anymail.message
.. _esp-templates:
ESP templates
-------------
.. To use a *Mandrill* (MailChimp) template stored in your Mandrill account,
.. set a :attr:`template_name` and (optionally) :attr:`template_content`
.. on your :class:`~django.core.mail.EmailMessage` object::
..
.. from django.core.mail import EmailMessage
..
.. msg = EmailMessage(subject="Shipped!", from_email="store@example.com",
.. to=["customer@example.com", "accounting@example.com"])
.. msg.template_name = "SHIPPING_NOTICE" # A Mandrill template name
.. msg.template_content = { # Content blocks to fill in
.. 'TRACKING_BLOCK': "<a href='.../*|TRACKINGNO|*'>track it</a>"
.. }
.. msg.global_merge_vars = { # Merge tags in your template
.. 'ORDERNO': "12345", 'TRACKINGNO': "1Z987"
.. }
.. msg.merge_vars = { # Per-recipient merge tags
.. 'accounting@example.com': {'NAME': "Pat"},
.. 'customer@example.com': {'NAME': "Kim"}
.. }
.. msg.send()
..
.. If :attr:`template_name` is set, Djrill will use Mandrill's
.. `messages/send-template API <https://mandrillapp.com/api/docs/messages.html#method=send-template>`_,
.. and will ignore any `body` text set on the `EmailMessage`.
..
.. All of Djrill's other :ref:`Mandrill-specific options <anymail-send-features>`
.. can be used with templates.
.. attribute:: AnymailMessage.template_name
.. attribute:: AnymailMessage.global_merge_vars
.. ``dict``: merge variables to use for all recipients (most useful with :ref:`mandrill-templates`). ::
..
.. message.global_merge_vars = {'company': "ACME", 'offer': "10% off"}
..
.. Merge data must be strings or other JSON-serializable types.
.. (See :ref:`formatting-merge-data` for details.)
.. attribute:: AnymailMessage.merge_vars
.. ``dict``: per-recipient merge variables (most useful with :ref:`mandrill-templates`). The keys
.. in the dict are the recipient email addresses, and the values are dicts of merge vars for
.. each recipient::
..
.. message.merge_vars = {
.. 'wiley@example.com': {'offer': "15% off anvils"},
.. 'rr@example.com': {'offer': "instant tunnel paint"}
.. }
..
.. Merge data must be strings or other JSON-serializable types.
.. (See :ref:`formatting-merge-data` for details.)
.. _formatting-merge-data:
Formatting merge data
~~~~~~~~~~~~~~~~~~~~~
If you're using a `date`, `datetime`, `Decimal`, or anything other
than strings and integers,
you'll need to format them into strings for use as merge data::
product = Product.objects.get(123) # A Django model
total_cost = Decimal('19.99')
ship_date = date(2015, 11, 18)
# Won't work -- you'll get "not JSON serializable" exceptions:
msg.global_merge_vars = {
'PRODUCT': product,
'TOTAL_COST': total_cost,
'SHIP_DATE': ship_date
}
# Do something this instead:
msg.global_merge_vars = {
'PRODUCT': product.name, # assuming name is a CharField
'TOTAL_COST': "%.2f" % total_cost,
'SHIP_DATE': ship_date.strftime('%B %d, %Y') # US-style "March 15, 2015"
}
These are just examples. You'll need to determine the best way to format
your merge data as strings.
Although floats are allowed in merge vars, you'll generally want to format them
into strings yourself to avoid surprises with floating-point precision.
Anymail will raise :exc:`~anymail.exceptions.AnymailSerializationError` if you attempt
to send a message with non-json-serializable data.
.. How To Use Default Mandrill Subject and From fields
.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
..
.. To use default Mandrill "subject" or "from" field from your template definition
.. (overriding your EmailMessage and Django defaults), set the following attrs:
.. :attr:`use_template_subject` and/or :attr:`use_template_from` on
.. your :class:`~django.core.mail.EmailMessage` object::
..
.. msg.use_template_subject = True
.. msg.use_template_from = True
.. msg.send()
..
.. .. attribute:: use_template_subject
..
.. If `True`, Djrill will omit the subject, and Mandrill will
.. use the default subject from the template.
..
.. .. attribute:: use_template_from
..
.. If `True`, Djrill will omit the "from" field, and Mandrill will
.. use the default "from" from the template.

178
docs/sending/tracking.rst Normal file
View File

@@ -0,0 +1,178 @@
.. _event-tracking:
Tracking sent mail status
=========================
.. note::
Normalized event-tracking webhooks and signals are coming
to Anymail soon.
.. `Mandrill webhooks`_ are used for notification about outbound messages
.. (bounces, clicks, etc.), and also for delivering inbound email
.. processed through Mandrill.
..
.. Djrill includes optional support for Mandrill's webhook notifications.
.. If enabled, it will send a Django signal for each event in a webhook.
.. Your code can connect to this signal for further processing.
.. _Mandrill webhooks: http://help.mandrill.com/entries/21738186-Introduction-to-Webhooks
.. _webhooks-config:
Configuring tracking webhooks
-----------------------------
.. warning:: Webhook Security
Webhooks are ordinary urls---they're wide open to the internet.
You must take steps to secure webhooks, or anyone could submit
random (or malicious) data to your app simply by invoking your
webhook URL. For security:
* Your webhook should only be accessible over SSL (https).
(This is beyond the scope of Anymail.)
* Your webhook must include a random, secret key, known only to your
app and your ESP. Anymail will verify calls to your webhook, and will
reject calls without the correct key.
.. * You can, optionally include the two settings :setting:`DJRILL_WEBHOOK_SIGNATURE_KEY`
.. and :setting:`DJRILL_WEBHOOK_URL` to enforce `webhook signature`_ checking
.. _webhook signature: http://help.mandrill.com/entries/23704122-Authenticating-webhook-requests
.. To enable Djrill webhook processing you need to create and set a webhook
.. secret in your project settings, include the Djrill url routing, and
.. then add the webhook in the Mandrill control panel.
..
.. 1. In your project's :file:`settings.py`, add a :setting:`DJRILL_WEBHOOK_SECRET`:
..
.. .. code-block:: python
..
.. DJRILL_WEBHOOK_SECRET = "<create your own random secret>"
..
.. substituting a secret you've generated just for Mandrill webhooks.
.. (Do *not* use your Mandrill API key or Django SECRET_KEY for this!)
..
.. An easy way to generate a random secret is to run the command below in a shell:
..
.. .. code-block:: console
..
.. $ python -c "from django.utils import crypto; print crypto.get_random_string(16)"
..
..
.. 2. In your base :file:`urls.py`, add routing for the Djrill urls:
..
.. .. code-block:: python
..
.. urlpatterns = patterns('',
.. ...
.. url(r'^djrill/', include(djrill.urls)),
.. )
..
..
.. 3. Now you need to tell Mandrill about your webhook:
..
.. * For receiving events on sent messages (e.g., bounces or clickthroughs),
.. you'll do this in Mandrill's `webhooks control panel`_.
.. * For setting up inbound email through Mandrill, you'll add your webhook
.. to Mandrill's `inbound settings`_ under "Routes" for your domain.
.. * And if you want both, you'll need to add the webhook in both places.
..
.. In all cases, the "Post to URL" is
.. :samp:`{https://yoursite.example.com}/djrill/webhook/?secret={your-secret}`
.. substituting your app's own domain, and changing *your-secret* to the secret
.. you created in step 1.
..
.. (For sent-message webhooks, don't forget to tick the "Trigger on Events"
.. checkboxes for the events you want to receive.)
..
..
.. Once you've completed these steps and your Django app is live on your site,
.. you can use the Mandrill "Test" commands to verify your webhook configuration.
.. Then see the next section for setting up Django signal handlers to process
.. the webhooks.
..
.. Incidentally, you have some control over the webhook url.
.. If you'd like to change the "djrill" prefix, that comes from
.. the url config in step 2. And if you'd like to change
.. the *name* of the "secret" query string parameter, you can set
.. :setting:`DJRILL_WEBHOOK_SECRET_NAME` in your :file:`settings.py`.
..
.. For extra security, Mandrill provides a signature in the request header
.. X-Mandrill-Signature. If you want to verify this signature, you need to provide
.. the settings :setting:`DJRILL_WEBHOOK_SIGNATURE_KEY` with the webhook-specific
.. signature key that can be found in the Mandrill admin panel and
.. :setting:`DJRILL_WEBHOOK_URL` where you should enter the exact URL, including
.. that you entered in Mandrill when creating the webhook.
.. _webhooks control panel: https://mandrillapp.com/settings/webhooks
.. _inbound settings: https://mandrillapp.com/inbound
.. _webhook-usage:
Tracking event signals
----------------------
.. Once you've enabled webhooks, Djrill will send a ``djrill.signals.webhook_event``
.. custom `Django signal`_ for each Mandrill event it receives.
.. You can connect your own receiver function to this signal for further processing.
..
.. Be sure to read Django's `listening to signals`_ docs for information on defining
.. and connecting signal receivers.
..
.. Examples:
..
.. .. code-block:: python
..
.. from djrill.signals import webhook_event
.. from django.dispatch import receiver
..
.. @receiver(webhook_event)
.. def handle_bounce(sender, event_type, data, **kwargs):
.. if event_type == 'hard_bounce' or event_type == 'soft_bounce':
.. print "Message to %s bounced: %s" % (
.. data['msg']['email'],
.. data['msg']['bounce_description']
.. )
..
.. @receiver(webhook_event)
.. def handle_inbound(sender, event_type, data, **kwargs):
.. if event_type == 'inbound':
.. print "Inbound message from %s: %s" % (
.. data['msg']['from_email'],
.. data['msg']['subject']
.. )
..
.. @receiver(webhook_event)
.. def handle_whitelist_sync(sender, event_type, data, **kwargs):
.. if event_type == 'whitelist_add' or event_type == 'whitelist_remove':
.. print "Rejection whitelist update: %s email %s (%s)" % (
.. data['action'],
.. data['reject']['email'],
.. data['reject']['reason']
.. )
..
..
.. Note that your webhook_event signal handlers will be called for all Mandrill
.. webhook callbacks, so you should always check the `event_type` param as shown
.. in the examples above to ensure you're processing the expected events.
..
.. Mandrill batches up multiple events into a single webhook call.
.. Djrill will invoke your signal handler once for each event in the batch.
..
.. The available fields in the `data` param are described in Mandrill's documentation:
.. `sent-message webhooks`_, `inbound webhooks`_, and `whitelist/blacklist sync webooks`_.
.. _Django signal: https://docs.djangoproject.com/en/stable/topics/signals/
.. _inbound webhooks:
http://help.mandrill.com/entries/22092308-What-is-the-format-of-inbound-email-webhooks-
.. _listening to signals:
https://docs.djangoproject.com/en/stable/topics/signals/#listening-to-signals
.. _sent-message webhooks: http://help.mandrill.com/entries/21738186-Introduction-to-Webhooks
.. _whitelist/blacklist sync webooks:
https://mandrill.zendesk.com/hc/en-us/articles/205583297-Sync-Event-Webhook-format

View File

@@ -0,0 +1,55 @@
.. _django-templates:
Using Django templates for email
================================
ESP's templating languages and merge capabilities are generally not compatible
with each other, which can make it hard to move email templates between them.
But since you're working in Django, you already have access to the
extremely-full-featured :mod:`Django templating system <django.template>`.
You don't even have to use Django's template syntax: it supports other
template languages (like Jinja2).
You're probably already using Django's templating system for your HTML pages,
so it can be an easy decision to use it for your email, too.
To compose email using *Django* templates, you can use Django's
:func:`~django.template.loaders.django.template.loader.render_to_string`
template shortcut to build the body and html.
Example that builds an email from the templates ``message_subject.txt``,
``message_body.txt`` and ``message_body.html``:
.. code-block:: python
from django.core.mail import EmailMultiAlternatives
from django.template import Context
from django.template.loader import render_to_string
template_data = {
'ORDERNO': "12345", 'TRACKINGNO': "1Z987"
}
plaintext_context = Context(autoescape=False) # HTML escaping not appropriate in plaintext
subject = render_to_string("message_subject.txt", template_data, plaintext_context)
text_body = render_to_string("message_body.txt", template_data, plaintext_context)
html_body = render_to_string("message_body.html", template_data)
msg = EmailMultiAlternatives(subject=subject, from_email="store@example.com",
to=["customer@example.com"], body=text_body)
msg.attach_alternative(html_body, "text/html")
msg.send()
Helpful add-ons
---------------
These (third-party) packages can be helpful for building your email
in Django:
.. TODO: flesh this out
* django-templated-mail
* Premailer, for inlining css
* BeautifulSoup, lxml, or html2text, for auto-generating plaintext from your html

16
docs/tips/index.rst Normal file
View File

@@ -0,0 +1,16 @@
Tips, tricks, and advanced usage
--------------------------------
Some suggestions and recipes for getting things
done with Anymail:
.. toctree::
:maxdepth: 1
multiple_backends
django_templates
.. TODO:
.. Working with django-mailer(2)
.. Sharing backend connections (sessions)

View File

@@ -1,24 +1,24 @@
.. _multiple-backends: .. _multiple-backends:
Mixing Email Backends Mixing email backends
===================== =====================
Since you are replacing Django's global :setting:`EMAIL_BACKEND`, by default Since you are replacing Django's global :setting:`EMAIL_BACKEND`, by default
Djrill will handle all outgoing mail, sending everything through Mandrill. Anymail will handle **all** outgoing mail, sending everything through your ESP.
You can use Django mail's optional :func:`connection <django.core.mail.get_connection>` You can use Django mail's optional :func:`connection <django.core.mail.get_connection>`
argument to send some mail through Mandrill and others through a different system. argument to send some mail through your ESP and others through a different system.
This could be useful, for example, to deliver customer emails with Mandrill, This could be useful, for example, to deliver customer emails with the ESP,
but send admin emails directly through an SMTP server: but send admin emails directly through an SMTP server:
.. code-block:: python .. code-block:: python
:emphasize-lines: 8,10 :emphasize-lines: 8,10,13,15
from django.core.mail import send_mail, get_connection from django.core.mail import send_mail, get_connection
# send_mail connection defaults to the settings EMAIL_BACKEND, which # send_mail connection defaults to the settings EMAIL_BACKEND, which
# we've set to DjrillBackend. This will be sent using Mandrill: # we've set to Anymail's MailgunBackend. This will be sent using Mailgun:
send_mail("Thanks", "We sent your order", "sales@example.com", ["customer@example.com"]) send_mail("Thanks", "We sent your order", "sales@example.com", ["customer@example.com"])
# Get a connection to an SMTP backend, and send using that instead: # Get a connection to an SMTP backend, and send using that instead:
@@ -26,13 +26,19 @@ but send admin emails directly through an SMTP server:
send_mail("Uh-Oh", "Need your attention", "admin@example.com", ["alert@example.com"], send_mail("Uh-Oh", "Need your attention", "admin@example.com", ["alert@example.com"],
connection=smtp_backend) connection=smtp_backend)
# You can even use multiple Anymail backends in the same app:
sendgrid_backend = get_connection('anymail.backends.sendgrid.SendGridBackend')
send_mail("Password reset", "Here you go", "user@example.com", ["noreply@example.com"],
connection=sendgrid_backend)
You can supply a different connection to Django's You can supply a different connection to Django's
:func:`~django.core.mail.send_mail` and :func:`~django.core.mail.send_mass_mail` helpers, :func:`~django.core.mail.send_mail` and :func:`~django.core.mail.send_mass_mail` helpers,
and in the constructor for an and in the constructor for an
:class:`~django.core.mail.EmailMessage` or :class:`~django.core.mail.EmailMultiAlternatives`. :class:`~django.core.mail.EmailMessage` or :class:`~django.core.mail.EmailMultiAlternatives`.
(See the `django.utils.log.AdminEmailHandler`_ docs for more information on Django's admin error logging.) (See the :class:`django.utils.log.AdminEmailHandler` docs for more information
on Django's admin error logging.)
.. _django.utils.log.AdminEmailHandler: .. _django.utils.log.AdminEmailHandler:
https://docs.djangoproject.com/en/stable/topics/logging/#django.utils.log.AdminEmailHandler https://docs.djangoproject.com/en/stable/topics/logging/#django.utils.log.AdminEmailHandler

View File

@@ -3,68 +3,60 @@
Troubleshooting Troubleshooting
=============== ===============
Djrill throwing errors? Not sending what you want? Here are some tips... Anymail throwing errors? Not sending what you want? Here are some tips...
Figuring Out What's Wrong Figuring out what's wrong
------------------------- -------------------------
* **Check the error message:** Look for a Mandrill error message in your **Check the error message**
Look for an Anymail error message in your
web browser or console (running Django in dev mode) or in your server web browser or console (running Django in dev mode) or in your server
error logs. As of v1.4, Djrill reports the detailed Mandrill error when error logs. If you see something like "invalid API key"
something goes wrong. And when the error is something like "invalid API key"
or "invalid email address", that's probably 90% of what you'll need to know or "invalid email address", that's probably 90% of what you'll need to know
to solve the problem. to solve the problem.
* **Check the Mandrill API logs:** The Mandrill dashboard includes an **Check your ESPs API logs**
*incredibly-helpful* list of your `recent API calls`_ -- and you can click
into each one to see the full request and response. Check to see if the
data you thought you were sending actually made it into the request, and
if Mandrill has any complaints in the response.
* **Double-check common issues:** Many ESPs offer an incredibly-helpful log
of your recent API calls in their dashboards. Check the logs to see if the
data you thought you were sending actually made it to your ESP, and
if they recorded any errors there.
* Did you set your :setting:`MANDRILL_API_KEY` in settings.py? **Double-check common issues**
* Did you add ``'djrill'`` to the list of :setting:`INSTALLED_APPS` in settings.py?
* Did you install Anymail with the ESPs you want available?
(E.g., `pip install anymail[mailgun,sendgrid]` -- *not* just `pip install anymail`.)
* Did you add any required settings for those ESPs to your settings.py?
(E.g., `ANYMAIL_MANDRILL_API_KEY`.)
* Did you add ``'anymail'`` to the list of :setting:`INSTALLED_APPS` in settings.py?
* Are you using a valid from address? Django's default is "webmaster@localhost", * Are you using a valid from address? Django's default is "webmaster@localhost",
which won't cut it. Either specify the ``from_email`` explicitly on every message which won't cut it. Either specify the ``from_email`` explicitly on every message
you send through Djrill, or add :setting:`DEFAULT_FROM_EMAIL` to your settings.py. you send through Anymail, or add :setting:`DEFAULT_FROM_EMAIL` to your settings.py.
* **Try it without Djrill:** Try switching your :setting:`EMAIL_BACKEND` **Try it without Anymail**
setting to Django's `File backend`_ and then running your email-sending
code again. If that causes errors, you'll know the issue is somewhere Try switching your :setting:`EMAIL_BACKEND` setting to
other than Djrill. And you can look through the :setting:`EMAIL_FILE_PATH` Django's :ref:`File backend <django:topic-email-file-backend>` and then running your
email-sending code again. If that causes errors, you'll know the issue is somewhere
other than Anymail. And you can look through the :setting:`EMAIL_FILE_PATH`
file contents afterward to see if you're generating the email you want. file contents afterward to see if you're generating the email you want.
.. _recent API calls: https://mandrillapp.com/settings/api Getting help
.. _File backend: https://docs.djangoproject.com/en/stable/topics/email/#file-backend
Getting Help
------------ ------------
If you've gone through the suggestions above and still aren't sure what's wrong, If you've gone through the suggestions above and still aren't sure what's wrong,
the Djrill community is happy to help. Djrill is supported and maintained by the the Anymail community is happy to help. Anymail is supported and maintained by the
people who use it -- like you! (We're not Mandrill employees.) people who use it -- like you! (We're not employees of any ESP.)
You can ask in either of these places (but please pick only one per question!): For questions or problems with Anymail, you can open a `GitHub issue`_.
(And if you've found a bug, you're welcome to :ref:`contribute <contributing>` a fix!)
Ask on `StackOverflow`_ Whenever you open an issue, it's always helpful to mention which ESP you're using,
Tag your question with **both** ``Django`` and ``Mandrill`` to get our attention. include the relevant portions of your code and settings, the text of any error messages,
Bonus: a lot of questions about Djrill are actually questions about Django and any exception stack traces.
itself, so by asking on StackOverflow you'll also get the benefit of the
thousands of Django experts there.
Open a `GitHub issue`_
We do our best to answer questions in GitHub issues. And if you've found
a Djrill bug, that's definitely the place to report it. (Or even fix it --
see :ref:`contributing`.)
Wherever you ask, it's always helpful to include the relevant portions of your
code, the text of any error messages, and any exception stack traces in your
question.
.. _StackOverflow: http://stackoverflow.com/questions/tagged/django+mandrill .. _GitHub issue: https://github.com/anymail/django-anymail/issues
.. _GitHub issue: https://github.com/brack3t/Djrill/issues

View File

@@ -1,121 +0,0 @@
.. _upgrading:
Upgrading from 1.x
==================
Djrill 2.0 includes some breaking changes from 1.x.
These changes should have minimal (or no) impact on most Djrill users,
but if you are upgrading please review the major topics below
to see if they apply to you.
Djrill 1.4 tried to warn you if you were using Djrill features
expected to change in 2.0. If you are seeing any deprecation warnings
with Djrill 1.4, you should fix them before upgrading to 2.0.
(Warnings appear in the console when running Django in debug mode.)
Please see the :ref:`release notes <history>` for a list of new features
and improvements in Djrill 2.0.
Dropped support for Django 1.3, Python 2.6, and Python 3.2
----------------------------------------------------------
Although Djrill may still work with these older configurations,
we no longer test against them. Djrill now requires Django 1.4
or later and Python 2.7, 3.3, or 3.4.
If you require support for these earlier versions, you should
not upgrade to Djrill 2.0. Djrill 1.4 remains available on
pypi, and will continue to receive security fixes.
Removed DjrillAdminSite
-----------------------
Earlier versions of Djrill included a custom Django admin site.
The equivalent functionality is available in Mandrill's dashboard,
and Djrill 2.0 drops support for it.
Although most Djrill users were unaware the admin site existed,
many did follow the earlier versions' instructions to enable it.
If you have added DjrillAdminSite, you will need to remove it for Djrill 2.0.
In your :file:`urls.py`:
.. code-block:: python
from djrill import DjrillAdminSite # REMOVE this
admin.site = DjrillAdminSite() # REMOVE this
admin.autodiscover() # REMOVE this if you added it only for Djrill
In your :file:`settings.py`:
.. code-block:: python
INSTALLED_APPS = (
...
# If you added SimpleAdminConfig only for Djrill:
'django.contrib.admin.apps.SimpleAdminConfig', # REMOVE this
'django.contrib.admin', # ADD this default back
...
)
(These instructions assume you had changed to SimpleAdminConfig
solely for DjrillAdminSite. If you are using it for custom admin
sites with any other Django apps you use, you should leave it
SimpleAdminConfig in place, but still remove the references to
DjrillAdminSite.)
Added exception for invalid or rejected recipients
--------------------------------------------------
Djrill 2.0 raises a new :exc:`djrill.MandrillRecipientsRefused` exception when
all recipients of a message are invalid or rejected by Mandrill. (This parallels
the behavior of Django's default :setting:`SMTP email backend <EMAIL_BACKEND>`,
which raises :exc:`SMTPRecipientsRefused <smtplib.SMTPRecipientsRefused>` when
all recipients are refused.)
Your email-sending code should handle this exception (along with other
exceptions that could occur during a send). However, if you want to retain the
Djrill 1.x behavior and treat invalid or rejected recipients as successful sends,
you can set :setting:`MANDRILL_IGNORE_RECIPIENT_STATUS` to ``True`` in your settings.py.
Other 2.0 breaking changes
--------------------------
Code that will be affected by these changes is far less common than
for the changes listed above, but they may impact some uses:
Removed unintended date-to-string conversion
If your code was inadvertently relying on Djrill to automatically
convert date or datetime values to strings in :attr:`merge_vars`,
:attr:`metadata`, or other Mandrill message attributes, you must
now explicitly do the string conversion yourself.
See :ref:`formatting-merge-data` for an explanation.
(Djrill 1.4 reported a DeprecationWarning for this case.)
(This does not affect :attr:`send_at`, where Djrill specifically
allows date or datetime values.)
Removed DjrillMessage class
The ``DjrillMessage`` class has not been needed since Djrill 0.2.
You should replace any uses of it with the standard
:class:`~django.core.mail.EmailMessage` class.
(Djrill 1.4 reported a DeprecationWarning for this case.)
Removed DjrillBackendHTTPError
This exception was deprecated in Djrill 0.3. Replace uses of it
with :exc:`djrill.MandrillAPIError`.
(Djrill 1.4 reported a DeprecationWarning for this case.)
Refactored Djrill backend and exceptions
Several internal details of ``djrill.mail.backends.DjrillBackend``
and Djrill's exception classes have been significantly updated for 2.0.
The intent is to make it easier to maintain and extend the backend
(including creating your own subclasses to override Djrill's default
behavior). As a result, though, any existing code that depended on
undocumented Djrill internals may need to be updated.

View File

@@ -1,387 +0,0 @@
Sending Mail
============
Djrill handles **all** outgoing email sent through Django's standard
:mod:`django.core.mail` package, including :func:`~django.core.mail.send_mail`,
:func:`~django.core.mail.send_mass_mail`, the :class:`~django.core.mail.EmailMessage` class,
and even :func:`~django.core.mail.mail_admins`.
If you'd like to selectively send only some messages through Mandrill,
there is a way to :ref:`use multiple email backends <multiple-backends>`.
.. _django-send-support:
Django Email Support
--------------------
Djrill supports most of the functionality of Django's :class:`~django.core.mail.EmailMessage`
and :class:`~django.core.mail.EmailMultiAlternatives` classes.
Some notes and limitations:
**Display Names**
All email addresses (from, to, cc, bcc) can be simple
("email\@example.com") or can include a display name
("Real Name <email\@example.com>").
**CC and BCC Recipients**
Djrill properly identifies "cc" and "bcc" recipients to Mandrill.
Note that you may need to set the Mandrill option :attr:`preserve_recipients`
to `!True` if you want recipients to be able to see who else was included
in the "to" list.
.. _sending-html:
**HTML/Alternative Parts**
To include an HTML version of a message, use
:meth:`~django.core.mail.EmailMultiAlternatives.attach_alternative`:
.. code-block:: python
from django.core.mail import EmailMultiAlternatives
msg = EmailMultiAlternatives("Subject", "text body",
"from@example.com", ["to@example.com"])
msg.attach_alternative("<html>html body</html>", "text/html")
Djrill allows a maximum of one
:meth:`~django.core.mail.EmailMultiAlternatives.attach_alternative`
on a message, and it must be ``mimetype="text/html"``.
Otherwise, Djrill will raise :exc:`~djrill.NotSupportedByMandrillError` when you
attempt to send the message. (Mandrill doesn't support sending multiple html
alternative parts, or any non-html alternatives.)
.. _sending-attachments:
**Attachments**
Djrill will send a message's attachments. (Note that Mandrill may impose limits
on size and type of attachments.)
Also, if an image attachment has a Content-ID header, Djrill will tell Mandrill
to treat that as an embedded image rather than an ordinary attachment.
(For an example, see :meth:`~DjrillBackendTests.test_embedded_images`
in :file:`tests/test_mandrill_send.py`.)
.. _message-headers:
**Headers**
Djrill accepts additional headers and passes them along to Mandrill:
.. code-block:: python
msg = EmailMessage( ...
headers={'Reply-To': "reply@example.com", 'List-Unsubscribe': "..."}
)
.. note::
Djrill also supports the `reply_to` param added to
:class:`~django.core.mail.EmailMessage` in Django 1.8.
(If you provide *both* a 'Reply-To' header and the `reply_to` param,
the header will take precedence.)
.. _mandrill-send-support:
Mandrill-Specific Options
-------------------------
Most of the options from the Mandrill
`messages/send API <https://mandrillapp.com/api/docs/messages.html#method=send>`_
`message` struct can be set directly on an :class:`~django.core.mail.EmailMessage`
(or subclass) object.
.. note::
You can set global defaults for common options with the
:setting:`MANDRILL_SETTINGS` setting, to avoid having to
set them on every message.
.. These attributes are in the same order as they appear in the Mandrill API docs...
.. attribute:: important
``Boolean``: whether Mandrill should send this message ahead of non-important ones.
.. attribute:: track_opens
``Boolean``: whether Mandrill should enable open-tracking for this message.
Default from your Mandrill account settings. ::
message.track_opens = True
.. attribute:: track_clicks
``Boolean``: whether Mandrill should enable click-tracking for this message.
Default from your Mandrill account settings.
.. note::
Mandrill has an option to track clicks in HTML email but not plaintext, but
it's *only* available in your Mandrill account settings. If you want to use that
option, set it at Mandrill, and *don't* set the ``track_clicks`` attribute here.
.. attribute:: auto_text
``Boolean``: whether Mandrill should automatically generate a text body from the HTML.
Default from your Mandrill account settings.
.. attribute:: auto_html
``Boolean``: whether Mandrill should automatically generate an HTML body from the plaintext.
Default from your Mandrill account settings.
.. attribute:: inline_css
``Boolean``: whether Mandrill should inline CSS styles in the HTML.
Default from your Mandrill account settings.
.. attribute:: url_strip_qs
``Boolean``: whether Mandrill should ignore any query parameters when aggregating
URL tracking data. Default from your Mandrill account settings.
.. attribute:: preserve_recipients
``Boolean``: whether Mandrill should include all recipients in the "to" message header.
Default from your Mandrill account settings.
.. attribute:: view_content_link
``Boolean``: set False on sensitive messages to instruct Mandrill not to log the content.
.. attribute:: tracking_domain
``str``: domain Mandrill should use to rewrite tracked links and host tracking pixels
for this message. Useful if you send email from multiple domains.
Default from your Mandrill account settings.
.. attribute:: signing_domain
``str``: domain Mandrill should use for DKIM signing and SPF on this message.
Useful if you send email from multiple domains.
Default from your Mandrill account settings.
.. attribute:: return_path_domain
``str``: domain Mandrill should use for the message's return-path.
.. attribute:: merge_language
``str``: the merge tag language if using merge tags -- e.g., "mailchimp" or "handlebars".
Default from your Mandrill account settings.
.. attribute:: global_merge_vars
``dict``: merge variables to use for all recipients (most useful with :ref:`mandrill-templates`). ::
message.global_merge_vars = {'company': "ACME", 'offer': "10% off"}
Merge data must be strings or other JSON-serializable types.
(See :ref:`formatting-merge-data` for details.)
.. attribute:: merge_vars
``dict``: per-recipient merge variables (most useful with :ref:`mandrill-templates`). The keys
in the dict are the recipient email addresses, and the values are dicts of merge vars for
each recipient::
message.merge_vars = {
'wiley@example.com': {'offer': "15% off anvils"},
'rr@example.com': {'offer': "instant tunnel paint"}
}
Merge data must be strings or other JSON-serializable types.
(See :ref:`formatting-merge-data` for details.)
.. attribute:: tags
``list`` of ``str``: tags to apply to the message, for filtering reports in the Mandrill
dashboard. (Note that Mandrill prohibits tags longer than 50 characters or starting with
underscores.) ::
message.tags = ["Order Confirmation", "Test Variant A"]
.. attribute:: subaccount
``str``: the ID of one of your subaccounts to use for sending this message.
.. attribute:: google_analytics_domains
``list`` of ``str``: domain names for links where Mandrill should add Google Analytics
tracking parameters. ::
message.google_analytics_domains = ["example.com"]
.. attribute:: google_analytics_campaign
``str`` or ``list`` of ``str``: the utm_campaign tracking parameter to attach to links
when adding Google Analytics tracking. (Mandrill defaults to the message's from_email as
the campaign name.)
.. attribute:: metadata
``dict``: metadata values Mandrill should store with the message for later search and
retrieval. ::
message.metadata = {'customer': customer.id, 'order': order.reference_number}
Mandrill restricts metadata keys to alphanumeric characters and underscore, and
metadata values to numbers, strings, boolean values, and None (null).
.. attribute:: recipient_metadata
``dict``: per-recipient metadata values. Keys are the recipient email addresses,
and values are dicts of metadata for each recipient (similar to
:attr:`merge_vars`)
Mandrill restricts metadata keys to alphanumeric characters and underscore, and
metadata values to numbers, strings, boolean values, and None (null).
.. attribute:: async
``Boolean``: whether Mandrill should use an async mode optimized for bulk sending.
.. attribute:: ip_pool
``str``: name of one of your Mandrill dedicated IP pools to use for sending this message.
.. attribute:: send_at
`datetime` or `date` or ``str``: instructs Mandrill to delay sending this message
until the specified time. Example::
msg.send_at = datetime.utcnow() + timedelta(hours=1)
Mandrill requires a UTC string in the form ``YYYY-MM-DD HH:MM:SS``.
Djrill will convert python dates and datetimes to this form.
(Dates will be given a time of 00:00:00.)
.. note:: Timezones
Mandrill assumes :attr:`!send_at` is in the UTC timezone,
which is likely *not* the same as your local time.
Djrill will convert timezone-*aware* datetimes to UTC for you.
But if you format your own string, supply a date, or a
*naive* datetime, you must make sure it is in UTC.
See the python `datetime` docs for more information.
For example, ``msg.send_at = datetime.now() + timedelta(hours=1)``
will try to schedule the message for an hour from the current time,
but *interpreted in the UTC timezone* (which isn't what you want).
If you're more than an hour west of the prime meridian, that will
be in the past (and the message will get sent immediately). If
you're east of there, the message might get sent quite a bit later
than you intended. One solution is to use `utcnow` as shown in
the earlier example.
.. note::
Scheduled sending is a paid Mandrill feature. If you are using
a free Mandrill account, :attr:`!send_at` won't work.
All the Mandrill-specific attributes listed above work with *any*
:class:`~django.core.mail.EmailMessage`-derived object, so you can use them with
many other apps that add Django mail functionality.
If you have questions about the python syntax for any of these properties,
see :class:`DjrillMandrillFeatureTests` in :file:`tests/test_mandrill_send.py` for examples.
.. _mandrill-response:
Response from Mandrill
----------------------
.. attribute:: mandrill_response
Djrill adds a :attr:`!mandrill_response` attribute to each :class:`~django.core.mail.EmailMessage`
as it sends it. This allows you to retrieve message ids, initial status information and more.
For an EmailMessage that is successfully sent to one or more email addresses, :attr:`!mandrill_response` will
be set to a ``list`` of ``dict``, where each entry has info for one email address. See the Mandrill docs for the
`messages/send API <https://mandrillapp.com/api/docs/messages.html#method=send>`_ for full details.
For example, to get the Mandrill message id for a sent email you might do this::
msg = EmailMultiAlternatives(subject="subject", body="body",
from_email="sender@example.com",to=["someone@example.com"])
msg.send()
response = msg.mandrill_response[0]
mandrill_id = response['_id']
For this example, msg.mandrill_response might look like this::
msg.mandrill_response = [
{
"email": "someone@example.com",
"status": "sent",
"_id": "abc123abc123abc123abc123abc123"
}
]
If an error is returned by Mandrill while sending the message then :attr:`!mandrill_response` will be set to None.
.. _djrill-exceptions:
Exceptions
----------
.. exception:: djrill.NotSupportedByMandrillError
If the email tries to use features that aren't supported by Mandrill, the send
call will raise a :exc:`~!djrill.NotSupportedByMandrillError` exception (a subclass
of :exc:`ValueError`).
.. exception:: djrill.MandrillRecipientsRefused
If *all* recipients (to, cc, bcc) of a message are invalid or rejected by Mandrill
(e.g., because they are your Mandrill blacklist), the send call will raise a
:exc:`~!djrill.MandrillRecipientsRefused` exception.
You can examine the message's :attr:`mandrill_response` attribute
to determine the cause of the error.
If a single message is sent to multiple recipients, and *any* recipient is valid
(or the message is queued by Mandrill because of rate limiting or :attr:`send_at`), then
this exception will not be raised. You can still examine the mandrill_response
property after the send to determine the status of each recipient.
You can disable this exception by setting :setting:`MANDRILL_IGNORE_RECIPIENT_STATUS`
to True in your settings.py, which will cause Djrill to treat any non-API-error response
from Mandrill as a successful send.
.. versionadded:: 2.0
Djrill 1.x behaved as if ``MANDRILL_IGNORE_RECIPIENT_STATUS = True``.
.. exception:: djrill.MandrillAPIError
If the Mandrill API fails or returns an error response, the send call will
raise a :exc:`~!djrill.MandrillAPIError` exception (a subclass of :exc:`requests.HTTPError`).
The exception's :attr:`status_code` and :attr:`response` attributes may
help explain what went wrong. (Tip: you can also check Mandrill's
`API error log <https://mandrillapp.com/settings/api>`_ to view the full API
request and error response.)
.. exception:: djrill.NotSerializableForMandrillError
The send call will raise a :exc:`~!djrill.NotSerializableForMandrillError` exception
if the message has attached data which cannot be serialized to JSON for the Mandrill API.
See :ref:`formatting-merge-data` for more information.
.. versionadded:: 2.0
Djrill 1.x raised a generic `TypeError` in this case.
:exc:`~!djrill.NotSerializableForMandrillError` is a subclass of `TypeError`
for compatibility with existing code.

View File

@@ -1,131 +0,0 @@
Sending Template Mail
=====================
.. _mandrill-templates:
Mandrill Templates
------------------
To use a *Mandrill* (MailChimp) template stored in your Mandrill account,
set a :attr:`template_name` and (optionally) :attr:`template_content`
on your :class:`~django.core.mail.EmailMessage` object::
from django.core.mail import EmailMessage
msg = EmailMessage(subject="Shipped!", from_email="store@example.com",
to=["customer@example.com", "accounting@example.com"])
msg.template_name = "SHIPPING_NOTICE" # A Mandrill template name
msg.template_content = { # Content blocks to fill in
'TRACKING_BLOCK': "<a href='.../*|TRACKINGNO|*'>track it</a>"
}
msg.global_merge_vars = { # Merge tags in your template
'ORDERNO': "12345", 'TRACKINGNO': "1Z987"
}
msg.merge_vars = { # Per-recipient merge tags
'accounting@example.com': {'NAME': "Pat"},
'customer@example.com': {'NAME': "Kim"}
}
msg.send()
If :attr:`template_name` is set, Djrill will use Mandrill's
`messages/send-template API <https://mandrillapp.com/api/docs/messages.html#method=send-template>`_,
and will ignore any `body` text set on the `EmailMessage`.
All of Djrill's other :ref:`Mandrill-specific options <mandrill-send-support>`
can be used with templates.
.. _formatting-merge-data:
Formatting Merge Data
~~~~~~~~~~~~~~~~~~~~~
If you're using dates, datetimes, Decimals, or anything other than strings and integers,
you'll need to format them into strings for use as merge data::
product = Product.objects.get(123) # A Django model
total_cost = Decimal('19.99')
ship_date = date(2015, 11, 18)
# Won't work -- you'll get "not JSON serializable" exceptions:
msg.global_merge_vars = {
'PRODUCT': product,
'TOTAL_COST': total_cost,
'SHIP_DATE': ship_date
}
# Do something this instead:
msg.global_merge_vars = {
'PRODUCT': product.name, # assuming name is a CharField
'TOTAL_COST': "%.2f" % total_cost,
'SHIP_DATE': ship_date.strftime('%B %d, %Y') # US-style "March 15, 2015"
}
These are just examples. You'll need to determine the best way to format
your merge data as strings.
Although floats are allowed in merge vars, you'll generally want to format them
into strings yourself to avoid surprises with floating-point precision.
Technically, Djrill will accept anything serializable by the Python json package --
which means advanced template users can include dicts and lists as merge vars
(for templates designed to handle objects and arrays).
See the Python :class:`json.JSONEncoder` docs for a list of allowable types.
Djrill will raise :exc:`djrill.NotSerializableForMandrillError` if you attempt
to send a message with non-json-serializable data.
How To Use Default Mandrill Subject and From fields
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To use default Mandrill "subject" or "from" field from your template definition
(overriding your EmailMessage and Django defaults), set the following attrs:
:attr:`use_template_subject` and/or :attr:`use_template_from` on
your :class:`~django.core.mail.EmailMessage` object::
msg.use_template_subject = True
msg.use_template_from = True
msg.send()
.. attribute:: use_template_subject
If `True`, Djrill will omit the subject, and Mandrill will
use the default subject from the template.
.. attribute:: use_template_from
If `True`, Djrill will omit the "from" field, and Mandrill will
use the default "from" from the template.
.. _django-templates:
Django Templates
----------------
To compose email using *Django* templates, you can use Django's
:func:`~django.template.loaders.django.template.loader.render_to_string`
template shortcut to build the body and html.
Example that builds an email from the templates ``message_subject.txt``,
``message_body.txt`` and ``message_body.html``::
from django.core.mail import EmailMultiAlternatives
from django.template import Context
from django.template.loader import render_to_string
template_data = {
'ORDERNO': "12345", 'TRACKINGNO': "1Z987"
}
plaintext_context = Context(autoescape=False) # HTML escaping not appropriate in plaintext
subject = render_to_string("message_subject.txt", template_data, plaintext_context)
text_body = render_to_string("message_body.txt", template_data, plaintext_context)
html_body = render_to_string("message_body.html", template_data)
msg = EmailMultiAlternatives(subject=subject, from_email="store@example.com",
to=["customer@example.com"], body=text_body)
msg.attach_alternative(html_body, "text/html")
msg.send()

View File

@@ -1,172 +0,0 @@
.. _webhooks:
Mandrill Webhooks and Inbound Email
===================================
`Mandrill webhooks`_ are used for notification about outbound messages
(bounces, clicks, etc.), and also for delivering inbound email
processed through Mandrill.
Djrill includes optional support for Mandrill's webhook notifications.
If enabled, it will send a Django signal for each event in a webhook.
Your code can connect to this signal for further processing.
.. warning:: Webhook Security
Webhooks are ordinary urls---they're wide open to the internet.
You must take steps to secure webhooks, or anyone could submit
random (or malicious) data to your app simply by invoking your
webhook URL. For security:
* Your webhook should only be accessible over SSL (https).
(This is beyond the scope of Djrill.)
* Your webhook must include a random, secret key, known only to your
app and Mandrill. Djrill will verify calls to your webhook, and will
reject calls without the correct key.
* You can, optionally include the two settings :setting:`DJRILL_WEBHOOK_SIGNATURE_KEY`
and :setting:`DJRILL_WEBHOOK_URL` to enforce `webhook signature`_ checking
.. _Mandrill webhooks: http://help.mandrill.com/entries/21738186-Introduction-to-Webhooks
.. _securing webhooks: http://apidocs.mailchimp.com/webhooks/#securing-webhooks
.. _webhook signature: http://help.mandrill.com/entries/23704122-Authenticating-webhook-requests
.. _webhooks-config:
Configuration
-------------
To enable Djrill webhook processing you need to create and set a webhook
secret in your project settings, include the Djrill url routing, and
then add the webhook in the Mandrill control panel.
1. In your project's :file:`settings.py`, add a :setting:`DJRILL_WEBHOOK_SECRET`:
.. code-block:: python
DJRILL_WEBHOOK_SECRET = "<create your own random secret>"
substituting a secret you've generated just for Mandrill webhooks.
(Do *not* use your Mandrill API key or Django SECRET_KEY for this!)
An easy way to generate a random secret is to run the command below in a shell:
.. code-block:: console
$ python -c "from django.utils import crypto; print crypto.get_random_string(16)"
2. In your base :file:`urls.py`, add routing for the Djrill urls:
.. code-block:: python
urlpatterns = patterns('',
...
url(r'^djrill/', include(djrill.urls)),
)
3. Now you need to tell Mandrill about your webhook:
* For receiving events on sent messages (e.g., bounces or clickthroughs),
you'll do this in Mandrill's `webhooks control panel`_.
* For setting up inbound email through Mandrill, you'll add your webhook
to Mandrill's `inbound settings`_ under "Routes" for your domain.
* And if you want both, you'll need to add the webhook in both places.
In all cases, the "Post to URL" is
:samp:`{https://yoursite.example.com}/djrill/webhook/?secret={your-secret}`
substituting your app's own domain, and changing *your-secret* to the secret
you created in step 1.
(For sent-message webhooks, don't forget to tick the "Trigger on Events"
checkboxes for the events you want to receive.)
Once you've completed these steps and your Django app is live on your site,
you can use the Mandrill "Test" commands to verify your webhook configuration.
Then see the next section for setting up Django signal handlers to process
the webhooks.
Incidentally, you have some control over the webhook url.
If you'd like to change the "djrill" prefix, that comes from
the url config in step 2. And if you'd like to change
the *name* of the "secret" query string parameter, you can set
:setting:`DJRILL_WEBHOOK_SECRET_NAME` in your :file:`settings.py`.
For extra security, Mandrill provides a signature in the request header
X-Mandrill-Signature. If you want to verify this signature, you need to provide
the settings :setting:`DJRILL_WEBHOOK_SIGNATURE_KEY` with the webhook-specific
signature key that can be found in the Mandrill admin panel and
:setting:`DJRILL_WEBHOOK_URL` where you should enter the exact URL, including
that you entered in Mandrill when creating the webhook.
.. _webhooks control panel: https://mandrillapp.com/settings/webhooks
.. _inbound settings: https://mandrillapp.com/inbound
.. _webhook-usage:
Webhook Notifications
---------------------
Once you've enabled webhooks, Djrill will send a ``djrill.signals.webhook_event``
custom `Django signal`_ for each Mandrill event it receives.
You can connect your own receiver function to this signal for further processing.
Be sure to read Django's `listening to signals`_ docs for information on defining
and connecting signal receivers.
Examples:
.. code-block:: python
from djrill.signals import webhook_event
from django.dispatch import receiver
@receiver(webhook_event)
def handle_bounce(sender, event_type, data, **kwargs):
if event_type == 'hard_bounce' or event_type == 'soft_bounce':
print "Message to %s bounced: %s" % (
data['msg']['email'],
data['msg']['bounce_description']
)
@receiver(webhook_event)
def handle_inbound(sender, event_type, data, **kwargs):
if event_type == 'inbound':
print "Inbound message from %s: %s" % (
data['msg']['from_email'],
data['msg']['subject']
)
@receiver(webhook_event)
def handle_whitelist_sync(sender, event_type, data, **kwargs):
if event_type == 'whitelist_add' or event_type == 'whitelist_remove':
print "Rejection whitelist update: %s email %s (%s)" % (
data['action'],
data['reject']['email'],
data['reject']['reason']
)
Note that your webhook_event signal handlers will be called for all Mandrill
webhook callbacks, so you should always check the `event_type` param as shown
in the examples above to ensure you're processing the expected events.
Mandrill batches up multiple events into a single webhook call.
Djrill will invoke your signal handler once for each event in the batch.
The available fields in the `data` param are described in Mandrill's documentation:
`sent-message webhooks`_, `inbound webhooks`_, and `whitelist/blacklist sync webooks`_.
.. _Django signal: https://docs.djangoproject.com/en/stable/topics/signals/
.. _inbound webhooks:
http://help.mandrill.com/entries/22092308-What-is-the-format-of-inbound-email-webhooks-
.. _listening to signals:
https://docs.djangoproject.com/en/stable/topics/signals/#listening-to-signals
.. _sent-message webhooks: http://help.mandrill.com/entries/21738186-Introduction-to-Webhooks
.. _whitelist/blacklist sync webooks:
https://mandrill.zendesk.com/hc/en-us/articles/205583297-Sync-Event-Webhook-format