mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 03:41:05 -05:00
Update (almost) all the docs
This commit is contained in:
102
README.rst
102
README.rst
@@ -1,5 +1,5 @@
|
||||
Djrill: Mandrill Transactional Email for Django
|
||||
===============================================
|
||||
Anymail: Multi-ESP transactional email for Django
|
||||
=================================================
|
||||
|
||||
.. This README is reused in multiple places:
|
||||
* 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
|
||||
|
||||
Djrill integrates the `Mandrill <http://mandrill.com>`_ transactional
|
||||
email service into Django.
|
||||
Anymail integrates several transactional email service providers (ESPs) into Django,
|
||||
using a consistent API that makes it (relatively) easy to switch between ESPs.
|
||||
|
||||
**UPGRADING FROM DJRILL 1.x?**
|
||||
There are some **breaking changes in Djrill 2.0**. Please see the
|
||||
`upgrade instructions <http://djrill.readthedocs.org/en/latest/upgrading/>`_.
|
||||
It currently supports Mailgun and Mandrill. Postmark and SendGrid are coming soon.
|
||||
|
||||
.. 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`
|
||||
package. It includes:
|
||||
Anymail normalizes ESP functionality so it "just works" with Django's
|
||||
built-in `django.core.mail` package. It includes:
|
||||
|
||||
* Support for HTML, attachments, extra headers, and other features of
|
||||
`Django's built-in email <https://docs.djangoproject.com/en/stable/topics/email/>`_
|
||||
* Mandrill-specific extensions like tags, metadata, tracking, and MailChimp templates
|
||||
* Optional support for Mandrill inbound email and other webhook notifications,
|
||||
via Django signals
|
||||
* Extensions that make it easy to use extra ESP functionality, like tags, metadata,
|
||||
and tracking, using code that's portable between ESPs
|
||||
* 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
|
||||
(including Python 3 with Django 1.6+, and PyPy support with Django 1.5+).
|
||||
Anymail is released under the BSD license. It is tested against Django 1.8--1.9
|
||||
(including Python 3 and PyPy).
|
||||
Djrill uses `semantic versioning <http://semver.org/>`_.
|
||||
|
||||
.. END shared-intro
|
||||
|
||||
.. image:: https://travis-ci.org/brack3t/Djrill.png?branch=master
|
||||
:target: https://travis-ci.org/brack3t/Djrill
|
||||
.. image:: https://travis-ci.org/anymail/django-anymail.png?branch=master
|
||||
:target: https://travis-ci.org/anymail/django-anymail
|
||||
:alt: build status on Travis-CI
|
||||
|
||||
|
||||
**Resources**
|
||||
|
||||
* Full documentation: https://djrill.readthedocs.org/en/latest/
|
||||
* Package on PyPI: https://pypi.python.org/pypi/djrill
|
||||
* Project on Github: https://github.com/brack3t/Djrill
|
||||
* Full documentation: https://anymail.readthedocs.org/en/latest/
|
||||
* Package on PyPI: https://pypi.python.org/pypi/django-anymail
|
||||
* Project on Github: https://github.com/anymail/django-anymail
|
||||
|
||||
|
||||
Djrill 1-2-3
|
||||
------------
|
||||
Anymail 1-2-3
|
||||
-------------
|
||||
|
||||
.. _quickstart:
|
||||
|
||||
.. 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
|
||||
|
||||
$ pip install djrill
|
||||
$ pip install anymail[mailgun] # or anymail[postmark,sendgrid] or ...
|
||||
|
||||
|
||||
2. Edit your project's ``settings.py``:
|
||||
@@ -72,43 +82,53 @@ Djrill 1-2-3
|
||||
|
||||
INSTALLED_APPS = (
|
||||
...
|
||||
"djrill"
|
||||
"anymail"
|
||||
)
|
||||
|
||||
MANDRILL_API_KEY = "<your Mandrill key>"
|
||||
EMAIL_BACKEND = "djrill.mail.backends.djrill.DjrillBackend"
|
||||
ANYMAIL = {
|
||||
"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
|
||||
|
||||
|
||||
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
|
||||
|
||||
from django.core.mail import send_mail
|
||||
|
||||
send_mail("It works!", "This will get sent through Mandrill",
|
||||
"Djrill Sender <djrill@example.com>", ["to@example.com"])
|
||||
send_mail("It works!", "This will get sent through Mailgun",
|
||||
"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
|
||||
|
||||
from django.core.mail import EmailMultiAlternatives
|
||||
from anymail.message import attach_inline_image
|
||||
|
||||
msg = EmailMultiAlternatives(
|
||||
subject="Djrill Message",
|
||||
body="This is the text email body",
|
||||
from_email="Djrill Sender <djrill@example.com>",
|
||||
to=["Recipient One <someone@example.com>", "another.person@example.com"],
|
||||
headers={'Reply-To': "Service <support@example.com>"} # optional extra headers
|
||||
)
|
||||
msg.attach_alternative("<p>This is the HTML email body</p>", "text/html")
|
||||
subject="Please activate your account",
|
||||
body="Click to activate your account: http://example.com/activate",
|
||||
from_email="Example <admin@example.com>",
|
||||
to=["New User <user1@example.com>", "account.manager@example.com"],
|
||||
reply_to=["Helpdesk <support@example.com>"])
|
||||
|
||||
# Optional Mandrill-specific extensions:
|
||||
msg.tags = ["one tag", "two tag", "red tag", "blue tag"]
|
||||
msg.metadata = {'user_id': "8675309"}
|
||||
# Include an inline image in the html:
|
||||
logo_cid = attach_inline_image(msg, open("logo.jpg", "rb").read())
|
||||
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:
|
||||
msg.send()
|
||||
@@ -116,5 +136,5 @@ Djrill 1-2-3
|
||||
.. 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.
|
||||
|
||||
@@ -259,7 +259,7 @@ texinfo_documents = [
|
||||
# -- Options for Intersphinx ------------------------------------------------
|
||||
|
||||
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/'),
|
||||
'requests': ('http://docs.python-requests.org/en/latest/', None),
|
||||
}
|
||||
|
||||
@@ -3,31 +3,45 @@
|
||||
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
|
||||
of some of the people who have helped improve Djrill.
|
||||
The `Anymail source code`_ is on GitHub.
|
||||
|
||||
.. _Djrill source code: https://github.com/brack3t/Djrill
|
||||
.. _AUTHORS.txt: https://github.com/brack3t/Djrill/blob/master/AUTHORS.txt
|
||||
.. _Anymail source code: https://github.com/anymail/django-anymail
|
||||
|
||||
|
||||
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
|
||||
----
|
||||
|
||||
You can report problems or request features in
|
||||
`Djrill's github issue tracker <https://github.com/brack3t/Djrill/issues>`_.
|
||||
You can report problems or request features in `Anymail's GitHub issue tracker`_.
|
||||
|
||||
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.
|
||||
* 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
|
||||
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
|
||||
-------
|
||||
|
||||
Djrill is `tested on Travis <https://travis-ci.org/brack3t/Djrill>`_ against several
|
||||
combinations of Django and Python versions. (Full list in
|
||||
`.travis.yml <https://github.com/brack3t/Djrill/blob/master/.travis.yml>`_.)
|
||||
Anymail is `tested on Travis`_ against several combinations of Django
|
||||
and Python versions. (Full list in `.travis.yml`_.)
|
||||
|
||||
Most of the included tests verify that Djrill constructs the expected Mandrill API
|
||||
calls, without actually calling Mandrill or sending any email. So these tests
|
||||
don't require a Mandrill API key, but they *do* require
|
||||
`mock <http://www.voidspace.org.uk/python/mock/index.html>`_
|
||||
and `six <https://pythonhosted.org/six/>`_ (``pip install mock six``).
|
||||
Most of the included tests verify that Anymail constructs the expected ESP API
|
||||
calls, without actually calling the ESP's API or sending any email. So these tests
|
||||
don't require API keys, but they *do* require `mock`_ (``pip install mock``).
|
||||
|
||||
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
|
||||
`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.)
|
||||
$ python -Wall runtests.py
|
||||
|
||||
.. _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
23
docs/esps/index.rst
Normal 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
103
docs/esps/mailgun.rst
Normal 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
151
docs/esps/mandrill.rst
Normal 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
17
docs/esps/postmark.rst
Normal 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
17
docs/esps/sendgrid.rst
Normal 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.)
|
||||
146
docs/history.rst
146
docs/history.rst
@@ -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
18
docs/inbound.rst
Normal 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
|
||||
---------------------
|
||||
@@ -1,10 +1,5 @@
|
||||
.. Djrill documentation master file, created by
|
||||
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
|
||||
===============================================
|
||||
Anymail: Multi-ESP transactional email for Django
|
||||
=================================================
|
||||
|
||||
Version |release|
|
||||
|
||||
@@ -15,6 +10,8 @@ Version |release|
|
||||
:end-before: END shared-intro
|
||||
|
||||
|
||||
.. _main-toc:
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
@@ -23,21 +20,10 @@ Documentation
|
||||
|
||||
quickstart
|
||||
installation
|
||||
upgrading
|
||||
usage/sending_mail
|
||||
usage/templates
|
||||
usage/multiple_backends
|
||||
usage/webhooks
|
||||
sending/index
|
||||
inbound
|
||||
esps/index
|
||||
tips/index
|
||||
troubleshooting
|
||||
contributing
|
||||
history
|
||||
|
||||
|
||||
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
|
||||
release_notes
|
||||
|
||||
@@ -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
|
||||
|
||||
$ pip install djrill
|
||||
$ pip install anymail[mailgun]
|
||||
|
||||
If you decide to install Djrill some other way, you'll also need to install its
|
||||
one dependency (other than Django, of course): the `requests <http://docs.python-requests.org>`_
|
||||
library from Kenneth Reitz.
|
||||
...or with both Postmark and SendGrid support:
|
||||
|
||||
.. 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 = (
|
||||
...
|
||||
"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::
|
||||
|
||||
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,
|
||||
ANYMAIL = {
|
||||
"MAILGUN_API_KEY" = "<your Mailgun key>",
|
||||
}
|
||||
|
||||
See :ref:`mandrill-send-support` for a list of available options. (Everything
|
||||
*except* :attr:`merge_vars`, :attr:`recipient_metadata`, and :attr:`send_at`
|
||||
can be used with :setting:`!MANDRILL_SETTINGS`.)
|
||||
3. Change your existing Django :setting:`EMAIL_BACKEND` to the Anymail backend
|
||||
for your ESP. For example, to send using Mailgun by default:
|
||||
|
||||
Attributes set on individual EmailMessage objects will override the global
|
||||
: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).
|
||||
.. code-block:: python
|
||||
|
||||
.. 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
|
||||
``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.)
|
||||
If you want to use Anymail's status tracking webhooks, follow the steps above
|
||||
to :ref:`configure an Anymail backend <backend-configuration>`, and then
|
||||
follow the instructions in the :ref:`event-tracking` section to set up
|
||||
the delivery webhooks.
|
||||
|
||||
|
||||
.. 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
|
||||
be used to globally set the `Mandrill subaccount <subaccounts>`_.
|
||||
Although this is still supported for compatibility with existing code,
|
||||
new code should set a global subaccount in :setting:`MANDRILL_SETTINGS`
|
||||
as shown above.
|
||||
If you want to use inbound email with Anymail, first follow the first two
|
||||
:ref:`backend configuration <backend-configuration>` steps above. (You can
|
||||
skip changing your :setting:`EMAIL_BACKEND` if you don't want to us Anymail
|
||||
for *sending* messages.) Then follow the instructions in the
|
||||
: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`.)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Djrill 1-2-3
|
||||
============
|
||||
Anymail 1-2-3
|
||||
=============
|
||||
|
||||
.. Quickstart is maintained in README.rst at the source root.
|
||||
(Docs can include from the readme; the readme can't include anything.)
|
||||
@@ -7,3 +7,16 @@ Djrill 1-2-3
|
||||
.. include:: ../README.rst
|
||||
:start-after: _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
23
docs/release_notes.rst
Normal 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
|
||||
449
docs/sending/anymail_additions.rst
Normal file
449
docs/sending/anymail_additions.rst
Normal 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
|
||||
162
docs/sending/django_email.rst
Normal file
162
docs/sending/django_email.rst
Normal 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.
|
||||
50
docs/sending/exceptions.rst
Normal file
50
docs/sending/exceptions.rst
Normal 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
13
docs/sending/index.rst
Normal 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
146
docs/sending/templates.rst
Normal 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
178
docs/sending/tracking.rst
Normal 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
|
||||
55
docs/tips/django_templates.rst
Normal file
55
docs/tips/django_templates.rst
Normal 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
16
docs/tips/index.rst
Normal 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)
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
.. _multiple-backends:
|
||||
|
||||
Mixing Email Backends
|
||||
Mixing email backends
|
||||
=====================
|
||||
|
||||
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>`
|
||||
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:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 8,10
|
||||
:emphasize-lines: 8,10,13,15
|
||||
|
||||
from django.core.mail import send_mail, get_connection
|
||||
|
||||
# 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"])
|
||||
|
||||
# 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"],
|
||||
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
|
||||
:func:`~django.core.mail.send_mail` and :func:`~django.core.mail.send_mass_mail` helpers,
|
||||
and in the constructor for an
|
||||
: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:
|
||||
https://docs.djangoproject.com/en/stable/topics/logging/#django.utils.log.AdminEmailHandler
|
||||
@@ -3,68 +3,60 @@
|
||||
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
|
||||
error logs. As of v1.4, Djrill reports the detailed Mandrill error when
|
||||
something goes wrong. And when the error is something like "invalid API key"
|
||||
error logs. If you see something like "invalid API key"
|
||||
or "invalid email address", that's probably 90% of what you'll need to know
|
||||
to solve the problem.
|
||||
|
||||
* **Check the Mandrill API logs:** The Mandrill dashboard includes an
|
||||
*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.
|
||||
**Check your ESPs API logs**
|
||||
|
||||
* **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?
|
||||
* Did you add ``'djrill'`` to the list of :setting:`INSTALLED_APPS` in settings.py?
|
||||
**Double-check common issues**
|
||||
|
||||
* 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",
|
||||
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`
|
||||
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
|
||||
other than Djrill. And you can look through the :setting:`EMAIL_FILE_PATH`
|
||||
**Try it without Anymail**
|
||||
|
||||
Try switching your :setting:`EMAIL_BACKEND` setting to
|
||||
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.
|
||||
|
||||
|
||||
.. _recent API calls: https://mandrillapp.com/settings/api
|
||||
.. _File backend: https://docs.djangoproject.com/en/stable/topics/email/#file-backend
|
||||
|
||||
|
||||
Getting Help
|
||||
Getting help
|
||||
------------
|
||||
|
||||
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
|
||||
people who use it -- like you! (We're not Mandrill employees.)
|
||||
the Anymail community is happy to help. Anymail is supported and maintained by the
|
||||
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`_
|
||||
Tag your question with **both** ``Django`` and ``Mandrill`` to get our attention.
|
||||
Bonus: a lot of questions about Djrill are actually questions about Django
|
||||
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.
|
||||
Whenever you open an issue, it's always helpful to mention which ESP you're using,
|
||||
include the relevant portions of your code and settings, the text of any error messages,
|
||||
and any exception stack traces.
|
||||
|
||||
|
||||
.. _StackOverflow: http://stackoverflow.com/questions/tagged/django+mandrill
|
||||
.. _GitHub issue: https://github.com/brack3t/Djrill/issues
|
||||
.. _GitHub issue: https://github.com/anymail/django-anymail/issues
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user