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
from wagon import Wagon app = Wagon() @app.route('(?P<local>.*)@(?P<domain>.*)') def catch_all(address, message, local, domain): print 'Hello %s from %r!' % (address, message.to) if __name__ == '__main__': app.run()
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()) break
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
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
# 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 @app.route('email@example.com') @check_dkim 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
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.