Sending Email in Django: A Complete Guide to send_mail, EmailMessage, and EmailMultiAlternatives

This article provides a comprehensive guide to sending email in Django. It covers the send_mail() helper, mass emailing with send_mass_mail(), advanced email composition using EmailMessage and EmailMultiAlternatives, SMTP configuration, and best practices for secure and reliable email delivery.

Django send_mail, EmailMessageEmailMultiAlternatives, send_mass_mailSMTP settings, Django email backend

~9 min read · Updated Mar 15, 2026

Introduction

Django offers a clean and convenient API for sending email, built on top of Python’s built‑in smtplib. These wrappers simplify email delivery, improve testing during development, and support environments where SMTP is not available.

All email utilities live in the django.core.mail module.


Quick Email Sending with send_mail()

The simplest way to send email in Django is using send_mail(). This function is ideal for sending plain text messages quickly.


from django.core.mail import send_mail

send_mail(
    "Subject here",
    "Here is the message.",
    "[email protected]",
    ["[email protected]"],
    fail_silently=False,
)

Required Parameters

  • subject: Email subject (string)
  • message: Plain text body
  • from_email: Sender address
  • recipient_list: List of recipients

Optional Keyword Arguments

  • fail_silently: Suppress SMTP errors
  • auth_user: Override SMTP username
  • auth_password: Override SMTP password
  • connection: Custom email backend
  • html_message: HTML version of the email

The return value is the number of successfully delivered messages (0 or 1).


Advanced Emails with EmailMessage and EmailMultiAlternatives

For more control—attachments, custom headers, HTML content—use EmailMessage or EmailMultiAlternatives.

Sending Multipart Emails (HTML + Text)


from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string

text_content = render_to_string(
    "templates/emails/my_email.txt",
    {"my_variable": 42},
)

html_content = render_to_string(
    "templates/emails/my_email.html",
    {"my_variable": 42},
)

msg = EmailMultiAlternatives(
    subject="Subject here",
    body=text_content,
    from_email="[email protected]",
    to=["[email protected]"],
    headers={"List-Unsubscribe": ""},
)

msg.attach_alternative(html_content, "text/html")
msg.send()

This approach is ideal for newsletters, transactional emails, and template‑based messages.


SMTP Configuration

Django sends email using the following settings:

  • EMAIL_HOST: SMTP server hostname
  • EMAIL_PORT: SMTP port
  • EMAIL_HOST_USER: Username
  • EMAIL_HOST_PASSWORD: Password
  • EMAIL_USE_TLS: Enable TLS
  • EMAIL_USE_SSL: Enable SSL

The character set defaults to DEFAULT_CHARSET.


Mass Emailing with send_mass_mail()

Use send_mass_mail() to send multiple emails efficiently using a single SMTP connection.

Example


message1 = (
    "Subject here",
    "Here is the message",
    "[email protected]",
    ["[email protected]", "[email protected]"],
)

message2 = (
    "Another Subject",
    "Here is another message",
    "[email protected]",
    ["[email protected]"],
)

send_mass_mail((message1, message2), fail_silently=False)

Each tuple becomes a separate email message. The return value is the number of successfully delivered messages.


Best Practices

  • Use EmailMultiAlternatives for HTML emails.
  • Use send_mass_mail() for bulk sending.
  • Store SMTP credentials securely (environment variables).
  • Use console or file email backends during development.
  • Always set fail_silently=False in production to detect errors.

Conclusion

Django’s email utilities provide a powerful yet simple interface for sending everything from basic notifications to rich HTML emails. With tools like send_mail(), EmailMessage, and EmailMultiAlternatives, you can build reliable, flexible, and secure email workflows for any application.

send_mail() vs send_mass_mail()

The key difference between send_mail() and send_mass_mail() is how they handle SMTP connections:

  • send_mail() opens a new SMTP connection for every email.
  • send_mass_mail() opens a single connection and sends all messages through it.

This makes send_mass_mail() more efficient when sending multiple emails.


mail_admins()

The mail_admins() function sends an email to all site administrators defined in the ADMINS setting.

Features

  • Automatically prefixes the subject with EMAIL_SUBJECT_PREFIX (default: “[Django] ”).
  • Uses SERVER_EMAIL as the “From:” address.
  • Supports html_message for sending HTML emails.

This function exists for convenience and readability.


mail_managers()

mail_managers() works exactly like mail_admins(), but sends email to the addresses listed in the MANAGERS setting.


Examples

Sending one email to multiple recipients (all appear in “To:”)


send_mail(
    "Subject",
    "Message.",
    "[email protected]",
    ["[email protected]", "[email protected]"],
)

Sending separate emails to each recipient


datatuple = (
    ("Subject", "Message.", "[email protected]", ["[email protected]"]),
    ("Subject", "Message.", "[email protected]", ["[email protected]"]),
)
send_mass_mail(datatuple)

Preventing Header Injection

Header injection is a security attack where an attacker inserts newline characters into email headers to manipulate “To:” or “From:” fields.

