Django has email support built in. You do not need a third-party package to send a password-reset message, an order receipt or a contact-form notification: you configure an email backend in settings.py, call send_mail() or build an EmailMultiAlternatives object, and Django hands the message to your SMTP server. This guide covers the settings that matter, the difference between TLS and SSL in Django's configuration, how to send plain text, HTML and attachments, how to test mail without sending anything, how to move sending out of the request cycle and how to fix the errors that appear most often. The examples use placeholders. Take the real host, user name and password from your SMTP provider's dashboard and keep them in environment variables, not in the repository.
The settings you need
Django reads email configuration from settings.py. For SMTP, set the backend and the connection details, and read the secrets from the environment:
# settings.py
import os
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = os.environ["EMAIL_HOST"] # e.g. smtp.example.com
EMAIL_PORT = int(os.environ.get("EMAIL_PORT", "587"))
EMAIL_HOST_USER = os.environ["EMAIL_HOST_USER"]
EMAIL_HOST_PASSWORD = os.environ["EMAIL_HOST_PASSWORD"]
EMAIL_USE_TLS = True # STARTTLS, normally port 587
EMAIL_USE_SSL = False # implicit TLS, normally port 465
EMAIL_TIMEOUT = 20 # seconds
DEFAULT_FROM_EMAIL = "Example App <[email protected]>"
SERVER_EMAIL = "[email protected]" # used for error mail to ADMINS
EMAIL_SUBJECT_PREFIX = "[Example] " # used by mail_admins()Two details prevent most first-time failures. First, EMAIL_USE_TLS and EMAIL_USE_SSL are mutually exclusive: Django raises an error if both are true. Use EMAIL_USE_TLS = True with port 587, or EMAIL_USE_SSL = True with port 465, never both. Second, set EMAIL_TIMEOUT. Without a timeout a blocked outbound port can hold a web worker for a long time while it waits for the connection, and that is how a mail problem becomes a site-wide slowdown. If you are not sure which port your server allows, read which SMTP port to use.
Use DEFAULT_FROM_EMAIL on purpose
If you do not pass from_email, Django uses DEFAULT_FROM_EMAIL. The address must belong to a domain you have authenticated with your sending provider, otherwise the server may refuse it or the receiving mailbox may mark it as suspicious. SERVER_EMAIL is different: it is the sender of error reports that Django sends to the addresses in ADMINS, so give it a monitored address on the same domain. The guide on authenticating your sending domain explains the DNS records.
Send a simple message
send_mail() is the shortest route. It returns the number of messages delivered to the server, which is 1 on success. With fail_silently=False, the default, it raises an exception when the connection or the login fails, which is what you want in most code paths.
from django.core.mail import send_mail
sent = send_mail(
subject="Your order has shipped",
message="Hi Alex, your order #1042 is on its way.",
from_email=None, # uses DEFAULT_FROM_EMAIL
recipient_list=["[email protected]"],
fail_silently=False,
)Keep the subject on one line. Django rejects header values that contain newlines and raises BadHeaderError, which protects you from header injection when you build a subject from user input. Do not try to bypass this check.
Send HTML with a plain-text alternative
For HTML, use EmailMultiAlternatives. Put a plain-text body in the message and attach the HTML as an alternative. Mail clients that cannot show HTML use the text part, and mailbox filters tend to trust a message that includes both.
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
def send_welcome(user):
ctx = {"name": user.first_name}
text = render_to_string("emails/welcome.txt", ctx)
html = render_to_string("emails/welcome.html", ctx)
msg = EmailMultiAlternatives(
subject="Welcome to Example",
body=text,
to=[user.email],
reply_to=["[email protected]"],
)
msg.attach_alternative(html, "text/html")
msg.send()Build the two templates from the same context so that the text and the HTML say the same thing. Use inline styles and simple table layouts in the HTML, because many email clients ignore external CSS. Our HTML email template guide lists what renders reliably.
Attach files
Use attach_file() for a file on disk or attach() for content in memory. Always pass the MIME type for generated content so that the recipient's client can open it.
msg.attach_file("/srv/invoices/1042.pdf") # from disk
msg.attach("report.csv", csv_text, "text/csv") # in memoryKeep attachments small. Large files make messages slow to send and are more likely to be rejected, and a link to a download page is often a better choice. As a rule of thumb, treat anything above a few megabytes as a candidate for a link.
Send several messages over one connection
Each call to send_mail() opens and closes an SMTP connection unless you pass one. To send a batch, open one connection and reuse it:
from django.core.mail import get_connection, EmailMessage
with get_connection() as conn:
messages = [
EmailMessage("Hello", body, to=[addr], connection=conn)
for addr, body in rows
]
conn.send_messages(messages)send_mass_mail() does something similar for tuples of data. Both are fine for a few hundred messages, but neither is a substitute for a queue. For larger volumes, follow the pattern in rate limits, retries and backoff.
Test without sending real mail
You should not send real messages from a test run. Django's test runner automatically replaces the backend with the in-memory locmem backend and stores sent messages in django.core.mail.outbox, so you can assert on them:
from django.core import mail
from django.test import TestCase
class WelcomeTests(TestCase):
def test_welcome_is_sent(self):
send_welcome(self.user)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, "Welcome to Example")
self.assertEqual(mail.outbox[0].to, [self.user.email])In development, set EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" to print messages to the terminal, or the filebased backend with EMAIL_FILE_PATH to save them as files. To check a real SMTP account before you wire it into the app, use the steps in how to test an SMTP connection.
Move sending out of the request
An SMTP conversation takes anywhere from a fraction of a second to many seconds when the network is slow. If a view sends the message before it returns a response, the visitor waits for it, and a mail outage becomes a site outage. Send in the background. The usual approach is a task queue such as Celery, RQ or Django's own task framework in recent releases: the view enqueues a task with the data it needs, and a worker builds and sends the message. Pass identifiers, not objects, so the worker loads fresh data. Make the task safe to retry, record the outcome and treat temporary failures differently from permanent ones. If a view must send in the request, such as a password reset, keep EMAIL_TIMEOUT short and show a friendly message when sending fails. Django also offers transaction.on_commit(), which is useful so that you only enqueue the task after the database transaction that created the record has been committed.
Use a different account for one message
Some projects send transactional mail through one account and marketing mail through another. You do not need a second settings file. Build a connection with explicit credentials and pass it to the message, and Django uses it instead of the defaults for that message only. This is also the cleanest way to keep password-reset mail on a separate sending domain from newsletters, which protects the reputation of the transactional domain.
from django.core.mail import get_connection, send_mail
conn = get_connection(
host="smtp.example.com", port=587,
username=user, password=pw, use_tls=True, timeout=20,
)
send_mail("Reset your password", body, "[email protected]",
[address], connection=conn)Django's built-in authentication emails, such as the password-reset flow, use DEFAULT_FROM_EMAIL unless you pass from_email to the form. Check the sender of those messages as well as your own, because they are often the first message a new user receives. Also remember that mail_admins() and mail_managers() use SERVER_EMAIL and the ADMINS and MANAGERS lists, so make sure those are set correctly in production, or error reports will go nowhere.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
SMTPAuthenticationError: (535, ...) | Wrong user or password, or a truncated environment variable | Re-copy the credentials and print their length in a shell. See SMTP error 535 |
ConnectionRefusedError or timeout | Wrong host or port, or the outbound port is blocked | Test the port from the same server and check the firewall |
ssl.SSLError: WRONG_VERSION_NUMBER | EMAIL_USE_SSL used with port 587 | Use EMAIL_USE_TLS with 587 or EMAIL_USE_SSL with 465 |
ValueError: EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive | Both flags are true | Enable only one |
SMTPSenderRefused | The From domain is not verified for this account | Send from an authenticated domain |
BadHeaderError | A newline in a subject or header value | Strip newlines from user-supplied header values |
| Mail works in the shell, not in production | Environment variables not loaded by the process manager | Check the service environment and restart the workers |
| Mail is accepted but lands in spam | Missing authentication or a poor sender reputation | Publish SPF, DKIM and DMARC; see the spam checklist |
Security and hygiene
Keep credentials out of version control and rotate them if they are ever exposed. Do not log message bodies that contain tokens or personal data. Validate addresses before you send, and avoid sending to addresses that you know are invalid, because repeated bounces damage your reputation; the guide on email validation before sending lists useful checks. Protect public forms with rate limits and a CAPTCHA or token check, because an open contact form can be abused to send spam in your name.
Send Django mail through a managed relay Use the SMTP host and credentials from your dashboard and send your first message in minutes.
References and further reading
Frequently asked questions
Why does Django say EMAIL_USE_TLS and EMAIL_USE_SSL are mutually exclusive?
They select different ways of encrypting the connection. Use EMAIL_USE_TLS with port 587 for STARTTLS, or EMAIL_USE_SSL with port 465 for implicit TLS, and never enable both.
How do I send HTML email in Django?
Create an EmailMultiAlternatives object with a plain-text body, call attach_alternative with the HTML and the text/html type, then call send.
How can I test Django email without sending it?
Django tests use the locmem backend automatically. Sent messages are stored in django.core.mail.outbox, where you can assert on them. In development you can use the console backend.
Should I send email inside a Django view?
Only when you must. For anything that does not need to be immediate, enqueue a background task so that a slow SMTP server does not slow the response.
Where should I keep the SMTP password in Django?
In an environment variable or a secrets manager that settings.py reads at startup, never in the repository.