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:
os.popen
to launch a
command-line mail programOn some systems, you can send email from a script with a call of the form:
os.popen('mail -s "xxx" [email protected]', '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 stream—here, 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.
sendmail
programThe 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.
smtplib
Python
modulePython’s standard library comes with support for the
client-side interface to SMTP—the Simple Mail Transfer
Protocol—a 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.
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.
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. PP3EInternetEmailsmtpmail.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: [email protected] To = raw_input('To? ').strip( ) # ex: [email protected] 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 To: %s Date: %s Subject: %s ' % (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 interface—it 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
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.
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:...PP3EInternetEmail>smtpmail.py
From?[email protected]
To?[email protected]
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
([email protected]
), 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, though—it’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, [email protected]
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.
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:...PP3EInternetEmail>python smtpmail.py
From?[email protected]
To?[email protected]
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 tool—running 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:...PP3EInternetEmail>python popmail.py
C:MarkPP3E-cdExamplesPP3EInternetEmail>popmail.py
Password for pop.earthlink.net?
Connecting...
+OK NGPopper vEL_6_10 at earthlink.net ready <[email protected]
.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: <[email protected]>
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 <[email protected]>; 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: [email protected]
To: [email protected]
Date: Tue Feb 07 22:51:08 2006
Subject: A B C D E F G
Message-Id: <[email protected]>
...more deleted...
Fiddle de dum, Fiddle de dee,
Eric the half a bee.
--------------------------------------------------------------------------------
[Press Enter key]
Status: U
Return-Path: <[email protected]>
Received: from rwcrmhc11.comcast.net ([204.127.192.81])
by mx-limpkin.atl.sa.earthlink.net (EarthLink SMTP Server) with SMTP id
1f6IGA3yA3Nl34p0
for <[email protected]>; 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: [email protected]
To: [email protected]
Date: Tue Feb 07 23:19:51 2006
Subject: testing smtpmail
Message-Id: <[email protected]>
...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.
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, either—to 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 to—the “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:...PP3EInternetEmail>python smtpmail-noTo.py
From?[email protected]
To?[email protected]
Subj?a b c d e f g
Type message text, end with line=(ctrl + D or Z)To: [email protected]
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.
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 lists—the 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?[*]
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.
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('[email protected]', ['[email protected]', '[email protected]'],
..."""From: [email protected]
...To: maillist
...Subject: test smtplib
... ...testing 1 2 3...
...""")
{}
This is a bit tricky to get right, though—header 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.
[*] 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.
[*] 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.
18.227.72.6