This is the next post about Twisted, this time about Twisted.mail. After my Twisted project is done I'm going to contribute all my findings in some examples, docstrings and maybe even a howto. This post mainly serves as my notebook for things to remember about SMTP in Twisted.

This is a twistd app taken from the Twisted examples section, and modified to support authentication. Below you'll find all my notes.

# Copyright (c) 2001-2004 Twisted Matrix Laboratories.# See LICENSE for details.# You can run this module directly with:# twistd -ny esmtpserver.tac"""
A toy email server with authentication.
"""fromzope.interfaceimportimplementsfromtwisted.internetimportdeferfromtwisted.mailimportsmtpfromtwisted.mail.mailimportMailService# these challengers are located in imap4fromtwisted.mail.imap4importLOGINCredentials,PLAINCredentialsfromtwisted.cred.checkersimportInMemoryUsernamePasswordDatabaseDontUse#except in examples and testingfromtwisted.cred.portalimportIRealmfromtwisted.cred.portalimportPortalclassConsoleMessageDelivery:implements(smtp.IMessageDelivery)defreceivedHeader(self,helo,origin,recipients):return"Received: ConsoleMessageDelivery"defvalidateFrom(self,helo,origin):# All addresses are acceptedreturnorigindefvalidateTo(self,user):# Only messages directed to the "console" user are accepted.ifuser.dest.local=="console":returnlambda:ConsoleMessage()raisesmtp.SMTPBadRcpt(user)classConsoleMessage:implements(smtp.IMessage)def__init__(self):self.lines=[]deflineReceived(self,line):self.lines.append(line)defeomReceived(self):print"New message received:"print"\n".join(self.lines)self.lines=Nonereturndefer.succeed(None)defconnectionLost(self):# There was an error, throw away the stored linesself.lines=NoneclassConsoleSMTPFactory(smtp.SMTPFactory):def__init__(self,*a,**kw):smtp.SMTPFactory.__init__(self,*a,**kw)# make this factory make ESMTP serversself.protocol=smtp.ESMTPdefbuildProtocol(self,addr):p=smtp.SMTPFactory.buildProtocol(self,addr)# add the challengers from imap4, more secure and complicated challengers are availablep.challengers={"LOGIN":LOGINCredentials,"PLAIN":PLAINCredentials}returnpclassSimpleRealm:implements(IRealm)defrequestAvatar(self,avatarId,mind,*interfaces):# if we are authenticating a IMessageDeliveryifsmtp.IMessageDeliveryininterfaces:# a tuple of the implemented interface, an instance implementing it and a logout callablereturnsmtp.IMessageDelivery,ConsoleMessageDelivery(),lambda:NoneraiseNotImplementedError()defmain():fromtwisted.applicationimportinternetfromtwisted.applicationimportserviceportal=Portal(SimpleRealm())# initiate a simple checkerchecker=InMemoryUsernamePasswordDatabaseDontUse()checker.addUser("guest","password")portal.registerChecker(checker)a=service.Application("Console SMTP Server")internet.TCPServer(2500,ConsoleSMTPFactory(portal)).setServiceParent(a)returnaapplication=main()

If you want anything beyond a toy, you'll need to use ESMTP instead of SMTP

You need to have a portal with a realm and a checker set up to use the AUTH command

challengers are what provide the protocol for logging in, note that they are located in twisted.imap4

A portal takes one checker instance per authentication method, so you can have one for handling strange tokens, and one for regular username/password authentication, but not two for the same type.

Take a look at the source for InMemoryUsernamePasswordDatabaseDontUse, it is really simple to write your own checker.