text

Testing Emails with Django

Django ticket #8638 titled, “Provide setting to disable e-mail sending”, caught my eye as an interesting item to look into. As I looked into it, however, I came up with a pretty simple way to set up testing that involves some settings.py changes and about 4 lines of Python code.

The big piece of magic comes from Python’s built-in smtpd library and the smtpd.DebuggingServer class, which discards all e-mails sent to it and prints them to stdout.

Let’s use the class to create a local mail server that will discard and print all emails sent to it, then update our settings.py file to point to this debugging mail server.

Create a file containing the following:

    import smtpd
    import asyncore
    server = smtpd.DebuggingServer(('127.0.0.1', 1025), None)
    asyncore.loop()

I named mine email_debugger.py and ran it by typing python email_debugger.py at the console. It won’t output anything until we try to send an email.

In your project’s settings.py file (or whatever local settings file you use) change Django’s email settings to use this server. I changed the following, making sure override settings that may possibly be set:

    EMAIL_HOST = 'localhost'
    EMAIL_PORT = 1025
    EMAIL_HOST_USER = ''
    EMAIL_HOST_PASSWORD = ''
    EMAIL_USE_TLS = False
    DEFAULT_FROM_EMAIL = 'testing@example.com'

Note that the e-mail host and port match those that are in the debugging script.

Now run you project and use whatever email sending routine you’d like to test. While writing this I tested the contact form that runs on this blog locally on my laptop. I entered in some dummy text and the output at the console of the email_debugger.py script looked like this:

---------- MESSAGE FOLLOWS ----------
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Subject: Website message from rob.cogit8.org
From: testing@example.com
To: django_manager@example.com
Date: Fri, 21 Nov 2008 04:46:09 -0000
Message-ID: <20081121044609.9329.85531@MBP-of-ROB.local>
X-Peer: 127.0.0.1

Message from:
Rob <website_user@example.com>

Message body:
I buy my eggs at the cheese shop!
------------ END MESSAGE ------------

As you can see this is pretty useful and shows all the email headers and email body that would normally be sent to your mail server.

Aside

As an aside, the above file that contains the email debugging server code could be replaced with a quick shell command. The Python smtpd library has the ability to run standalone and takes various options. The above script can be replaced with the following command:

    python -m smtpd -n -c DebuggingServer localhost:1025

Where to go from here

If you take a look at the smtpd.py file, you’ll notice the DebuggingServer class simply defines a single method process_message. If you wanted to do something different to these emails, you could subclass the SMTPServer class and define your own process_message that could do anything from output to stdout in a different format, or write the emails to a file that can be read by an email client, or save the emails to a database, etc.

For example, if the project will be sending many emails, we may want the output to be less verbose, say, one line per email. So we could create the following:

    import smtpd
    import asyncore
    
    class OneLineDebuggingServer(smtpd.SMTPServer):
        def process_message(self, peer, mailfrom, rcpttos, data):
            print "Sending mail to %s" % (','.join(rcpttos))
    
    server = OneLineDebuggingServer(('127.0.0.1', 1025), None)
    asyncore.loop()

This would also be useful as a Django management command where the management command pulled the EMAIL_HOST and EMAIL_PORT from the settings.py file.

Conclusion

I don’t know where ticket 8638 will end up, but hopefully the above is enough to help those wanting to test emails locally and avoid errors from the runserver command in the meantime.