Tuesday, April 14, 2015

SendEmail automates boiler-plate email messages

Last week I needed to email out marks to my students. How to automate a bulk email project? No doubt the Internet will turn up hundreds of spyware-infested programmes of dubious lineage and intent, all designed for just this purpose.

So I turned to Python. Because if I write my own code I can trust it to do exactly what I wish, no more, no less. And so memories of Paul's first Python programme came flooding back... ah, sweet nostalgia! If you have no idea what I am talking about here, read Sunday's article. In it, I presented the minimal four line Python snippet that sends email. In this article I will develop that core into a fully-functional command-line application.

Implementing SendEmail
Here's that code again, for reference.

import smtplib
server = smtplib.SMTP('hostname', 25)
server.login('username', 'password')
server.sendmail('me@here.com', 'you@there.com',\
"I just thought I'd write to tell you about Python.")

I think that's pretty straightforward. In fact it's difficult to comment Python code since it's all so self-explanatory. But a few remarks for beginners might be helpful.

A "module" is simply a file containing code, grouped together because it shares certain functionality. We can use an existing module in our current code by way of the import command. This exposes certain named objects, for example the class SMTP from the module smtplib.

It so happens that smtplib is a "standard" module, meaning that it comes with the default install of Python. We can generally rely on these to work across operating systems, although there are necessary exceptions. I have regularly written code on Windows to deploy on UNIX and Mac OS with no problems whatsoever. That's one of the nice things about an interpreted language.

If you know any object-oriented programming, it is apparent that line 2 instantiates the SMTP class, with arguments for the host name and port number. From here on, the code deals with our specific instance, server. The first method, login, is, naturally enough, used to log onto the secure server. I do hope that open SMTP servers are a thing of the past! (In fact, we should likely also be using SSL for a secure connection... but I will leave that for now.)

The second method, sendmail, specifies the "to" and "from" addresses, along with the body of the text.

Of course this simple snippet is just a start. The body of the email should be formatted according to the accepted conventions, in order to provide header information. For example, one thing we definitely want to do is specify the subject line. And this, in fact, goes inside the body of an email message. Even if it doesn't look like it when you read your email.

But before we extend code functionality much further, it makes good sense to write our own custom class. This will wrap smtplib in order to encapsulate various configuration data. The above code snippet shows all of these hard-coded as literals, but we will allow these to be specified through class properties.

My class SendEmail implements the functionality discussed so far, and additionally allows us to specify time-out duration and server debugging status. Any possible "exceptions" (that is to say, errors) are translated to a simple return string. With generous spacing and commenting, SendEmail comes to 90 lines. To understand them, you'll need to know a bit more about the syntax of Python classes. There are many good tutorials linked from python.org that you can check out.


Were I to call my module from other code, I would use SendEmail directly. But I also wish to use this module as a command line application. A second class, CommandLine, implements the sort of functionality we might expect. Namely:
  • Command-line parameters may be used to specify input and output particulars.
  • A configuration file holds the email data specific to this run (because hard-coding = evil).
  • Progress can be logged to a file for review.
  • Feedback is provided to the terminal, um, screen.
  • A reasonable, though not paranoid, amount of error-checking is implemented.

Boiler-plate text

This got me most of the way to my goal. The last and most important piece of functionality I required was the ability to "boiler-plate" text in the email body. This allows me to send custom messages to different addressees.

This is best explained by presenting a typical configuration file.

host = smtp.server.com
port = 26
username = andrebreton
password = thefish

subject: Surrealist working group membership check
from: andre@andrebreton.com
to: max@artist.com | fpicabia@artist.com | 
    paul.eluard@artist.com | dot@artist.com

data =
    Max Ernst | 2 April 1891 | Bruhl, Germany
    Francis Picabia | 22 January 1879 | Paris, France
    Paul Éluard | 14 December 1895 | Saint-Denis, France
    Dorothea Tanning | August 25, 1910 | Galesburg, Illinois

body = 
    Hello {0}:

    Please confirm that you were born in {2} on {1}.
    This is necessary to update our records.

    Yours, André.

Place holders in the body of the email will be filled in by the corresponding fields from the data section. Every line of data results in one email being sent. If there are no braces, the same email will be sent to all, and no data section is required.

While I was at it, I implemented an alternative configuration structure. This omits the "to" line. Instead, the first field in each data line will be understood as the email address. This format is often handier, since the entire data structure can then easily be generated by a spreadsheet or some other desktop application, to look like so:

data =
    max@artist.com | Max Ernst | ...
    fpicabia@artist.com | Francis Picabia | ...
    paul.eluard@artist.com | Paul Éluard | ...
    dot@artist.com | Dorothea Tanning | ...

The class CommandLine handles all of this functionality in 175 lines. I consider that to be quite efficient. After all, the goal is not maximum concision but maximum readability. When I come to change this module in ten year's time, I will still understand it.

In fact, the first version of SendEmail was written in July 2000. It's already 15 years old. The proof of the pudding, etc.

And that's the real benefit of Python. It's made to be read by humans. Even the same human, years later.

Grab the file

I have made SendFile.py available on my server. Grab it here.


The examples shown here should not be taken to imply membership, or lack of same, in the Surrealist movement. ;-)


No comments:

Post a Comment