Django protects against this by raising ValueError if any header value contains a newline.

Safe Example


def send_email(request):
    subject = request.POST.get("subject", "")
    message = request.POST.get("message", "")
    from_email = request.POST.get("from_email", "")
    if subject and message and from_email:
        try:
            send_mail(subject, message, from_email, ["[email protected]"])
        except ValueError:
            return HttpResponse("Invalid header found.")
        return HttpResponseRedirect("/contact/thanks/")
    return HttpResponse("Make sure all fields are entered and valid.")

The EmailMessage Class

The send_mail() and send_mass_mail() functions are thin wrappers around the EmailMessage class.

To use advanced features such as:

  • BCC recipients
  • CC recipients
  • Reply-To headers
  • File attachments
  • Multipart emails

you must create EmailMessage objects directly.

Example


from django.core.mail import EmailMessage

email = EmailMessage(
    subject="Hello",
    body="Body goes here",
    from_email="[email protected]",
    to=["[email protected]", "[email protected]"],
    bcc=["[email protected]"],
    reply_to=["[email protected]"],
    headers={"Message-ID": "foo"},
)

To send the email:


email.send()

Conclusion

Django provides a flexible and powerful email system. Use send_mail() for simple messages, send_mass_mail() for efficient bulk sending, and EmailMessage for full control over email structure. Functions like mail_admins() and mail_managers() make system notifications easy and secure.

Introduction

Django’s email framework goes far beyond simple send_mail() usage. For advanced email composition—attachments, inline images, HTML alternatives, custom headers, and backend control—Django provides the powerful EmailMessage and EmailMultiAlternatives classes.


EmailMessage Methods

send(fail_silently=False)

Sends the email. If a connection was provided, it will be reused; otherwise, Django creates a new backend connection. Returns:

  • 1 → message sent successfully
  • 0 → message not sent

If fail_silently=True, exceptions are suppressed.


message(policy=email.policy.default)

Builds and returns a Python email.message.EmailMessage object. The policy argument controls serialization rules (e.g., SMTP line endings).

Override this method when subclassing EmailMessage to customize the underlying MIME structure.


recipients()

Returns a combined list of all recipients from:

  • to
  • cc
  • bcc

If you add custom recipient fields in a subclass, override this method.


Handling Attachments

attach(filename, content, mimetype)

Two usage patterns:

1. Standard attachment


message.attach("design.png", img_data, "image/png")

2. MIMEPart attachment (Django 6.0+)

Useful for inline images or attachments requiring custom headers.


import email.utils
from email.message import MIMEPart

cid = email.utils.make_msgid()
inline_image = MIMEPart()
inline_image.set_content(
    image_data_bytes,
    maintype="image",
    subtype="png",
    disposition="inline",
    cid=cid,
)
message.attach(inline_image)
message.attach_alternative(f'', "text/html")

Support for legacy MIMEBase is deprecated.


attach_file(path, mimetype=None)

Attach a file directly from disk:


message.attach_file("/images/weather_map.png")

EmailAttachment (Django 5.2+)

A named tuple with:

  • filename
  • content
  • mimetype

Used internally to store attachments.


Sending Multiple Content Types

EmailMultiAlternatives

A subclass of EmailMessage that supports multiple body formats (e.g., text + HTML).

Example


from django.core.mail import EmailMultiAlternatives

