Wagon is to SMTP what Flask is to WSGI/HTTP. The initial idea for Wagon came from Benjamin Coe’s SMTPRoutes library, but the code for this project has been written from scratch.

The philosophy is that we can do for SMTP the same thing we did with HTTP, provide a simple interface like WSGI (albeit I’m haven’t formally defining it) and build applications on top of that, to abstract away the underlying server for running this.

You can run Wagon with Secure-SMTPD and as a procmail script, and it’s fairly trivial to plug into whatever other mail server you want to use.


pip install wagon

Or install the development version:

$ pip install hg+http://hg.flowblok.id.au/wagon

Hello World

from wagon import Wagon

app = Wagon()

def catch_all(address, message, local, domain):
    print 'Hello %s from %r!' % (address, message.to)

if __name__ == '__main__':

Matching Routes

Route matching is done on the basis of which address the message was delivered to. This has nothing to do with the addresses in the To and CC headers in the message itself.

Patterns in the route decorator are simply regular expressions, and we pass the groups to the function via keyword arguments.

Matching is done by looking at each route (in the order they were defined in) and trying to match the delivery address against the pattern for the route. If the pattern matches, we assume this is the correct route for the message, and call the function with the address, message and match groups.

def match_route(address, message):
    for regex, route in app.routes:
        match = regex.match(address)
        if match is not None:
            route(address, message, **match.groupdict())

Sender Authentication

Email is vulnerable to spoofing attacks. Fortunately, there are two technologies that save the day!

SPF lets domain owners specify in their DNS records the hosts which are allowed to send mail from the domain.

DKIM adds a header to each message sent with an signature of the mail using a private key belonging to the domain. To verify, you fetch the public key using DNS, and check the signature.

These are implemented in Wagon by decorators in the wagon.auth module. The decorators are fundamentally very simple: they check the authenticity of the message, and if it is found to be invalid, silently drops the message on the floor.

# import the decorators
from wagon import Wagon
from wagon.auth import check_dkim, check_spf

app = Wagon()

# only accept messages with DKIM signatures
# for this route only
def route(address, message):
    print 'The message has passed both SPF and DKIM checks.'

# but all messages need to pass SPF
app.mail_func = check_spf(app.mail_func)

Running a Server

There is a builtin server for debugging provided by secure-smtpd which is used when you call app.run().

However, you can use whatever server you like, as long as it supports the simple protocol of calling the application object with two parameters, the delivery address and the message object.