Dec 28 2008

Creating Mail Digests with Python and IMAP

Published by tim at 10:00 pm under coding

There should be a word for “effort undertaken even though you’re sure, somewhere, somebody has written something for it already, or maybe it’s already a Linux command.” I get the distinct feeling that this utility already exists, but I googled to no avail.

I have a bunch of e-mail boxes, some incredibly infrequently used, but I wanted to be sure that I noticed any important messages without having to go to all the work of, you know, actually logging into them. So I created a quick little digest utility that will on a daily cron.

This entire process was about two times more painful than it really needed to be because using the IMAP libraries in Python really requires knowing the IMAP specification, and I tried to be lazy and not bother reading the specification first, so I got stuck for a bit on how to search an IMAP mailbox properly.

TL;DR Version
You see,

imap.search(None, ‘ALL’)

was working fine, but I wanted to get all of yesterday’s messages. It turns out that instead of using:

imap.search(None, ‘SINCE 1-Dec-2008′)

you just need to have every token be a separate parameter:

imap.search(None, ‘SINCE’, ‘1-Dec-2008′)

which seems unpythonic to me, but no matter.

Testing From The Console
It helped me to be able to interact with the IMAP servers from the command line. Usually, I just use telnet to do this, but the IMAP servers that I was working with were SSL only. There’s an easy way to deal with this, at least under Debian; just install the telnet-ssl package and then log into it like:

telnet-ssl -z ssl imap.gmail.com

IMAP takes arbitrary sequence IDs at the start of every command. The starting login sequence is something like:

0001 LOGIN userid password
0002 SELECT INBOX
0003 SEARCH SINCE 27-Dec-2008

Turning On Debug Information
Alternatively, you can just turn on the debug information for the IMAP libraries, by doing something like:

imaplib.Debug = 99
imap = imaplib.IMAP4_SSL(”imap.gmail.com”)

… and then go through the standard sequence.

Code can be found on github or cut and pasted from below.

#!/usr/bin/python
# create a digest of e-mail in the past 24 hours

import cStringIO as StringIO
import imaplib
import smtplib
import datetime
import getopt
import sys
import email.MIMEText

args = []

try:
    opt, args = getopt.getopt(sys.argv[1:], "", ["days="])
except getopt.GetoptError:
    pass

if len(args) < 5:
    print "usage: digest.py [--days=nn] from-mail to-mail imap-host imap-userid imap-password"
    sys.exit(0)

opt = dict(opt)
mail_from = args[0]
mail_to = args[1]
imap_host = args[2]
imap_userid = args[3]
imap_password = args[4]
imap_days = int(opt.get("--days", 1))

since = datetime.datetime.today() - datetime.timedelta(days=imap_days)
since = since.strftime("%d-%b-%Y")

imap = imaplib.IMAP4_SSL(imap_host)
imap.login(imap_userid, imap_password)
imap.select(mailbox="INBOX")
typ, dat = imap.search(None, 'SINCE', since)

buf = StringIO.StringIO()
msgs = map(int, dat[0].split())

if msgs:
    msgs.reverse()

    for msg in msgs:
        fields = imap.fetch(msg, '(BODY[HEADER.FIELDS (FROM SUBJECT DATE)])')
        buf.write( fields[1][0][1] )

    msg = email.MIMEText.MIMEText(buf.getvalue())
    msg['Subject'] = "Digest for %s" % since
    msg['From'] = mail_from
    msg['Reply-to'] = mail_from
    msg['To'] = mail_to

    s = smtplib.SMTP()
    s.connect("localhost")
    s.sendmail(mail_from, mail_to, msg.as_string())
    s.close()

2 responses so far

2 Responses to “Creating Mail Digests with Python and IMAP”

  1. sebp999on 02 Mar 2010 at 9:26 am

    Aha the imaplib docs are wrong! That sorts it! Thank you!

  2. Wayneon 23 Mar 2010 at 9:45 am

    Ok, seriously you just saved me a bunch of work. I had the EXACT same feeling as you, it had to be out there… somewhere.

    I searched and searched… and finally you were result #3 for “python email fetch all mail since yesterday”

    Thanks for sharing!

Trackback URI | Comments RSS

Leave a Reply