Using SMTP as a User Interface

SMTP is more than just a protocol for routing messages between people; it can also be used for communications in which the recipient is a computer program. Many server applications use SMTP to allow people to publish information via email. By sending mail to a special address, users can post to their weblogs, add a page to a Wiki, or import image files into their photo sharing services. Offering an SMTP interface to your application makes getting data in and out as easy as sending and receiving email. This lets people use your application as part of their existing email workflow, which makes it more likely that they're actually going to use that program you worked so hard to write. SMTP is also supported by many mobile phones, so it can be an easy way to enable mobile access to your applications.

8.2.1. How Do I Do That?

Example 8-2 shows how you might offer an SMTP interface to an application. smtpgoogle.py is an SMTP server that takes email as input and uses it to run a Google "I'm Feeling Lucky" search. Then it sends a reply containing the search result. The code is similar to the basic SMTP server in Example 8-1, but with application-specific classes implementing smtp.IMessage and smtp.ImessageDelivery.

Example 8-2. smtpgoogle.py

from twisted.mail import smtp

from twisted.web import google, client

from zope.interface import implements

from twisted.internet import protocol, reactor, defer

import os, email, email.Utils

from email import Header, MIMEBase, MIMEMultipart, MIMEText

class GoogleQueryPageFetcher(object):

 def fetchFirstResult(self, query):


 Given a query, finds the first Google result, and

 downloads that page. Returns a Deferred, which will

 be called back with a tuple containing

 (firstMatchUrl, firstMatchPageData)


 # the twisted.web.google.checkGoogle function does an

 # "I'm feeling lucky" search on Google

 return google.checkGoogle(query).addCallback(


 def _gotUrlFromGoogle(self, url):

 # grab the page

 return client.getPage(url).addCallback(

 self._downloadedPage, url)

 def _downloadedPage(self, pageContents, url):

 # return a tuple containing both the url and page data

 return (url, pageContents)

class ReplyFromGoogle(object):


 def _ _init_ _(self, address, upstreamServer):

 self.address = address

 self.lines = []

 self.upstreamServer = upstreamServer

 def lineReceived(self, line):


 def eomReceived(self):

 message = email.message_from_string("

 fromAddr = email.Utils.parseaddr(message['From'])[1]

 query = message['Subject']

 searcher = GoogleQueryPageFetcher( )

 return searcher.fetchFirstResult(query).addCallback(

 self._sendPageToEmailAddress, query, fromAddr)

 def _sendPageToEmailAddress(self, pageData, query, destAddress):

 pageUrl, pageContents = pageData

 msg = MIMEMultipart.MIMEMultipart( )

 msg['From'] = self.address

 msg['To'] = destAddress

 msg['Subject'] = "First Google result for '%s'" % query

 body = MIMEText.MIMEText(

 "The first Google result for '%s' is:

%s" % (

 query, pageUrl))

 # create a text/html attachment with the page data

 attachment = MIMEBase.MIMEBase("text", "html")

 attachment['Content-Location'] = pageUrl




 return smtp.sendmail(

 self.upstreamServer, self.address, destAddress, msg)

 def connectionLost(self):


class GoogleMessageDelivery(object):


 def _ _init_ _(self, googleSearchAddress, upstreamSMTPServer):

 self.googleSearchAddress = googleSearchAddress

 self.upstreamServer = upstreamSMTPServer

 def receivedHeader(self, helo, origin, recipients):

 myHostname, clientIP = helo

 headerValue = "by %s from %s with ESMTP ; %s" % (

 myHostname, clientIP, smtp.rfc822date( ))

 # email.Header.Header used for automatic wrapping of long lines

 return "Received: %s" % Header.Header(headerValue)

 def validateTo(self, user):

 if not str(user.dest).lower( ) == self.googleSearchAddress:

 raise smtp.SMTPBadRcpt(user.dest)


 return lambda: ReplyFromGoogle(self.googleSearchAddress,


 def validateFrom(self, helo, originAddress):

 # accept mail from anywhere. To reject an address, raise

 # smtp.SMTPBadSender here.

 return originAddress

class SMTPServerFactory(protocol.ServerFactory):

 def _ _init_ _(self, googleSearchAddress, upstreamSMTPServer):

 self.googleSearchAddress = googleSearchAddress

 self.upstreamSMTPServer = upstreamSMTPServer

 def buildProtocol(self, addr):

 delivery = GoogleMessageDelivery(self.googleSearchAddress,


 smtpProtocol = smtp.SMTP(delivery)

 smtpProtocol.factory = self

 return smtpProtocol

if __name__ == "_ _main_ _":

 import sys

 googleSearchAddress = sys.argv[1]

 upstreamSMTPServer = sys.argv[2]


 25, SMTPServerFactory(googleSearchAddress, upstreamSMTPServer))

 reactor.run( )

Run smtpgoogle.py from the command line with two arguments. The first is the email address you want to use for Google searches: you'll send mail to this address to search Google, and you'll get a reply from this address with the result. The second argument is the upstream SMTP server you want to use for sending the reply emails; typically this is your ISP's mail server:

 $ python smtpgoogle.py google@myhostname.mydomain smtp.myisp.com

If you're running this command behind a firewall, you might have to fake the address you use for Google searches in order to send the reply emails. The address google@localhost, for example, might be rejected by your upstream mail server as an invalid sender address. As long as you're using a mail client configured to send all mail through localhost, you should be able to use any address; smtpgoogle.py will use that address for Google searches, and refuse to accept mail for any other address.

Once the server is running, try sending an email to the address you specified. The subject of the email should be the query you want to send to Google. Figure 8-2 shows a query email being sent.

Figure 8-2. Sending a query to the smtpgoogle.py search service

smtpgoogle.py should process your query and send a reply to whatever From: address you used when sending the email. Check your mail, and you should get an email similar to that in Figure 8-3 with the first Google result.

Figure 8-3. Reply containing result of a Google search


8.2.2. How Does That Work?

Example 8-2 defines the GoogleQueryPageFetcher class, which does the work of finding the first Google result for a query and then downloading that page. The function fetchFirstResult returns a Deferred object that will be called back with a tuple containing the URL and contents of the first matching page.

The ReplyFromGoogle class takes this feature and makes it available to SMTP by implementing smtp.IMessage. The key is the eomReceived function, which is called once the entire message has been received. The smtp.IMessage interface says that eomReceived should return a Deferred. Because of this, ReplyFromGoogle is free to run a series of asynchronous operations while processing the email, as long as it eventually returns a successful value or raises an exception. Rather than just writing the message data to disk, ReplyFromGoogle.eomReceived runs the Google search and sets up a callback to take the search result and send it out by email. It constructs a message using classes in Python's email module, and sends it back to the address that the query came from.

This technique of doing all the work in the context of eomReceived is a good approach to use when you expect that it won't take more than a second or two to process the incoming message. At other times, you might need to do something that's going to take a while. In this case, you don't want to leave the SMTP client hanging there waiting for a response, especially because it will eventually get tired of waiting and time out. So you might choose to add the incoming message to a queue for processing, and to immediately return a successful result for eomReceived. If something goes wrong later, you can always send an email back to the sender informing them of what happened.

Getting Started

Building Simple Clients and Servers

Web Clients

Web Servers

Web Services and RPC


Mail Clients

Mail Servers

NNTP Clients and Servers


Services, Processes, and Logging

Twisted Network Programming Essentials
Twisted Network Programming Essentials
ISBN: 0596100329
EAN: 2147483647
Year: 2004
Pages: 107
Authors: Abe Fettig

Similar book on Amazon

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net