Notify with Python
By: James Briggs
Tags: Data Science, Python, Technology, Programming, Software Development

The Code

All of the functionality above filters from a single script called notify.py .

We will use Outlook in our example code. However, translating this to other providers is incredibly easy, which we will also cover quickly at the end.

There are two Python libraries we need here, email and smtplib .

email — For managing email messages. With this we will setup the email message itself, including subject, body, and attachments.

— For managing email messages. With this we will setup the email message itself, including subject, body, and attachments. smtplib — Handles the SMTP connection. The simple mail transfer protocol (SMTP) is the protocol used by the majority of email systems, allowing mail to be sent over the internet.’

MIME

The message itself is built using a MIMEMultipart object from the email module. We also use three MIME sub-classes, which we attach to the MIMEMultipart object:

MIMEText — This will contain the email ‘payload’, meaning the text within the email body.

— This will contain the email ‘payload’, meaning the text within the email body. MIMEImage — Reasonably easy to guess, this is used to contain images within our email.

— Reasonably easy to guess, this is used to contain images within our email. MIMEApplication — Used for MIME message application objects. For us, these are file attachments.

In addition to these sub-classes, there are also other parameters, such as the Subject value in MimeMultipart . All of these together gives us the following structure.

Let’s take a look at putting these all together.

This script, for the most part, is reasonably straightforward. At the top, we have our imports — which are the MIME parts we covered before, and Python’s os library.

Following this define a function called message . This allows us to call the function with different parameters and build an email message object with ease. For example, we can write an email with multiple images and attachments like so:

email_msg = message(

text="Model processing complete, please see attached data.",

img=['accuracy.png', 'loss.png'],

attachments=['data_in.csv', 'data_out.csv']

)

First we initialize the MIMEMultipart object, assigning it to msg .

We then set the email subject using the 'Subject' key.

The attach method allows us to add different MIME sub-classes to our MIMEMultipart object. With this we can add the email body, using the MIMEText sub-class.

For both images img and attachments attachment , we can pass either nothing, a single file-path, or a list of file-paths.

This is handled by first checking if the parameters are None , if they are, we pass. Otherwise, we check the data-type given, is it is not a list , we make it one — this allows us to use the following for loop to iterate through our items.

At this point we use the MIMEImage and MIMEApplication sub-classes to attach our images and files respectively. For both we use os.basename to retrieve the filename from the given file-path, which we include as the attachment name.

SMTP

Now that we have built our email message object, we need to send it.

This is where the smtplib module comes in. The code is again, pretty straight-forward, with one exception.

As we are dealing directly with different email provider’s, and their respective servers, we need different SMTP addresses for each. Fortunately, this is really easy to find.

Type “outlook smtp” into Google. Without even clicking on a page, we are given the server address smtp-mail.outlook.com , and port number 587 .

We use both of these when initalizing the SMTP object with smtplib.SMTP — near the beginning of the send function.

smtp.ehlo() and smtp.starttls() are both SMTP commands. ehlo (Extended Hello) essentially greets the server. starttls informs the server we will be communicating using an encrypted transport level security (TLS) connection. You can learn more about SMTP commands here.

After this we simply read in our email and password from file, storing both in email and pwd respectively.

We then login to the SMTP server with smtp.login , and send the email with smtp.sendmail .

I always send the notifications to myself, but in the case of automated reporting (or for any other reason), you may want to send the email elsewhere. To do this, change the destination_address : smtp.sendmail(email, destination_address, msg.as_string) .

Finally, we terminate the session and close the connection with smtp.quit .

All of this is placed within a try-except statement. In the case of momentary network connection loss, we will be unable to connect to the server. Resulting in a socket.gaierror .

Implementing this try-except statement prevents the program from breaking in the case of a lapse in network connection. How you deal with this may differ, depending on how important it is for the email to be sent.

For me, I use this for ML model training updates and data transfer completion. If an email does not get sent, it doesn’t really matter. So this simple, passive handling of connection loss is suitable.

Putting it Together

Now we have written both parts of our code, we can send emails with just:

# build a message object

msg = message(text="See attached!", img='important.png',

attachment='data.csv') send(msg) # send the email (defaults to Outlook)

Jason Farlette pointed out that those of you using Gmail may need to ‘allow access for less secure apps’. Steps for doing so can be found in this Stack Overflow question.