Module: Interactive POP3 Mailbox Inspector

Credit: Xavier Defrang

Suppose you have a POP3 mailbox somewhere, perhaps on a slow connection, and need to examine messages, and perhaps mark them for deletion, in an interactive way. Perhaps you’re behind a slow Internet link and don’t want to wait for that funny 10-MB MPEG movie that you already received twice yesterday to be fully downloaded before you can read your mail. Or maybe there’s a peculiar malformed message that is hanging your MUA. This issue is best tackled interactively, but you need a helping script to let you examine some data about each message and determine which messages should be removed.

Instead of telneting to your POP server and trying to remember the POP3 protocol commands (or hoping that the server implements help), you can use the small script shown in Example 10-3 to inspect your mailbox and do some cleaning. Basically, Python’s standard POP3 module, poplib, remembers the protocol commands on your behalf, and this script helps you use them appropriately.

Example 10-3 uses the poplib module to connect to your mailbox. It then prompts you about what to do with each undelivered message. You can view the top of the message, leave it on the server, or mark it for deletion. No particular tricks or hacks are used in this piece of code: it’s a simple example of poplib usage. In addition to being practically useful in emergencies, it can show how poplib works. The poplib.POP3 call returns an object that is ready for connection to a POP3 server specified as its argument. We complete the connection by calling the user and pass_ methods to specify a user ID and password. Note the trailing underscore in pass_: this method could not be called pass because that is a Python keyword (the do-nothing statement), and by convention, such issues are always solved by appending an underscore to the identifier.

After connection, we keep working with methods of the pop object. The stat method returns the number of messages and the total size of the mailbox in bytes. The top method takes a message-number argument and returns information about that message, as well as the message itself as a list of lines (you can specify a second argument N to ensure that no more than N lines are returned). The dele method also takes a message-number argument and deletes that message from the mailbox (without renumbering all other messages). When we’re done, we call the quit method. If you’re familiar with the POP3 protocol, you’ll notice the close correspondence between these methods and the POP3 commands.

Example 10-3. Interactive POP3 mailbox inspector

# Helper interactive script to clean POP3 mailboxes from malformed mails that
# hangs MUA's, messages that are too large, etc.
#
# Iterates over nonretrieved mails, prints selected elements from the headers,
# and prompts interactively about whether each message should be deleted

import sys, getpass, poplib, re

# Change according to your needs
POPHOST = "pop.domain.com"
POPUSER = "jdoe"
POPPASS = ""
# The number of message body lines to retrieve
MAXLINES = 10
HEADERS = "From To Subject".split(  )

args = len(sys.argv)
if args>1: POPHOST = sys.argv[1]
if args>2: POPUSER = sys.argv[2]
if args>3: POPPASS = sys.argv[3]
if args>4: MAXLINES= int(sys.argv[4])
if args>5: HEADERS = sys.argv[5:]

# Headers you're actually interested in
rx_headers  = re.compile('|'.join(headers), re.IGNORECASE)

try:
    # Connect to the POPer and identify user
    pop = poplib.POP3(POPHOST)
    pop.user(POPUSER)

    if not POPPASS or POPPASS=='=':
        # If no password was supplied, ask for it
        POPPASS = getpass.getpass("Password for %s@%s:" % (POPUSER, POPHOST))

    # Authenticate user
    pop.pass_(POPPASS)

    # Get some general information (msg_count, box_size)
    stat = pop.stat(  )

    # Print some useless information
    print "Logged in as %s@%s" % (POPUSER, POPHOST)
    print "Status: %d message(s), %d bytes" % stat

    bye = 0
    count_del = 0
    for n in range(stat[0]):

        msgnum = n+1

        # Retrieve headers
        response, lines, bytes = pop.top(msgnum, MAXLINES)

        # Print message info and headers you're interested in
        print "Message %d (%d bytes)" % (msgnum, bytes)
        print "-" * 30
        print "\n".join(filter(rx_headers.match, lines))
        print "-" * 30

        # Input loop
        while 1:
            k = raw_input("(d=delete, s=skip, v=view, q=quit) What?")
            k = k[:1].lower(  )
            if k == 'd':
                # Mark message for deletion
                k = raw_input("Delete message %d? (y/n)" % msgnum)
                if k in "yY":
                    pop.dele(msgnum)
                    print "Message %d marked for deletion" % msgnum
                    count_del += 1
                    break
            elif k == 's':
                print "Message %d left on server" % msgnum
                break
            elif k == 'v':
                print "-" * 30
                print "\n".join(lines)
                print "-" * 30
            elif k == 'q':
                bye = 1
                break

        # Time to say goodbye?
        if bye:
            print "Bye"
            break

    # Summary
    print "Deleting %d message(s) in mailbox %s@%s" % (
        count_del, POPUSER, POPHOST)

    # Commit operations and disconnect from server
    print "Closing POP3 session"
    pop.quit(  )

except poplib.error_proto, detail:

    # Fancy error handling
    print "POP3 Protocol Error:", detail

See Also

Documentation for the standard library modules poplib and getpass in the Library Reference; the POP protocol is described in RFC 1939 (http://www.ietf.org/rfc/rfc1939.txt).

Get Python Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.