Back to Blog

Why Your Django Emails Land in Spam

admin
August 7, 2026 3 min read
31 views
The message sends, the log says accepted, and the user never sees it. Why receiving servers distrust you, the three records that fix most of it, and the alignment rule people miss.

Why Your Django Emails Land in Spam

send_mail() returns 1. The log says the message was accepted. The user says they never got it, and
eventually finds it in junk. Nothing failed — a receiving server simply decided your message was not
worth delivering, and told nobody.

Why you are distrusted by default

Receiving systems score every message. The inputs are the reputation of the sending address, the
reputation of the domain, whether the message is authenticated, whether recipients engage with your
mail, and how many previous messages bounced or were reported.

A new server has no history on any of those. Worse, it sits in an address range belonging to a
hosting provider — ranges that receivers associate with far more spam than genuine correspondence.
You start from a position of suspicion, and you have to earn your way out.

The three records

SPF lists which servers may send for your domain. One TXT record, and you may only have one —
adding a second for a new provider breaks authentication rather than extending it. Evaluation is
limited to ten DNS lookups, and chaining several providers can silently exceed that.

DKIM signs each message cryptographically. The public key is published in DNS under a selector,
which lets you rotate keys without an interruption.

DMARC ties the two to the address your recipients actually see, and tells receivers what to do
when it does not match.

The part most guides omit

DMARC does not merely require SPF or DKIM to pass. It requires the domain that passed to align
with the domain in your visible From address.

A mail provider sending on your behalf may pass SPF for its own domain. SPF passes, DMARC fails, and
the reason is invisible unless you look at which domain passed rather than whether something did.
This is why providers ask you to publish DKIM records in your own DNS: it is what makes the signature
align with your domain.

Do not send from the application server

For almost every Django application, relay through a provider whose business is maintaining sender
reputation. Running your own mail server is a specialisation, and many hosts block the relevant
outbound port by default anyway — which produces a timeout that looks like a network problem.

Configuration that will not hang your site

EMAIL_TIMEOUT = 10
DEFAULT_FROM_EMAIL = "Example App <noreply@example.com>"

EMAIL_TIMEOUT is almost always omitted and matters most. Without it, an unresponsive mail server
blocks a worker until the operating system gives up, and your user watches a spinner.

Send from a task queue rather than inside the request, with retries and backoff. And pass
fail_silently=False — the default swallows errors, which is exactly how mail failures go unnoticed
for weeks.

Handle bounces

When an address hard-bounces, stop sending to it. Continuing to mail addresses that do not exist is
one of the strongest signals of a sender who does not maintain their list, and receivers respond by
treating everything you send with suspicion.

The same applies to complaints: someone marking you as spam should be unsubscribed immediately and
without argument.

Make unsubscribing easy

Include an unsubscribe header so mail clients can offer one-click removal, and honour it promptly. It
feels counter-productive and improves deliverability, because the alternative is a frustrated
recipient reporting you — which costs far more than one subscriber.

Test properly

Send a real message and read the headers at the receiving end. You want three passes with your own
domain in all three places. Anything else means alignment is not what you think it is.


The full picture, including separate streams for transactional and bulk mail, is in Email
Deliverability for Django
.

Comments (0)

Please login to leave a comment.

No comments yet. Be the first to comment!