Django SMTP Testing: Catch Every Email Before It Reaches Real Users
Maildog gives your Django app a real SMTP server that catches every message — password resets, verification links, invoices, and notifications — in a safe inbox you can inspect. Point Django at mail.maildog.io:2525, run your tests, and never accidentally email a customer from staging again.
Django makes sending email easy — and testing it dangerous
One call to send_mail() or EmailMessage.send() and your message is on its way. The hard part is testing that email safely, and Django's defaults push developers into one of several traps.
The console backend hides too much
django.core.mail.backends.console.EmailBackend just prints emails to stdout. You can see that something was sent, but you can't verify HTML rendering, check real headers, click a verification link, or confirm what a recipient's inbox would show.
The SMTP backend sends real mail
If staging or a developer laptop is accidentally pointed at a production SMTP relay, you can email thousands of real users with test data — and Django's EMAIL_BACKEND is just a settings string one stray env var away from disaster.
locmem and mailoutbox aren't enough
The in-memory locmem backend stores messages in django.core.mail.outbox for assertions, but it's useless for visually inspecting what a human actually receives. It tells you a message object exists — not whether it renders correctly or whether SPF/DKIM would pass.
Generated emails are the real pain
Password resets from PasswordResetView, django-allauth verification, and template-rendered invoices are generated deep inside the framework and its packages. You can't eyeball them in code — you need to see the rendered message and click the real link.
A real SMTP server that catches Django email instead of delivering it
Maildog is purpose-built for django smtp testing: a sandbox inbox that speaks SMTP exactly like a production mail server, so your Django EMAIL_BACKEND stays set to the genuine SMTP backend — no fake module, no monkeypatching. Every message travels over a real authenticated, TLS-encrypted connection and is trapped before it can reach a real recipient.
Inspect rendered HTML and text. See the fully rendered HTML and plain-text versions side by side, plus every header, attachment, and MIME part for each captured message.
Click real reset and allauth links. Click the actual tokenized password-reset and django-allauth verification links to walk through the full confirmation flow without a live mailbox.
Validate your real settings. Because the connection is real SMTP, your EMAIL_HOST, EMAIL_PORT, EMAIL_HOST_USER, EMAIL_HOST_PASSWORD, and EMAIL_USE_TLS are all genuinely exercised — the same code path that runs in production runs against the sandbox.
Connect in minutes. Each sandbox inbox has its own unique SMTP username and password, shown on the inbox's Integration tab in the Maildog panel. Drop those into your Django settings and you're testing right away.
Key features
Real Sandbox SMTP Server for Django
A genuine SMTP endpoint at mail.maildog.io on port 2525 (STARTTLS, recommended), 587 (STARTTLS), or 465 (implicit TLS). Use the standard smtp.EmailBackend — no custom backend or stubbing required. Every message is caught and held, never delivered.
Full Email Inspection
View the rendered HTML, the plain-text fallback, raw source, and every header for each captured message. Confirm that your Django templates, context variables, inline CSS, and {% url %}-built links all render the way a real user would see them.
Password Reset & allauth Link Testing
PasswordResetView and django-allauth verification flows generate tokenized one-time links. Maildog catches those emails so you can click the actual link, walk through the full confirmation flow, and verify the redirect — all without a live mailbox.
SPF, DKIM & DMARC Validation
Maildog checks SPF, DKIM, and DMARC on every captured message so you can spot authentication problems in staging — long before they hurt your sender reputation and ship mail that lands in the inbox, not spam.
Safe by Default — No Accidental Sends
Because Maildog is a sandbox, nothing leaves the box. Even if a staging environment is misconfigured or a test loop fires a thousand send_mail() calls, your real users never receive a single message.
Team-Friendly Shared Inboxes
QA, developers, and product can all open the same captured email. No more "works on my machine" for email rendering — everyone inspects the exact same message.
Real Django email config for Maildog
Point Django at the Maildog sandbox by setting your email variables in settings.py. The example below uses port 2525 with STARTTLS, the recommended Maildog configuration, and works cleanly with Django 4.x and 5.x.
# settings.py
# Use Django's real SMTP backend so your production code path
# is exercised exactly as-is against the Maildog sandbox.
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
# Maildog sandbox SMTP connection
EMAIL_HOST = "mail.maildog.io"
EMAIL_PORT = 2525 # STARTTLS (recommended). 587 also works.
EMAIL_USE_TLS = True # STARTTLS on 2525/587
EMAIL_USE_SSL = False # set True (and EMAIL_PORT = 465) for implicit TLS
# Credentials are unique per sandbox inbox.
# Find them on the inbox's "Integration" tab in the Maildog panel.
EMAIL_HOST_USER = "your-sandbox-username"
EMAIL_HOST_PASSWORD = "your-sandbox-password"
DEFAULT_FROM_EMAIL = "no-reply@yourapp.example"import os
# Best practice: read these from env so staging, CI, and
# production never share credentials.
EMAIL_HOST = os.environ["EMAIL_HOST"] # mail.maildog.io
EMAIL_PORT = int(os.environ.get("EMAIL_PORT", 2525))
EMAIL_USE_TLS = os.environ.get("EMAIL_USE_TLS", "True") == "True"
EMAIL_HOST_USER = os.environ["EMAIL_HOST_USER"]
EMAIL_HOST_PASSWORD = os.environ["EMAIL_HOST_PASSWORD"]from django.core.mail import send_mail
send_mail(
subject="Welcome to Acme",
message="Thanks for signing up! Confirm your account to get started.",
from_email=None, # falls back to DEFAULT_FROM_EMAIL
recipient_list=["new.user@example.com"],
fail_silently=False,
)from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
text_body = render_to_string("emails/welcome.txt", {"name": "Sam"})
html_body = render_to_string("emails/welcome.html", {"name": "Sam"})
msg = EmailMultiAlternatives(
subject="Welcome to Acme",
body=text_body,
from_email="no-reply@yourapp.example",
to=["new.user@example.com"],
)
msg.attach_alternative(html_body, "text/html")
msg.send()Why developers choose Maildog
Faster debugging
Stop guessing what your email looks like from a console dump. See the rendered HTML, raw source, and headers instantly, so you can fix a broken template or a malformed link in one pass instead of redeploying repeatedly.
Safe email testing
Every message is trapped in the sandbox. Test password resets, signup verification, and notification floods as aggressively as you want — your real users and your sender reputation are never at risk.
Better deliverability
Built-in SPF, DKIM, and DMARC checks catch authentication issues in staging, so the email you eventually send to production reaches the inbox instead of the spam folder.
Improved workflow
A real SMTP backend means staging, CI, and production all run the same Django email code path. Shared inboxes let QA and developers inspect the exact same captured message — no environment-specific surprises.
Console vs locmem vs a real sandbox
Use locmem for fast unit-test assertions on mail.outbox, but use a real sandbox SMTP server like Maildog for everything that matters visually — rendered templates, clickable reset links, and end-to-end QA.
| Capability | Console backend | locmem | Maildog | Mailtrap |
|---|---|---|---|---|
| Exercises real SMTP code path | Prints to stdout | In-memory outbox | ||
| Tests EMAIL_HOST / TLS / auth settings | ||||
| Renders HTML email visually | ||||
| Click real reset / allauth links | ||||
| SPF / DKIM / DMARC validation | Partial | |||
| Safe — never reaches real users | ||||
| Good for unit-test assertions | Partial | Yes (outbox) | Via inbox/API | Via API |
| Best used in | Local quick-look | Unit tests | Staging, CI, QA | Staging, QA |
Frequently asked questions
How do I configure Django SMTP testing with Maildog?
Set EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" and point EMAIL_HOST to mail.maildog.io with EMAIL_PORT = 2525 and EMAIL_USE_TLS = True. Use the unique username and password from your sandbox inbox's Integration tab for EMAIL_HOST_USER and EMAIL_HOST_PASSWORD. Django then sends over real SMTP, and Maildog catches every message in your sandbox.
Which Maildog port and TLS setting should Django use?
Use port 2525 with EMAIL_USE_TLS = True (STARTTLS) as the recommended default; port 587 works the same way. Only use port 465 with EMAIL_USE_SSL = True for implicit TLS. Set exactly one of EMAIL_USE_TLS or EMAIL_USE_SSL to True — Django treats them as mutually exclusive.
What's the difference between Django's console backend and a sandbox SMTP server?
The console backend (console.EmailBackend) only prints email text to your terminal — it never opens an SMTP connection, so it can't validate your host, port, TLS, or auth settings and can't render HTML. A sandbox SMTP server like Maildog accepts the message over a real SMTP connection and lets you inspect the fully rendered email, exercising the same code path as production.
Should I use locmem or Maildog for testing Django emails?
Use both for different jobs. The locmem backend stores messages in django.core.mail.outbox for fast unit-test assertions like len(mail.outbox) == 1. Maildog is for visual inspection and end-to-end testing — checking rendered HTML, clicking real password-reset links, and validating SPF/DKIM/DMARC in staging and CI.
How do I test Django password reset emails?
Point Django at the Maildog sandbox, trigger the password reset (e.g. via PasswordResetView or your reset form), then open the captured email in your Maildog inbox. You can see the rendered message and click the actual tokenized reset link to walk through the full confirmation flow without sending anything to a real user.
How do I test django-allauth verification emails?
Configure Django's email settings to use the Maildog sandbox, then sign up a test user. django-allauth generates the verification email and sends it over SMTP to Maildog, where it's caught and held. Open the message, inspect the rendered content, and click the verification link to confirm the whole signup-confirmation flow end to end.
Will Maildog accidentally send test emails to real users?
No. Maildog is a sandbox — every message your Django app sends is trapped and held for inspection, never delivered to real recipients. Even if a staging environment is misconfigured or a test loop fires thousands of send_mail() calls, no real user ever receives anything.
Does mailoutbox replace a sandbox SMTP server?
No. mailoutbox (the pytest-django fixture) and django.core.mail.outbox only capture in-memory message objects for assertions in unit tests. They never open a real SMTP connection, so they can't validate your connection settings, render HTML, or let you click real links. A sandbox SMTP server like Maildog complements mailoutbox by covering the visual and end-to-end testing it can't.
Keep exploring
Test Django email the safe way
Point Django at a Maildog sandbox and catch your first message in minutes — no real recipient ever contacted.
Start Django SMTP Testing Free