I l@ve RuBoard Previous Section Next Section

10.15 Module: Watching for New IMAP Mail Using a GUI

Credit: Brent Burley

Suppose you need to poll an IMAP inbox for unread messages and display the sender and subject in a scrollable window using Tkinter. The key functionality you need is in the standard Python module imaplib, with some help from the rfc822 module. Example 10-4 reads the server name, user, and password from the ~/.imap file. They must be all on one line, separated by spaces.

The hard (and interesting) part of developing this program was figuring out how to get the IMAP part working, which took a fair bit of investigating. The most productive approach to understanding the IMAP protocol proved to be talking to the IMAP server directly from a Python interactive session to see what it returned:

>>> import imaplib
>>> M = imaplib.IMAP4(imap_server)
>>> M.login(imap_user, imap_password)
('OK', ['LOGIN complete'])
>>> M.select(readonly=1)
('OK', ['8'])
>>> M.search(None, '(UNSEEN UNDELETED)')
('OK', ['8'])
>>> M.fetch(8, '(BODY[HEADER.FIELDS (SUBJECT FROM)])')
('OK', [('8 (BODY[HEADER.FIELDS (SUBJECT FROM)] {71}', 'From: John Doe
<[email protected]>
Subject: test
message

'), ')'])

Interactive exploration is so simple with Python because of excellent interactive environments such as the standard interactive session (with readline and completion) or IDEs such as IDLE. As such, it is often the best way to clarify one's doubts or any ambiguities one may find in the documentation.

Example 10-4. Watching for new IMAP mail with a GUI
import imaplib, string, sys, os, re, rfc822
from Tkinter import *

PollInterval = 60 # seconds

def getimapaccount(  ):
    try:
        f = open(os.path.expanduser('~/.imap'))
    except IOError, e:
        print 'Unable to open ~/.imap: ', e
        sys.exit(1)
    global imap_server, imap_user, imap_password
    try:
        imap_server, imap_user, imap_password = string.split(f.readline(  ))
    except ValueError:
        print 'Invalid data in ~/.imap'
        sys.exit(1)
    f.close(  )

class msg: # a file-like object for passing a string to rfc822.Message
    def _ _init_ _(self, text):
        self.lines = string.split(text, '\015\012')
        self.lines.reverse(  )
    def readline(self):
        try: return self.lines.pop(  ) + '\n'
        except: return ''

class Mailwatcher(Frame):
    def _ _init_ _(self, master=None):
        Frame._ _init_ _(self, master)
        self.pack(side=TOP, expand=YES, fill=BOTH)
        self.scroll = Scrollbar(self)
        self.list = Listbox(self, font='7x13',
                            yscrollcommand=self.scroll.set,
                            setgrid=1, height=6, width=80)
        self.scroll.configure(command=self.list.yview)
        self.scroll.pack(side=LEFT, fill=BOTH)
        self.list.pack(side=LEFT, expand=YES, fill=BOTH)

    def getmail(self):
        self.after(1000*PollInterval, self.getmail)
        self.list.delete(0,END)
        try:
            M = imaplib.IMAP4(imap_server)
            M.login(imap_user, imap_password)
        except Exception, e:
            self.list.insert(END, 'IMAP login error: ', e)
            return

        try:
            result, message = M.select(readonly=1)
            if result != 'OK':
                raise Exception, message
            typ, data = M.search(None, '(UNSEEN UNDELETED)')
            for num in string.split(data[0]):
                try:
                    f = M.fetch(num, '(BODY[HEADER.FIELDS (SUBJECT FROM)])')
                    m = rfc822.Message(msg(f[1][0][1]), 0)
                    subject = m['subject']
                except KeyError:
                    f = M.fetch(num, '(BODY[HEADER.FIELDS (FROM)])')
                    m = rfc822.Message(msg(f[1][0][1]), 0)
                    subject = '(no subject)'
                fromaddr = m.getaddr('from')
                if fromaddr[0] == "": n = fromaddr[1]
                else: n = fromaddr[0]
                text = '%-20.20s  %s' % (n, subject)
                self.list.insert(END, text)
            len = self.list.size(  )
            if len > 0: self.list.see(len-1)
        except Exception, e:
            self.list.delete(0,END)
            print sys.exc_info(  )
            self.list.insert(END, 'IMAP read error: ', e)
        M.logout(  )

if _ _name_ _=='_ _main_ _':
    getimapaccount(  )
    root = Tk(className='mailwatcher')
    root.title('mailwatcher')
    mw = Mailwatcher(root)
    mw.getmail(  )
    mw.mainloop(  )

10.15.1 See Also

Documentation for the standard library modules imaplib and rfc822 in the Library Reference; information about Tkinter can be obtained from a variety of sources, such as Pythonware's An Introduction to Tkinter, by Fredrik Lundh (http://www.pythonware.com/library), New Mexico Tech's Tkinter reference (http://www.nmt.edu/tcc/help/lang/python/docs.html), and various books; the IMAP protocol is described in RFC 2060 (http://www.ietf.org/rfc/rfc1939.txt).

    I l@ve RuBoard Previous Section Next Section