The standard protocol for sending mail on the Internet is the Simple Mail Transfer Protocol (SMTP). SMTP allows one computer to transfer email messages to another computer using a standard set of commands. Mail clients use SMTP to send outgoing messages, and mail servers use SMTP to forward messages to their final destination. The current specification for SMTP is defined in RFC 2821 (http://www.faqs.org/rfcs/rfc2821.html).
Twisted supports SMTP, using the twisted.protocols.smtp module. In addition to SMTP Protocol classes, this module has handy functions for sending mail. Combined with the email module in the Python standard library, you can create and send messages (even complex MIME messages with attachments) with only a few lines of code.
7.2.1. How Do I Do That?
Create your message using Python's email package. Then use the twisted.protocols.smtp.sendmail function to send the message using SMTP, as demonstrated in Example 7-3.
Example 7-3. smtpsend.py
from twisted.protocols import smtp from twisted.internet import reactor, defer from email.MIMEBase import MIMEBase from email.MIMEMultipart import MIMEMultipart from email import Encoders import sys, mimetypes, os def buildMessage(fromaddr, toaddr, subject, body, filenames): message = MIMEMultipart( ) message['From'] = fromaddr message['To'] = toaddr message['Subject'] = subject textPart = MIMEBase('text', 'plain') textPart.set_payload(body) message.attach(textPart) for filename in filenames: # guess the mimetype mimetype = mimetypes.guess_type(filename) if not mimetype: mimetype = 'application/octet-stream' maintype, subtype = mimetype.split('/') attachment = MIMEBase(maintype, subtype) attachment.set_payload(file(filename).read( )) # base64 encode for safety Encoders.encode_base64(attachment) # include filename info attachment.add_header('Content-Disposition', 'attachment', filename=os.path.split(filename)) message.attach(attachment) return message def sendComplete(result): print "Message sent." reactor.stop( ) def handleError(error): print >> sys.stderr, "Error", error.getErrorMessage( ) reactor.stop( ) if __name__ == "_ _main_ _": if len(sys.argv) < 5: print "Usage: %s smtphost fromaddr toaddr file1 [file2, ...]" % ( sys.argv) sys.exit(1) smtphost = sys.argv fromaddr = sys.argv toaddr = sys.argv filenames = sys.argv[4:] subject = raw_input("Subject: ") body = raw_input("Message (one line): ") message = buildMessage(fromaddr, toaddr, subject, body, filenames) messageData = message.as_string(unixfrom=False) sending = smtp.sendmail(smtphost, fromaddr, [toaddr], messageData) sending.addCallback(sendComplete).addErrback(handleError) reactor.run( )
Run smtpsend.py from the command line with four or more arguments: the name of your SMTP server, the address the message is from, the address it should be delivered to, and one or more files to attach. It will ask you for a subject line and single-line message body, and then create and send the email:
$ python smtpsend.py smtp.myisp.com email@example.com > firstname.lastname@example.org logo.png homepage.html Subject: Project Files Message (one line): Here are those files I was talking about. Message sent.
7.2.2. How Does That Work?
The twisted.protocols.smtp.sendmail function is a simple, handy way to send email using Twisted. It encapsulates all the work of connecting to the STMP server, telling it the address the message is coming from and the address it should be delivered to, and transferring the message data. Call the sendmail function with four arguments: the SMTP server hostname, the address the message is coming from, a list of addresses it should be delivered to, and the message data itself (either as a string or a file-like object). It returns a Deferred that will call back when the transfer is complete.
There's one potentially tricky thing about smtp.sendmail that you should be aware of: if you're sending a message to multiple recipients, a successful callback means that the sever accepted the message for delivery to at least one of them. If you're sending the message to more than one address, therefore, be sure to check the return value passed to your callback function. This value will be a tuple in the form (numberDelivered, addressInfo), where numberDelivered is a count of the number of recipients for which the server accepted the message, and addressInfo is a list of tuples in the format (address, responseCode, responseMessage). If you're sending a message to a single address, as in Example 7-3, you don't have to check the return value; the callback itself indicates that the message was accepted for the single recipient it was addressed to.
Because the sendmail function handles all the SMTP work, most of the code in smtpsend.py is involved with constructing the email message that will be sent. The Python email package contains modules for constructing messages of various types, and the buildMessage function uses two of them: MIMEMultipart, which creates a message of type multipart/mixed (suitable for storing a message body and attachments), and MIMEBase, which can hold any type of message data. The buildMessage function creates an email.MIMEMultipart.MIMEMultipart object called message. Then it iterates through the list of filenames and creates a MIMEBase object for each one containing the file's data. It uses the Python mimetypes module to guess the file type (based on the filename's extension). In addition, it Base64-encodes the attachment contents, which allows binary data to be represented as ASCII text (a requirement for passing through most mail servers).
Once the attachments have been added, the message is ready to be sent. smtpsend.py passes the message data to smtp.sendmail, along with the hostname of the SMTP server and the sender and recipient addresses. It sets up the callback and errback functions, and it hands off control to the reactor.
Building Simple Clients and Servers
Web Services and RPC
NNTP Clients and Servers
Services, Processes, and Logging