Documentation menu

Django

Configure Django's email backend to send development mail to MailDev, keep production settings separate, and test outgoing email with the Django test client.

Django's SMTP backend needs four settings. Add them to your development settings module:

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'localhost'
EMAIL_PORT = 1025
EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = ''
EMAIL_USE_TLS = False
EMAIL_USE_SSL = False
DEFAULT_FROM_EMAIL = 'app@example.com'

EMAIL_BACKEND is the SMTP backend by default, so that line is optional — but being explicit helps if something else in your settings chain has swapped it for the console or locmem backend.

Reading it from the environment

The version that also works in Docker and CI, where MailDev is not on localhost:

import os

EMAIL_HOST = os.environ.get('EMAIL_HOST', 'localhost')
EMAIL_PORT = int(os.environ.get('EMAIL_PORT', 1025))
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER', '')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD', '')
EMAIL_USE_TLS = os.environ.get('EMAIL_USE_TLS', 'false').lower() == 'true'

With django-environ, a single EMAIL_URL covers it:

import environ

env = environ.Env()
vars().update(env.email_url('EMAIL_URL', default='smtp://localhost:1025'))

Send a test message

The shell is the quickest check:

python manage.py shell
from django.core.mail import send_mail

send_mail(
    subject='Hello from MailDev',
    message='It works!',
    from_email='app@example.com',
    recipient_list=['user@test.com'],
)

Or use the management command, which exists precisely for this:

python manage.py sendtestemail user@test.com

HTML email

MailDev shows the HTML part, the plain-text alternative, and the raw source separately, which makes it easy to notice that your text fallback is empty:

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

context = {'name': 'Ada'}
text_body = render_to_string('emails/welcome.txt', context)
html_body = render_to_string('emails/welcome.html', context)

message = EmailMultiAlternatives(
    subject='Welcome',
    body=text_body,
    from_email='app@example.com',
    to=['user@test.com'],
)
message.attach_alternative(html_body, 'text/html')
message.send()

Attachments

message.attach_file('reports/summary.pdf')
message.attach('data.csv', csv_string, 'text/csv')

Both appear in the inbox as downloadable files, so you can open the PDF your view actually generated rather than trusting that it worked.

Password reset and other built-in mail

Django's auth views send through the same backend, so django.contrib.auth's password-reset flow lands in MailDev with no extra configuration. That is the fastest way to check a customized registration/password_reset_email.html — trigger the flow, read the message, and click the link straight out of the HTML preview.

Testing

For unit tests, Django's locmem backend and mail.outbox are the right tool — they need no external process and the test suite sets them up automatically.

MailDev earns its place in end-to-end tests, where the mail is sent by a real server process that your test cannot reach into. Point the app at MailDev and read the REST API:

import time
import requests

MAILDEV = 'http://localhost:1080'


def wait_for_email(subject, timeout=10):
    deadline = time.time() + timeout
    while time.time() < deadline:
        response = requests.get(f'{MAILDEV}/api/email/summary', params={'search': subject})
        items = response.json()['items']
        if items:
            return requests.get(f'{MAILDEV}/api/email/{items[0]["id"]}').json()
        time.sleep(0.2)
    raise AssertionError(f'No email matching {subject!r} arrived')


def clear_inbox():
    requests.delete(f'{MAILDEV}/api/email/all')

More patterns in testing email in CI.

Celery

If you send mail from a Celery task, the worker needs the same settings — it is a separate process. Check that it loads the development settings module, or the worker will silently try your production SMTP host.

Troubleshooting

ConnectionRefusedError: [Errno 111] — MailDev is not running, or Django is in a container and pointing at its own localhost.

SMTPNotSupportedError: STARTTLS extension not supported — you have EMAIL_USE_TLS = True against a listener that does not require it. Set it to False, or configure TLS on MailDev.

Nothing arrives and no error is raised. Something set EMAIL_BACKEND to console or locmem — check your settings chain, including anything a test runner injects.

Last updated 2026-08-25Edit this pageView as markdown