msg = EmailMultiAlternatives(
    "hello",
    "This is an important message.",
    "[email protected]",
    ["[email protected]"],
)
msg.attach_alternative("

This is an important message.

", "text/html") msg.send()

alternatives attribute

A list of EmailAlternative named tuples (content, mimetype).


body_contains(text)

Checks whether the given text appears in:

  • the main body
  • all text/* alternatives

Useful for testing.


Changing the Default Content Type

By default, EmailMessage uses text/plain. To send HTML as the main body:


msg = EmailMessage(subject, html_content, from_email, [to])
msg.content_subtype = "html"
msg.send()

Email Backends

The backend handles the actual sending of messages.

Backend Methods

  • open() → opens a long-lived connection
  • close() → closes the connection
  • send_messages() → sends a list of EmailMessage objects

Using a backend as a context manager


from django.core import mail

with mail.get_connection() as connection:
    mail.EmailMessage(subject1, body1, from1, [to1], connection=connection).send()
    mail.EmailMessage(subject2, body2, from2, [to2], connection=connection).send()

This ensures efficient reuse of the same SMTP connection.


Conclusion

Django’s advanced email system provides everything needed for professional-grade email delivery: attachments, inline images, HTML alternatives, custom headers, and backend control. With EmailMessage, EmailMultiAlternatives, and flexible backends, you can build robust and scalable email workflows for any application.

Obtaining an Email Backend Instance

The get_connection() function in django.core.mail returns an instance of the email backend you want to use.


from django.core.mail import get_connection

connection = get_connection()

Parameters

  • backend: Optional import path to override EMAIL_BACKEND
  • fail_silently: If True, suppresses exceptions during sending
  • Other keyword arguments → passed directly to the backend constructor

If no backend is specified, Django uses the EMAIL_BACKEND setting.


Built‑in Django Email Backends

1. SMTP Backend (Default)

Sends real emails through an SMTP server.

Settings used:

  • EMAIL_HOST
  • EMAIL_PORT
  • EMAIL_HOST_USER
  • EMAIL_HOST_PASSWORD
  • EMAIL_USE_TLS
  • EMAIL_USE_SSL
  • EMAIL_TIMEOUT
  • EMAIL_SSL_KEYFILE
  • EMAIL_SSL_CERTFILE

Explicit configuration:


EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"

2. Console Backend

Writes emails to stdout instead of sending them. Ideal for development.


EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

3. File Backend

Writes each email to a file on disk.


EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
EMAIL_FILE_PATH = "/tmp/app-messages"

4. In‑Memory Backend (locmem)

Stores emails in django.core.mail.outbox. Perfect for testing.


EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"

5. Dummy Backend

Does nothing—emails are ignored. Useful for development.


EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"

Creating a Custom Email Backend

If you need custom email‑sending behavior, you can write your own backend.

Custom backends must:

  • Subclass BaseEmailBackend
  • Implement send_messages(email_messages)

If your backend maintains a persistent connection, also implement:

  • open()
  • close()

The smtp.EmailBackend class is a good reference implementation.


Sending Multiple Emails Efficiently

Opening and closing SMTP connections is expensive. Django provides two ways to reuse a connection.

Method 1: Using send_messages()

Send a list of messages using a single connection.


from django.core import mail

connection = mail.get_connection()
messages = get_notification_email()
connection.send_messages(messages)

Method 2: Manually opening and closing the connection


from django.core import mail

connection = mail.get_connection()
connection.open()

email1 = mail.EmailMessage("Hello", "Body", "[email protected]", ["to1"], connection=connection)
email1.send()

email2 = mail.EmailMessage("Hello", "Body", "[email protected]", ["to2"])
email3 = mail.EmailMessage("Hello", "Body", "[email protected]", ["to3"])

connection.send_messages([email2, email3])
connection.close()

Email Configuration for Development

During development, you often want to inspect emails without actually sending them.

Recommended options

  • Console backend → prints emails to terminal
  • File backend → writes emails to disk
  • LocMem backend → stores emails in memory for tests

Using a local SMTP server with aiosmtpd


python -m pip install "aiosmtpd >= 1.4.5"
python -m aiosmtpd -n -l localhost:8025

This server prints all email headers and bodies to stdout. Just set EMAIL_HOST and EMAIL_PORT accordingly.


Conclusion

Django’s email backend system provides flexibility for every scenario—from real SMTP delivery to development‑friendly console and file backends. With get_connection(), custom backend support, and efficient multi‑email sending, Django gives you full control over how emails are delivered in your application.

Written & researched by Dr. Shahin Siami

Related Articles

Django Tasks Framework: A Complete Guide to Background Task Execution in Django 6.0

Django 6.0 introduces the Tasks framework, a built‑in system for defining and queuing background work outside the request–response cycle. This article explains how Tasks work, how to configure backends, how to define and enqueue tasks, how context works, and how to integrate third‑party worker systems for production environments.

Continue

Asynchronous Support in Django: A Complete Guide to Async Views, ORM, Middleware, Performance, and Safety

This article explains Django’s asynchronous (async) capabilities, including async views, ASGI support, middleware behavior, async ORM features, performance considerations, handling disconnects, and Django’s async safety protections. It also covers how to use sync_to_async(), async ORM methods, and how to safely run synchronous code in async environments.

Continue

Django System Check Framework: A Complete Guide to Writing, Registering, Running, and Testing System Checks

This article explains Django’s System Check Framework—a powerful mechanism for detecting configuration issues, validating project structure, and ensuring code quality. It covers how checks are executed, how to write custom checks, how messages work, how to register and tag checks, how to extend checks for fields and models, and how to write both unit and integration tests for system checks.

Continue

Django Signals: A Complete Guide to Listening, Connecting, Sending, and Managing Application Events

This article provides a comprehensive explanation of Django’s signal system—an event‑driven mechanism that allows decoupled applications to react to actions occurring elsewhere in the framework. It covers how to define receivers, connect signals, use decorators, handle specific senders, organize signal code, and follow best practices to avoid complexity.

Continue

Understanding Django Settings: Configuration, Environment Management, and Best Practices

This article provides a complete overview of Django’s settings system. It explains how settings files work, how to designate a settings module, how to use settings in your code, how to configure Django manually, how to secure sensitive settings, and how to work with custom default settings. It also covers the role of django.setup() for standalone scripts.

Continue

Serializing and Deserializing Django Objects: A Complete Guide to Django’s Serialization Framework

This article explains Django’s serialization framework, including how to serialize and deserialize model instances, work with subsets of fields, handle inherited models, use different serialization formats (JSON, XML, YAML, JSONL), and understand how relational fields are represented. It also covers DeserializedObject behavior and common pitfalls.

Continue