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" [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.

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 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.

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.

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. 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.

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:...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.

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, 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.

Bogus mail in a mail client (PyMailGUI)

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 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?[*]

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.

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('[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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.182.73