Section 14.5. SMTP: Sending Email


14.5. SMTP: Sending Email

There is a proverb in hackerdom that states that every useful computer program eventually grows complex enough to send email. Whether such wisdom rings true or not in practice, the ability to automatically initiate email from within a program is a powerful tool.

For instance, test systems can automatically email failure reports, user interface programs can ship purchase orders to suppliers by email, and so on. Moreover, a portable Python mail script could be used to send messages from any computer in the world with Python and an Internet connection. Freedom from dependence on mail programs like Outlook is an attractive feature if you happen to make your living traveling around teaching Python on all sorts of computers.

Luckily, sending email from within a Python script is just as easy as reading it. In fact, there are at least four ways to do so:


Calling os.popen to launch a command-line mail program

On some systems, you can send email from a script with a call of the form:

 os.popen('mail -s "xxx" a@b.c', 'w').write(text) 

As we saw earlier in the book, the popen tool runs the command-line string passed to its first argument, and returns a file-like object connected to it. If we use an open mode of w, we are connected to the command's standard input streamhere, we write the text of the new mail message to the standard Unix mail command-line program. The net effect is as if we had run mail interactively, but it happens inside a running Python script.


Running the sendmail program

The open source sendmail program offers another way to initiate mail from a program. Assuming it is installed and configured on your system, you can launch it using Python tools like the os.popen call of the previous paragraph.


Using the standard smtplib Python module

Python's standard library comes with support for the client-side interface to SMTPthe Simple Mail Transfer Protocola higher-level Internet standard for sending mail over sockets. Like the poplib module we met in the previous section, smtplib hides all the socket and protocol details and can be used to send mail on any machine with Python and a socket-based Internet link.


Fetching and using third-party packages and tools

