Twisted Bits

a blog by jeff lafay, software developer

SMTP Debugging with Python

This is something that can be added to your developer bag of tricks, tool box, or whatever else you prefer to keep handy dev tools in.

We write lots of code and lots of apps. It's really nice to get notifications when things go south or when we need to send users notifications for various reasons (registration, social activity, etc.).

However, debugging emails or preventing emails from being sent out to users while we're developing can sometimes take a little more work and configuration than we'd like. 

I use a couple of methods when developing apps to prevent emails from leaving my machine and so I can inspect email output.  If you have python installed on your machine, this can be used in conjunction with any app that your developing.

Method One, Quick and Easy

The quickest way to start up an SMTP server with python is to do it by command line. It can be copy/pasted into a bash script, batch file, etc. 

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

This will block the command line and print out emails as they're received by the DebuggingServer  object on port 25. I tend to use this method when I'm not necessarily too concerned about the actual emails being sent but when I just don't want my app to suffer SMTP errors or exceptions while I develop/debug.

Sending an email to this server will result in console output

---------- MESSAGE FOLLOWS ----------
From: fu@bar
To: luser@bar
X-Peer: 127.0.0.1
This is my test SMTP message!
------------ END MESSAGE ------------

Method Two, Inherit DebuggingServer to Log Emails

The DebuggingServer class can be inherited from to log emails. Which is especially advantageous when you want to know what is being emailed and/or if you want to make sure all the emails are at least being transmitted (at times it's difficult to gauge which SMTP servers will accept emails from your app due to filtering).

My custom class includes logging to file and prints out one line every time an email is received by the debug SMTP server object which includes the sender's hostname (printing out the address it's going to is also good to output) and the current local time. Because this is your own little SMTP debugger, you can send emails to any addresses because the pseudo server will accept anything passed to it.

from smtpd import DebuggingServer
import datetime, sys, os, re 


class LoggingSmtpServer(DebuggingServer): def __init__(self, localaddr, remoteaddr, log_dir): DebuggingServer.__init__(self, localaddr, remoteaddr) self.log_dir = log_dir def process_message(self, peer, mailfrom, rcpttos, data): time = datetime.datetime.now().time() # replace email CR's with actual CR's and LF's with actual LF's body = data.replace('=0D', '\r').replace('=0A', '\n') body = re.sub(r'=\s+', '', body) logpath = '{0}{1}_emails.log'.format(self.log_dir, datetime.datetime.today().strftime('%Y-%m-%d')) with open(logpath, 'a') as f: f.write('[Message from {0} @ {1}]\n\n'.format(str(peer), str(time))) f.write('{0}\n.\n[ -- fin -- ]\n\n\n'.format(body)) print '{0} -- Received message from {1}'.format(str(time), str(peer)) def main(): import asyncore logdir = '../logs/smtp/'
server = LoggingSmtpServer(('localhost', 25), None, logdir) print 'SMTP Debug Logger Started. Terminate with [ctrl-c]' try: # block command line to keep the debugger running until user terminates the app asyncore.loop(timeout=1) except KeyboardInterrupt: print 'Server closing ...' try: server.close() except: pass if __name__ == "__main__": main()

I used asyncore in main() so it will block and keep the custom debugging server pumping along. It will also halt the script if a keyboard interrupt arises. I changed the logging output to my own taste and of course can be customized if need be.

My little concoction outputs the same test emails like this

[Message from ('127.0.0.1', 54208) @ 22:47:06.966000]

From: fu@bar
To: luser@bar

This is my test SMTP message!
.
[ -- fin -- ] 

Usually the first method is all that's needed since it's quick and you don't have to think about much to "stub" out emails in your apps. For reference, I have a github repo with these example scripts.

Other Thoughts 

If you want to get crazy with the cheese wiz, you could do other things when building your own little SMTP debugger. Like blocking the process thread for random time intervals to simulate spontaneous network latency.

This blog post has been tagged Debugging, Python.