Skip to main content

Email Composition and Delivery

To send emails in this project, you use the EmailMessage class for composition and the MAILERS configuration to define how those messages are delivered.

Sending a Basic Email

The EmailMessage class is the primary tool for composing plain text emails. It handles headers, recipients, and the message body.

from django.core.mail import EmailMessage

email = EmailMessage(
subject="Order Confirmation",
body="Your order #12345 has been shipped.",
from_email="support@example.com",
to=["customer@example.com"],
cc=["archive@example.com"],
reply_to=["helpdesk@example.com"],
)
email.send()

Key Requirements:

  • Recipients: The to, cc, bcc, and reply_to arguments must be lists or tuples. Passing a single string will raise a TypeError.
  • From Email: If from_email is omitted, the value of settings.DEFAULT_FROM_EMAIL is used.

Sending HTML and Multipart Emails

To send emails with both plain text and HTML versions, use the EmailMultiAlternatives class. This ensures that clients that cannot render HTML can still display the plain text version.

from django.core.mail import EmailMultiAlternatives

subject = "Weekly Newsletter"
text_content = "Check out our latest updates on the website."
html_content = "<h1>Weekly Newsletter</h1><p>Check out our <strong>latest updates</strong> on the website.</p>"
from_email = "newsletter@example.com"
to = ["subscriber@example.com"]

msg = EmailMultiAlternatives(subject, text_content, from_email, to)
msg.attach_alternative(html_content, "text/html")
msg.send()

Configuring Mailers

The delivery mechanism is controlled by the MAILERS setting in your settings.py. This allows you to define multiple backends (e.g., one for marketing, one for transactional emails).

# settings.py
MAILERS = {
"default": {
"BACKEND": "django.core.mail.backends.smtp.EmailBackend",
"OPTIONS": {
"host": "smtp.sendgrid.net",
"port": 587,
"use_tls": True,
"username": "apikey",
"password": "your_api_key",
},
},
"console": {
"BACKEND": "django.core.mail.backends.console.EmailBackend",
},
}

Selecting a Mailer at Runtime

When calling .send(), you can specify which configured mailer to use by passing the using argument with the alias defined in your MAILERS setting.

# Uses the 'console' mailer instead of 'default'
email.send(using="console")

You can also access mailers directly via the mailers handler:

from django.core.mail import mailers

# Get the console mailer instance
mailer = mailers["console"]
mailer.send_messages([email])

Adding Attachments

You can attach files using attach() (for content in memory) or attach_file() (for files on disk).

# Attaching a file from the filesystem
email.attach_file("/path/to/report.pdf")

# Attaching content directly
email.attach("invoice.txt", "Invoice content...", "text/plain")

Built-in Backends

This project provides several backends for different environments:

Backend ClassPurpose
django.core.mail.backends.smtp.EmailBackendSends emails via an SMTP server (Production).
django.core.mail.backends.console.EmailBackendWrites emails to stdout (Development).
django.core.mail.backends.filebased.EmailBackendSaves emails as files in a directory. Requires file_path in OPTIONS.
django.core.mail.backends.locmem.EmailBackendStores emails in django.core.mail.outbox (Testing).
django.core.mail.backends.dummy.EmailBackendDoes nothing; returns success (Testing).

Troubleshooting and Best Practices

  • Header Validation: Do not include newlines in subject or header values. This project raises a ValueError (formerly BadHeaderError) if CRLF injection is detected.
  • Deprecation Warning: The connection argument in EmailMessage and send() is deprecated. Use the using argument with a MAILERS alias instead.
  • Deprecation Warning: Directly instantiating backend classes (e.g., EmailBackend()) is deprecated. Use mailers["alias"] to obtain backend instances.
  • Bcc Handling: Recipients in the bcc list are included in the delivery but are automatically stripped from the message headers to ensure privacy.
  • Performance: For sending multiple emails, the backends support context manager usage to reuse a single network connection:
    with mailers["default"] as connection:
    for msg in bulk_messages:
    connection.send_messages([msg])