17.4. Sending Mail by SMTP
PyMailCGI supports two main functions, as links on the root page: composing and sending new mail to others and viewing incoming mail. The View function leads to pages that let users reply to, forward, and delete existing email. Since the Send function is the simplest, let's start with its pages and scripts first.
17.4.1. The Message Composition Page
The Send function steps users through two pages: one to edit a message and one to confirm delivery. When you click on the Send link on the main page in Figure 17-2 the Python CGI script in Example 17-3 runs on the web server.
Example 17-3. PP3E\Internet\Web\PyMailCgi\cgi-bin\onRootSendLink.py
No, this file wasn't truncated; there's not much to see in this script because all the action has been encapsulated in the commonhtml and externs modules. All that we can tell here is that the script calls something named editpage to generate a reply, passing in something called myaddress for its "From" header.
That's by designby hiding details in shared utility modules we make top-level scripts such as this much easier to read and write, avoid code redundancy, and achieve a common look-and-feel to all our pages. There are no inputs to this script either; when run, it produces a page for composing a new message, as shown in Figure 17-3.
Figure 17-3. PyMailCGI send (write) page
Most of the composition page is self-explanatoryfill in headers and the main text of the message (a "From" header and standard signature line are initialized from settings in the mailconfig module, discussed further ahead). The Browse buttons open file selector dialogs, for picking an attachment. This interface looks very different from the PyMailGUI client program in Chapter 15, but it is functionally very similar. Also notice the top and bottom of this pagefor reasons explained in the next section, they are going to look the same in all the pages of our system.
17.4.2. The Send Mail Script
As usual, the HTML of the edit page in Figure 17-3 names its handler script. When we click its Send button, Example 17-4 runs on the server to process our inputs and send the mail message.
Example 17-4. PP3E\Internet\Web\PyMailCgi\cgi-bin\onEditPageSend.py
This script gets mail header and text input information from the edit page's form (or from query parameters in an explicit URL) and sends the message off using Python's standard smtplib module, courtesy of the mailtools package. We studied mailtools in Chapter 14, so I won't say much more about it now. Note, though, that because we are reusing its send call, sent mail is automatically saved in a "sentmail.txt" file on the server; there are no tools for viewing this in PyMailCGI itself, but it serves as a log.
New in this version, the saveAttachments function grabs any part files sent from the browser and stores them in temporary local files on the server from which they will be added to the mail when sent. We covered CGI upload in detail at the end of Chapter 16; see that discussion for more on how the code here works. The business of attaching the files to the mail is automatic in mailtools.
A utility in commonhtml ultimately fetches the name of the SMTP server to receive the message from either the mailconfig module or the script's inputs (in a form field or URL query parameter). If all goes well, we're presented with a generated confirmation page, as captured in Figure 17-4.
Figure 17-4. PyMailCGI send confirmation page
As we'll see, this send mail script is also used to deliver reply and forward messages for incoming POP mail. The user interface for those operations is slightly different for composing new email from scratch, but as in PyMailGUI, the submission handler logic has been factored into the same, shared codereplies and forwards are really just mail send operations with quoted text and preset header fields.
Notice that there are no usernames or passwords to be found here; as we saw in Chapter 14, SMTP usually requires only a server that listens on the SMTP port, not a user account or password. As we also saw in that chapter, SMTP send operations that fail either raise a Python exception (e.g., if the server host can't be reached) or return a dictionary of failed recipients; our mailtools package modules insulate us from these details by always raising an exception in either case.
17.4.3. Error Pages
If there is a problem during mail delivery, we get an error page such as the one shown in Figure 17-5. This page reflects a failed recipient and includes a stack trace generated by the standard library's TRaceback module. On an actual exception, the Python error message and extra details would be displayed.
Figure 17-5. PyMailCGI send error page
It's also worth pointing out that the commonhtml module encapsulates the generation of both the confirmation and the error pages so that all such pages look the same in PyMailCGI no matter where and when they are produced. Logic that generates the mail edit page in commonhtml is reused by the reply and forward actions too (but with different mail headers).
17.4.4. Common Look-and-Feel
In fact, commonhtml makes all pages look similarit also provides common page header (top) and footer (bottom) generation functions, which are used everywhere in the system. You may have already noticed that all the pages so far follow the same pattern: they start with a title and horizontal rule, have something unique in the middle, and end with another rule, followed by a Python icon and link at the bottom. This common look-and-feel is the product of shared code in commonhtml; it generates everything but the middle section for every page in the system (except the root page, a static HTML file).
Most important, if we ever change the header and footer format functions in the commonhtml module, all our page's headers and footers will automatically be updated. If you are interested in seeing how this encapsulated logic works right now, flip ahead to Example 17-14. We'll explore its code after we study the rest of the mail site's pages.
17.4.5. Using the Send Mail Script Outside a Browser
I initially wrote the send script to be used only within PyMailCGI using values typed into the mail edit form. But as we've seen, inputs can be sent in either form fields or URL query parameters. Because the send mail script checks for inputs in CGI inputs before importing from the mailconfig module, it's also possible to call this script outside the edit page to send emailfor instance, explicitly typing a URL of this nature into your browser's address field (but all on one line and with no intervening spaces):
http://localhost:8000/cgi-bin/ onEditPageSend.py?site=smtp.rmi.net& Fromemail@example.com& Tofirstname.lastname@example.org& Subject=test+url& text=Hello+Mark;this+is+Mark
will indeed send an email message as specified by the input parameters at the end. That URL string is a lot to type into a browser's address field, of course, but it might be useful if generated automatically by another script. As we saw in Chapters 14 and 16, the module urllib can then be used to submit such a URL string to the server from within a Python program. Example 17-5 shows one way to automate this.
Example 17-5. PP3E\Internet\Web\PyMailCgi\sendurl.py
Running this script from the system command line is yet another way to send an email messagethis time, by contacting our CGI script on a web server machine to do all the work. The script sendurl.py runs on any machine with Python and sockets, lets us input mail parameters interactively, and invokes another Python script that lives on a possibly remote machine. It prints HTML returned by our CGI script:
C:\...\PP3E\Internet\Web\PyMailCgi>sendurl.py Site>smtp.comcast.net From>email@example.com To >firstname.lastname@example.org Subj>testing sendurl.py text>But sir, it's only wafer-thin... Reply html: <html><head><title>PyMailCGI: Confirmation page (PP3E)</title></head> <body bgcolor="#FFFFFF"><h1>PyMailCGI Confirmation</h1><hr> <h2>Send mail operation was successful</h2> <p>Press the link below to return to the main page.</p> </p><hr><a href="http://www.python.org"> <img src="/books/2/726/1/html/2/../PythonPoweredSmall.gif" align=left alt="[Python Logo]" border=0 hspace=15></a> <a href="../pymailcgi.html">Back to root page</a> </body></html>
The HTML reply printed by this script would normally be rendered into a new web page if caught by a browser. Such cryptic output might be less than ideal, but you could easily search the reply string for its components to determine the result (e.g., using the string find method or an in membership test to look for "successful"), parse out its components with Python's standard htmllib or re module, and so on. The resulting mail messageviewed, for variety, with Chapter 15's PyMailGUI programshows up in this book's email account as seen in Figure 17-6 (it's a single text-part message).
Figure 17-6. sendurl.py result
Of course, there are other, less remote ways to send email from a client machine. For instance, the Python smtplib module (used by mailtools) itself depends only upon the client and SMTP server connections being operational, whereas this script also depends on the web server machine and CGI script (requests go from client to web server to CGI script to SMTP server). Because our CGI script supports general URLs, though, it can do more than a mailto: HTML tag and can be invoked with urllib outside the context of a running web browser. For instance, as discussed in Chapter 16, scripts like sendurl.py can be used to invoke and test server-side programs.