Other tools in the open source library provide higher-level mail handling packages for Python (accessible from http://www.python.org); most build upon one of the prior three techniques.

Of these four options, smtplib is by far the most portable and powerful. Using os.popen to spawn a mail program usually works on Unix-like platforms only, not on Windows (it assumes a command-line mail program), and requires spawning one or more processes along the way. And although the sendmail program is powerful, it is also somewhat Unix-biased, complex, and may not be installed even on all Unix-like machines.

By contrast, the smtplib module works on any machine that has Python and an Internet link, including Unix, Linux, Mac, and Windows. It sends mail over sockets in-process, instead of starting other programs to do the work. Moreover, SMTP affords us much control over the formatting and routing of email.

14.5.1. SMTP Mail Sender Script

Since SMTP is arguably the best option for sending mail from a Python script, let's explore a simple mailing program that illustrates its interfaces. The Python script shown in Example 14-19 is intended to be used from an interactive command line; it reads a new mail message from the user and sends the new mail by SMTP using Python's smtplib module.

Example 14-19. PP3E\Internet\Email\smtpmail.py

 #!/usr/local/bin/python ########################################################################### # use the Python SMTP mail interface module to send email messages; this # is just a simple one-shot send script--see pymail, PyMailGUI, and # PyMailCGI for clients with more user interaction features; also see # popmail.py for a script that retrieves mail, and the mailtools pkg # for attachments and formatting with the newer std lib email package; ########################################################################### import smtplib, sys, time, mailconfig mailserver = mailconfig.smtpservername          # ex: starship.python.net From = raw_input('From? ').strip( )             # ex: lutz@rmi.net To   = raw_input('To?   ').strip( )             # ex: python-list@python.org To   = To.split(';')                             # allow a list of recipients Subj = raw_input('Subj? ').strip( ) # standard headers, followed by blank line, followed by text date = time.ctime(time.time( )) text = ('From: %s\nTo: %s\nDate: %s\nSubject: %s\n\n'                          % (From, ';'.join(To), date, Subj)) print 'Type message text, end with line=(ctrl + D or Z)' while 1:     line = sys.stdin.readline( )     if not line:         break                        # exit on ctrl-d/z   # if line[:4] == 'From':   #     line = '>' + line            # servers escape for us     text = text + line print 'Connecting...' server = smtplib.SMTP(mailserver)              # connect, no log-in step failed = server.sendmail(From, To, text) server.quit( ) if failed:                                     # smtplib may raise exceptions     print 'Failed recipients:', failed         # too, but let them pass here else:     print 'No errors.' print 'Bye.' 

Most of this script is user interfaceit inputs the sender's address ("From"), one or more recipient addresses ("To", separated by ";" if more than one), and a subject line. The sending date is picked up from Python's standard time module, standard header lines are formatted, and the while loop reads message lines until the user types the end-of-file character (Ctrl-Z on Windows, Ctrl-D on Linux).

To be robust, be sure to add a blank line between the header lines and the body in the message's text; it's required by the SMTP protocol and some SMTP servers enforce this. Our script conforms by inserting an empty line with \n\n at the end of the string format expression. Later in this chapter, we'll format our messages with the Python email package, which handles such details for us automatically.

The rest of the script is where all the SMTP magic occurs: to send a mail by SMTP, simply run these two sorts of calls:


server = smtplib.SMTP(mailserver)

Make an instance of the SMTP object, passing in the name of the SMTP server that will dispatch the message first. If this doesn't throw an exception, you're connected to the SMTP server via a socket when the call returns.


failed = server.sendmail(From, To, text)

Call the SMTP object's sendmail method, passing in the sender address, one or more recipient addresses, and the text of the message itself with as many standard mail header lines as you care to provide.

When you're done, call the object's quit method to disconnect from the server. Notice that, on failure, the sendmail method may either raise an exception or return a list of the recipient addresses that failed; the script handles the latter case but lets exceptions kill the script with a Python error message.

For advanced usage, the call server.login(user, password) provides an interface to SMTP servers that require authentication; watch for this call to appear in the mailtools package example later in this chapter. An additional call, server.starttls, puts the SMTP connection in Transport Layer Security (TLS) mode; all commands will be encrypted using the socket module's SSL support, and they assume the server supports this mode. See the Python library manual for other calls not covered here.

14.5.2. Sending Messages

Let's ship a few messages across the world. The smtpmail script is a one-shot tool: each run allows you to send a single new mail message. Like most of the client-side tools in this chapter, it can be run from any computer with Python and an Internet link. Here it is running on Windows:

 C:\...\PP3E\Internet\Email>smtpmail.py From? Eric.the.Half.a.Bee@yahoo.com To?   pp3e@earthlink.net Subj? A B C D E F G Type message text, end with line=(ctrl + D or Z) Fiddle de dum, Fiddle de dee, Eric the half a bee. ^Z Connecting... No errors. Bye. 

This mail is sent to the book's email account address (pp3e@earthlink.net), so it ultimately shows up in the inbox at my ISP, but only after being routed through an arbitrary number of machines on the Net, and across arbitrarily distant network links. It's complex at the bottom, but usually, the Internet "just works."

Notice the "From" address, thoughit's completely fictitious (as far as I know, at least). It turns out that we can usually provide any "From" address we like because SMTP doesn't check its validity (only its general format is checked). Furthermore, unlike POP, there is usually no notion of a username or password in SMTP, so the sender is more difficult to determine. We need only pass email to any machine with a server listening on the SMTP port, and we don't need an account on that machine. Here,

Eric.the.Half.a.Bee@yahoo.com
works fine as the sender; Marketing.Geek.From.Hell@spam. com might work just as well.

It turns out that this behavior is the basis of some of those annoying junk emails that show up in your mailbox without a real sender's address.[*] Salespeople infected with e-millionaire mania will email advertising to all addresses on a list without providing a real "From" address, to cover their tracks.

[*] We all know by now that such junk mail is usually referred to as spam, but not everyone knows that this name is a reference to a Monty Python skit where people trying to order breakfast at a restaurant were repeatedly drowned out by a group of Vikings singing an increasingly loud chorus of "spam, spam, spam..." (no, really). While spam can be used in many ways, this usage differs from its appearance in this book's examples, and from the name of a much-lauded meat product.

Normally, of course, you should use the same "To" address in the message and the SMTP call, and provide your real email address as the "From" value (that's the only way people will be able to reply to your message). Moreover, apart from teasing your significant other, sending phony addresses is just plain bad Internet citizenship. Let's run the script again to ship off another mail with more politically correct coordinates:

 C:\...\PP3E\Internet\Email>python smtpmail.py From? pp3e@earthlink.net To?   pp3e@earthlink.net Subj? testing smtpmail Type message text, end with line=(ctrl + D or Z) Lovely Spam! Wonderful Spam! ^Z Connecting... No errors. Bye. 

At this point, we could run whatever email tool we normally use to access our mailbox to verify the results of these two send operations; the two new emails should show up in our mailbox regardless of which mail client is used to view them. Since we've already written a Python script for reading mail, though, let's put it to use as a verification toolrunning the popmail script from the last section reveals our two new messages at the end of the mail list (parts of the output have been trimmed for space here):

 C:\...\PP3E\Internet\Email>python popmail.py  C:\Mark\PP3E-cd\Examples\PP3E\Internet\Email>popmail.py Password for pop.earthlink.net? Connecting... +OK NGPopper vEL_6_10 at earthlink.net ready <25557.1139379723@pop-borzoi.atl.sa .earthlink.net> There are 4 mail messages in 3264 bytes ('+OK', ['1 876', '2 800', '3 818', '4 770'], 28) -------------------------------------------------------------------------------- [Press Enter key] --------------------------------------------------------------------------------  ...more deleted... Status:  U Return-Path: <Eric.the.Half.a.Bee@yahoo.com> Received: from rwcrmhc12.comcast.net ([216.148.227.152])         by mx-austrian.atl.sa.earthlink.net (EarthLink SMTP Server) with ESMTP i d 1f6Iem1pl3Nl34j0         for <pp3e@earthlink.net>; Wed, 8 Feb 2006 00:51:07 -0500 (EST) Received: from [192.168.1.117] (c-67-161-147-100.hsd1.co.comcast.net[67.161.147. 100])           by comcast.net (rwcrmhc12) with ESMTP           id <20060208055106m1200t3cj1e>; Wed, 8 Feb 2006 05:51:06 +0000 From: Eric.the.Half.a.Bee@yahoo.com To: pp3e@earthlink.net Date: Tue Feb 07 22:51:08 2006 Subject: A B C D E F G Message-Id: <200602080051.1f6Iem1pl3Nl34j0@mx-austrian.atl.sa.earthlink.net>  ...more deleted... Fiddle de dum, Fiddle de dee, Eric the half a bee. -------------------------------------------------------------------------------- [Press Enter key] Status:  U Return-Path: <pp3e@earthlink.net> Received: from rwcrmhc11.comcast.net ([204.127.192.81])         by mx-limpkin.atl.sa.earthlink.net (EarthLink SMTP Server) with SMTP id 1f6IGA3yA3Nl34p0         for <pp3e@earthlink.net>; Wed, 8 Feb 2006 01:20:16 -0500 (EST) Received: from [192.168.1.117] (c-67-161-147-62.hsd1.co.comcast.net[67.161.147.6 2])           by comcast.net (rwcrmhc11) with ESMTP           id <20060208062000m1100bufjle>; Wed, 8 Feb 2006 06:20:00 +0000 From: pp3e@earthlink.net To: pp3e@earthlink.net Date: Tue Feb 07 23:19:51 2006 Subject: testing smtpmail Message-Id: <200602080120.1f6IGA3yA3Nl34p0@mx-limpkin.atl.sa.earthlink.net>  ...more deleted... Lovely Spam! Wonderful Spam! -------------------------------------------------------------------------------- Bye. 

Technically, the ISP used for this book's email account in this edition tests to make sure that at least the domain of the email sender's address (the part after "@") is a real, valid domain name, and disallows delivery if not. As mentioned earlier, some servers also require that SMTP senders have a direct connection to their network, and may require an authentication call with username and password (described earlier in this chapter). In the second edition of the book, I used an ISP that let me get away with more nonsense, but this may vary per server; the rules have tightened since then to limit spam.

14.5.3. More Ways to Abuse the Net

The first mail listed at the end of the preceding section was the one we sent with a fictitious address; the second was the more legitimate message. Like "From" addresses, header lines are a bit arbitrary under SMTP. smtpmail automatically adds "From:" and "To:" header lines in the message's text with the same addresses as passed to the SMTP interface, but only as a polite convention. Sometimes, though, you can't tell who a mail was sent to, eitherto obscure the target audience or to support legitimate email lists, senders may manipulate the contents of headers in the message's text.

For example, if we change smtpmail to not automatically generate a "To:" header line with the same address(es) sent to the SMTP interface call, we can manually type a "To:" header that differs from the address we're really sending tothe "To" address list passed into the smtplib send call gives the true recipients, but the "To:" header line in the text of the message is what most mail clients will display:

 C:\...\PP3E\Internet\Email>python smtpmail-noTo.py From? Eric.the.Half.a.Bee@aol.com To?   pp3e@earthlink.net Subj? a b c d e f g Type message text, end with line=(ctrl + D or Z) To: nobody.in.particular@marketing.com Spam; Spam and eggs; Spam, spam,   and spam ^Z Connecting... No errors. Bye. 

In some ways, the "From" and "To" addresses in send method calls and message header lines are similar to addresses on envelopes and letters in envelopes. The former is used for routing, but the latter is what the reader sees. Here, I gave the real "To" address as my mailbox on the earthlink.net server, but then gave a fictitious name in the manually typed "To:" header line; the first address is where it really goes and the second appears in mail clients. If your mail tool picks out the "To:" line, such mails will look odd when viewed.

For instance, when the mail we just sent shows up in my mailbox on earthlink.net, it's difficult to tell much about its origin or destination in either Outlook or a Python-coded mail tool we'll meet in the next chapter (see Figure 14-5). And its raw text will show only the machines it has been routed through.

Figure 14-5. Bogus mail in a mail client (PyMailGUI)


Once again, though, don't do this unless you have good reason. This demonstration is only intended to help you understand mail headers and simple spamming techniques. To write an automatic spam filter that deletes incoming junk mail, for instance, you need to know some of the telltale signs to look for in a message's text.

Such "To" address juggling may also be useful in the context of legitimate mailing liststhe name of the list appears in the "To:" header when the message is viewed, not the potentially many individual recipients named in the send-mail call. A mail client can simply send a mail to all on the list, but insert the general list name in the "To:" header.

But in other contexts, sending email with bogus "From:" and "To:" lines is equivalent to making anonymous phone calls. Most mailers won't even let you change the "From" line, and they don't distinguish between the "To" address and header line. When you program mail scripts of your own, though, SMTP is wide open in this regard. So be good out there, okay?[*]

[*] Since writing these words for the second edition of this book, spam mail has become quite a bit more sophisticated than simply forging sender and recipient names (as we all know far too well). For more on the subject, see the SpamBayes mail filter written in Python. Also, manipulating recipient names does indeed have practical application for email lists, so the techniques described are not necessarily all bad.

Does Anybody Really Know What Time It Is?

Minor caveat: the simple date format used in the smtpmail program doesn't quite follow the SMTP date formatting standard. Most servers don't care and will let any sort of date text appear in date header lines.

If you want to be more in line with the standard, though, you could format the date header with code like this (adopted from the standard module urllib, and parsable with standard tools such as the time.strptime call):

 import timegmt = time.gmtime(time.time( )) fmt = '%a, %d %b %Y %H:%M:%S GMT' str = time.strftime(fmt, gmt) hdr = 'Date: ' + strprint hdr 

The hdr variable looks like this when this code is run:

 Date: Fri, 02 Jun 2000 16:40:41 GMT 

Instead of the date format currently used by the smtpmail program:

 >>> import time >>> time.ctime(time.time( )) 'Fri Jun 02 10:23:51 2000' 

The time.strftime call allows arbitrary date and time formatting (time.ctime is just one standard format).

Better yet, in the new email package (described in this chapter), an email.Utils call can be used to properly format date and time automatically:

 >>> import email.Utils >>> email.Utils.formatdate( ) 'Mon, 06 Feb 2006 06:41:43 -0000' >>> email.Utils.formatdate(localtime=True) 'Sun, 05 Feb 2006 23:41:55 -0700' >>> email.Utils.formatdate(usegmt=True) 'Mon, 06 Feb 2006 06:42:11 GMT' 

See the mailtools example in this chapter for an example usage.


14.5.4. Back to the Big Internet Picture

So where are we in the Internet abstraction model now? Because mail is transferred over sockets (remember sockets?), they are at the root of all of this email fetching and sending. All email read and written ultimately consists of formatted bytes shipped over sockets between computers on the Net. As we've seen, though, the POP and SMTP interfaces in Python hide all the details. Moreover, the scripts we've begun writing even hide the Python interfaces and provide higher-level interactive tools.

Both popmail and smtpmail provide portable email tools but aren't quite what we'd expect in terms of usability these days. Later in this chapter, we'll use what we've seen thus far to implement a more interactive, console-based mail tool. In the next chapter, we'll also code a Tkinter email GUI, and then we'll go on to build a web-based interface in a later chapter. All of these tools, though, vary primarily in terms of user interface only; each ultimately employs the mail modules we've met here to transfer mail message text over the Internet with sockets.

14.5.5. Sending Email from the Interactive Prompt

Just as for reading mail, we can use the Python interactive prompt as our email sending client too, if we type calls manually:

 >>> from smtplib import SMTP >>> conn = SMTP('smtp.comcast.net') >>> conn.sendmail('pp3e@earthlink.net', ['lutz@rmi.net', 'pp3e@earthlink.net'], ... """From: pp3e@earthlink.net ... To: maillist ... Subject: test smtplib ... ... testing 1 2 3... ... """) {} 

This is a bit tricky to get right, thoughheader lines are governed by standards: the blank line after the subject line is required and significant, for instance. Furthermore, mail formatting gets much more complex as we start writing messages with attachments. In practice, the email package in the standard library is generally used to construct emails, before shipping them off with smtplib. The package lets us build mails by assigning headers and attaching and possibly encoding parts, and creates a correctly formatted mail text. To learn how, let's move on to the next section.




Programming Python
Programming Python
ISBN: 0596009259
EAN: 2147483647
Year: 2004
Pages: 270
Authors: Mark Lutz

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