home

Mailbox Monitor in Python (written for Python 3.3.2)

Update: 2017 - OAUTH / AUTHENTICATE

In early 2017 I wrote an article about using OAUTH / AUTHENTICATE with GMail. This is important because soon after I wrote this article / code (2013), GMail changed its policy to use OAUTH by default. Thus, the following / linked script would not work without reducing security in GMail. I also discuss how to reduce security ("Allow less secure apps") in the 2017 article.

I will probably never update this Python code for OAUTH in part because I outlined (in 2017) how to do this in PHP. If I were to write a script for OAUTH, it would be in PHP.

Purpose and Main Features (original 2013 article / code)

This script monitors a mailbox for new emails and email deletion. It uses the IMAP IDLE command to continuously monitor the box and detect activity within a few seconds or less. It writes activity to a file. You can then use "tail -f" or such from another program to do more processing.

I've tested it on GMail using a 14 minute interval of DONE (end idle), NOOP, and re-IDLE to keep the connection alive. In a few days of testing, it's stayed connected for 2 - 3 hours; I havnen't left it running for longer yet. It has error handling that should overcome lost connections.

I've seen IDLE called "email push" as opposed to polling. I'd call my script a listener or event handler.

The script also logs the IMAP mailbox UID and email-header Message-Id of both new messages and existing messages as they come up in the IMAP EXISTS stream of activity.

I'm testing in Windows 7, with-up-to-date updates as I'm writing this.

License

Do whatever you want with the code and this documentation, but I would like attribution even if this is just a distant inspiration. A link back to this page would be nice. Please feel free re-post any or all of this for redundancy.

(first) Acknowledgement

Speaking of inspiration, I started in PHP with "2naive's" code. That code was very helpful.

Prereqs

You need to create an SSL Certificate, self-signed is fine. You don't need to put your real name or location, and those fields may not be required at all. As detailed in the Python doc, you can create a cert like this:
% openssl req -new -x509 -days 365 -nodes -out cert.pem -keyout cert.pem

You can get openssl for Windows from Cygwin and probably other places. I used version from-Cygwin "OpenSSL 1.0.1e 11 Feb 2013," but I'm pretty sure earlier versions will work. I haven't tested what happens when the cert expires; I don't know how GMail will react to that.

More Description / Doc

IMAP Commands

Should be very useful to understand what's going on.

The Threads

The listener threads are to run multiple mailboxes. The idle threads are to "ping" the server with NOOP every 14 minutes.

14 Minutes

There is some discussion on this on the web. I had some indication that 29 minutes would not work, but I'm not swearing that 14 minutes is the magic number.

Alternatives

Password versus OAUTH

Just as an FYI, one can probably use OAUTH with refresh tokens with GMail rather than one's email password.

Extra Notes

What happened in PHP?

I mentioned that I started in PHP. It seemed that neither threading nor forking was going to work in Windows. (I love the idea of Linux, I primarily used Linux for years, and I hope to return full-time one day, but as much as I hate to admit it, Linux can be painful to use as a workstation for anything beyond the basics.) Python appeared that it would work, and I'd been meaning to learn it. Eric Raymond recommended it years ago, and then XKCD did. (Note that I would take political advice from Raymond and not Munroe, but I'll take tech advice from Munroe.)

4096 bit SSL Cert

The openssl command I used will generate a 1024 bit SSL cert. I'm not certain of what will happen if you use a higher one, but in honor of Neal Stephenson's character Avi Halaby, I should try it with 4096 bits some time. (Cryptonomicon, fiction, 1999)

Changes

0.3.9

  1. Changed to 29 minute NOOP cycle. Seems to work.
  2. Rearranged the exception handling in listenerThread.run(). Before, it wasn't covering the idle thread.

0.3.8

I put a VERSION_G (for global) variable in there and printed it to try to keep messups like last time from happening.

I created 3 ways to invoke:

  1. % py imap_idle.py
    and then input uname / pwd by hand
  2. % py imap_idle.py bob@example.com password
  3. % printf "%s\n%s\n" "bob@example.com password" | py imap_idle.py -pipe

    This one works in Cygwin Terminal--Cygwin setup.exe v2.774 (copyright through 2012), bash "GNU bash, version 4.1.10(4)-release (i686-pc-cygwin) / Copyright (C) 2009 Free Software Foundation, Inc.", mintty (aka Cygwin Terminal), "mintty 1.1.3 (C) 2013 Andy Koppe"

    Lord only knows what will happen in Linux, but I'm sure there are variants on this.

WARNING

With options 2 and 3, you run the risk of leaking your password in a shared environment. I think there are ways to make option 3 safe, but I'll leave that to others to ponder for now.

More on the -pipe option

I had to provide an explicit pipe argument because getpass (in Windows 7) will hang, waiting for input despite the piped input.

v.0.3.7

Messed up the comments and took a few minutes to realize it. The previous version had a syntax error with the comments.

v0.3.6

I took username / password out of the script itself and made them arguments. Note that people might see your arguments on shared hosts with the "ps" command or such. My understanding is that modern systems may tend to prevent such things, but you've been warned.

I did that after I posted my GMail password to the web for about 30 seconds. The web server access logs show that no one saw it, but I've changed my password anyhow.

This is a great argument for using OAUTH or something different.

v0.3.5

The first public version (0.3.1) would exit after several exceptions. For 0.3.5, I observed that GMail terminates the connection more often than one would think, so I changed the exception handling to reset the exception count if there were no exceptions for a period of time. I haven't tested this in full.

Version History

Valid XHTML 1.0 Strict