Why does Django not email me the 500 internal server error?
By: Antonis Christofides
Tags: Email, Django

You’ve set your EMAIL_* settings correctly, and when you try to send emails with django.core.mail.send_mail() it works. However, Django still does not send you internal server errors. Why?

The sender’s email address matters

SERVER_EMAIL is the email address from which emails with error messages appear to come from. It is set in the “From:” field of the email. The default is “root@localhost”, and while “root” is OK, “localhost” is not, and some mail servers may refuse the email. The domain name where your Django application runs is usually OK, but if this doesn’t work you can use any other valid domain. The domain of your email address should work properly.

→ Set SERVER_EMAIL

→ When testing with django.core.mail.send_mail(), use the same sender email address as the one you’ve specified in SERVER_EMAIL.

The recipient’s email address matters

Because of spam, mail servers are often very picky about which emails they will accept. It’s possible that even if your smarthost accepts the email, the next mail server may refuse it. For example, I made some experiments using EMAIL_HOST = 'mail.runbox.com' and this command:

send_mail('Hello', 'hello, world', 'noreply@example.com', ['anthony@itia.ntua.gr'])

In that case, Runbox accepted the email and subsequently attempted to deliver it to the mail server of ntua.gr, which rejected it because it didn’t like the sender (noreply@example.com; I literally used “example.com”, and ntua.gr didn’t like that domain). When something like this happens, send_mail() will appear to work, because send_mail() manages to deliver the email to the smarthost, and the error occurs after that; not only will we never receive the email, but it is also likely that we will not receive the failure notification (the returned email), so it’s often hard to know what went wrong and we need to guess.

One thing you can do to lessen the probability of error is to make sure that the recipient has an email address served by the provider who provides the smarthost. In my case, the smarthost is mail.runbox.com , and the recipient is antonis@djangodeployment.com, and the email for domain djangodeployment.com is served by Runbox. It is unlikely that mail.runbox.com would accept an email addressed to antonis@djangodeployment.com if another Runbox server were to subsequently refuse it. If something like this happened, I believe it would be a configuration error on behalf of Runbox. But it’s very normal that mail.runbox.com will accept an email which will subsequently be refused by ntua.gr or Gmail or another provider downstream.

→ At least one of the ADMINS should have an email address served by the provider who runs the smarthost.

→ When testing with django.core.mail.sendmail(), use the same recipient email address as the one of the ADMINS.

Commas can matter

This will work:

ADMINS = [('John', 'john@example.com')]

This is a common error; it won’t work:

ADMINS = (('John', 'john@example.com'))

You can make it work by adding a comma, like this:

ADMINS = (('John', 'john@example.com'),)

Despite the fact that I have a very sharp eye, I once forgot the comma and the site worked for months without notifying me of errors. Therefore, use the foolproof way:

→ Specify ADMINS as a list, not as a tuple.

Test whether it sends errors

A favorite way of mine is to temporarily rename a template file and make a related request, which will raise a TemplateDoesNotExist exception. Your browser should show the “server error” page. Don’t forget to rename the template file back to what it was. By the time you finish doing that, you should have received the email with the full trace.

→ Temporarily rename a template file and make a related request in order to test whether errors are emailed OK.

Django can’t notify you that it can’t notify you

While Django attempts to send an error email, if something goes wrong, it fails silently. This behaviour is appropriate (the system is in error, it attempts to email its administrator with the exception, but sending the email also results in an error; can’t do much more). Suppose, however, that when you try to verify that error emails work, you find out they don’t work. What has gone wrong? Nothing is written in any log. Intercepting the communication with ngrep won’t work either, because it’s usually encrypted. In my book, I recommend to use a locally installed mail server. If you do so, you will at least be able to look at the local mail server’s logs.

I don’t recommend using exim or postfix, as they are quite complicated. Instead, I recommend dma. To make it work, you’ll also need django-sendmail-backend.

→ Use a local mail server