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 package, 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", "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 EmailMultiAlternatives object. Use its attach_alternative() method to send HTML:

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.

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’s 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.

Additional headers

Anymail passes additional headers to your ESP. (Some ESPs may limit which headers they’ll allow.)

msg = EmailMessage( ...
    headers={
        "List-Unsubscribe": unsubscribe_url,
        "X-Example-Header": "myapp",
    }
)

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 ANYMAIL_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 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 AnymailRecipientsRefused with the Mailgun backend.

For those ESPs, use Anymail’s delivery event tracking if you need to be notified of sends to blacklisted or invalid emails.