Django email support¶
Anymail builds on Django’s core email functionality. If you are already sending
email using Django’s default 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 Sending email in the Django docs first.
Anymail supports most of the functionality of Django’s EmailMessage
and EmailMultiAlternatives
classes.
Anymail handles all outgoing email sent through Django’s
django.core.mail
module, including send_mail()
,
send_mass_mail()
, the EmailMessage
class,
and even 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 multiple email backends.
HTML email¶
To send an HTML message, you can simply use Django’s send_mail()
function with the html_message
parameter:
from django.core.mail import send_mail send_mail("Subject", "text body", "[email protected]", ["[email protected]"], html_message="<html>html body</html>")
However, many Django email capabilities—and additional Anymail features—are only
available when working with an EmailMultiAlternatives
object. Use its attach_alternative()
method to send HTML:
from django.core.mail import EmailMultiAlternatives msg = EmailMultiAlternatives("Subject", "text body", "[email protected]", ["[email protected]"]) 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.
Attachments¶
Anymail will send a message’s attachments to your ESP. You can add attachments
with the attach()
or
attach_file()
methods
of Django’s EmailMessage
.
Note that some ESPs impose limits on the size and type of attachments they will send.
Inline images
If your message has any attachments with Content-Disposition: inline
headers, Anymail will tell your ESP to treat them as inline rather than ordinary
attached files. If you want to reference an attachment from an <img>
in your
HTML source, the attachment also needs a Content-ID header.
Anymail comes with attach_inline_image()
and
attach_inline_image_file()
convenience functions that
do the right thing. See Inline images in the “Anymail additions” section.
(If you prefer to do the work yourself, Python’s MIMEImage
and add_header()
should be helpful.)
Even if you mark an attachment as inline, some email clients may decide to also display it as an attachment. This is largely outside your control.
Changed in version 4.3: For convenience, Anymail will treat an attachment with a Content-ID but no Content-Disposition as inline. (Many—though not all—email clients make the same assumption. But to ensure consistent behavior with non-Anymail email backends, you should always set both Content-ID and Content-Disposition: inline headers for inline images. Or just use Anymail’s inline image helpers, which handle this for you.)
Additional headers¶
Anymail passes additional headers to your ESP. (Some ESPs may limit
which headers they’ll allow.) EmailMessage expects a dict
of headers:
# Use `headers` when creating an EmailMessage msg = EmailMessage( ... headers={ "List-Unsubscribe": unsubscribe_url, "X-Example-Header": "myapp", } ) # Or use the `extra_headers` attribute later msg.extra_headers["In-Reply-To"] = inbound_msg["Message-ID"]
Anymail treats header names as case-insensitive (because that’s how email handles them). If you supply multiple headers that differ only in case, only one of them will make it into the resulting email.
Django’s default SMTP EmailBackend
has special handling for certain headers. Anymail replicates its behavior for compatibility:
If you supply a “Reply-To” header, it will override the message’s
reply_to
attribute.If you supply a “From” header, it will override the message’s
from_email
and become the From field the recipient sees. In addition, the originalfrom_email
value will be used as the message’senvelope_sender
, which becomes the Return-Path at the recipient end. (Only if your ESP supports altering envelope sender, otherwise you’ll get an unsupported feature error.)If you supply a “To” header, you’ll usually get an unsupported feature error. With Django’s SMTP EmailBackend, this can be used to show the recipient a To address that’s different from the actual envelope recipients in the message’s
to
list. Spoofing the To header like this is popular with spammers, and almost none of Anymail’s supported ESPs allow it.
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
AnymailUnsupportedFeature
error, and the message won’t be sent.
For example, very few ESPs support alternative message parts added with
attach_alternative()
(other than a single text/html part that becomes the HTML body).
If you try to send a message with other alternative parts, Anymail will
raise AnymailUnsupportedFeature
.
If you’d like to silently ignore AnymailUnsupportedFeature
errors and send the messages anyway, set
"IGNORE_UNSUPPORTED_FEATURES"
to True
in your settings.py:
ANYMAIL = { ... "IGNORE_UNSUPPORTED_FEATURES": True, }
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
AnymailRecipientsRefused
error.
You can examine the message’s anymail_status
attribute to determine the cause of the error. (See 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
send_at
), then this exception will not be raised.
You can still examine the message’s anymail_status
property after the send to determine the status of each recipient.
You can disable this exception by setting
"IGNORE_RECIPIENT_STATUS"
to True
in
your settings.py ANYMAIL
dict, which will cause Anymail to treat any
response from your ESP (other than an API error) as a successful send.
Note
Most ESPs don’t check recipient status during the send API call. For example,
Mailgun always queues sent messages, so you’ll never catch
AnymailRecipientsRefused
with the Mailgun backend.
You can use Anymail’s delivery event tracking if you need to be notified of sends to suppression-listed or invalid emails.