imaplib - IMAP4 client library

Purpose:Client library for IMAP4 communication.
Available In:1.5.2 and later

imaplib implements a client for communicating with Internet Message Access Protocol (IMAP) version 4 servers. The IMAP protocol defines a set of commands sent to the server and the responses delivered back to the client. Most of the commands are available as methods of the IMAP4 object used to communicate with the server.

These examples discuss part of the IMAP protocol, but are by no means complete. Refer to RFC 3501 for complete details.

Variations

There are 3 client classes for communicating with servers using various mechanisms. The first, IMAP4, uses clear text sockets; IMAP4_SSL uses encrypted communication over SSL sockets; and IMAP4_stream uses the standard input and standard output of an external command. All of the examples below will use IMAP4_SSL.

Connecting to a Server

There are two steps for establishing a connection with an IMAP server. First, set up the socket connection itself. Second, authenticate as a user with an account on the server. The following example code will read server and user information from a configuration file.

Warning

You probably do not want to store email passwords in clear text, but handling encryption will distract from the rest of the examples.

import imaplib
import ConfigParser
import os

def open_connection(verbose=False):
    # Read the config file
    config = ConfigParser.ConfigParser()
    config.read([os.path.expanduser('~/.pymotw')])

    # Connect to the server
    hostname = config.get('server', 'hostname')
    if verbose: print 'Connecting to', hostname
    connection = imaplib.IMAP4_SSL(hostname)

    # Login to our account
    username = config.get('account', 'username')
    password = config.get('account', 'password')
    if verbose: print 'Logging in as', username
    connection.login(username, password)
    return connection

if __name__ == '__main__':
    c = open_connection(verbose=True)
    try:
        print c
    finally:
        c.logout()

When run, open_connection() reads the configuration information from a file in your home directory, then opens the IMAP4_SSL connection and authenticates.

$ python imaplib_connect.py
Connecting to mail.example.com
Logging in as example
<imaplib.IMAP4_SSL instance at 0x928cb0>

Note

The other examples below will reuse this module, to avoid duplicating the code.

Authentication Failure

If the connection is established but authentication fails, an exception is raised.

import imaplib
import ConfigParser
import os

# Read the config file
config = ConfigParser.ConfigParser()
config.read([os.path.expanduser('~/.pymotw')])

# Connect to the server
hostname = config.get('server', 'hostname')
print 'Connecting to', hostname
connection = imaplib.IMAP4_SSL(hostname)

# Login to our account
username = config.get('account', 'username')
password = 'this_is_the_wrong_password'
print 'Logging in as', username
connection.login(username, password)
$ python imaplib_connect_fail.py
Connecting to mail.example.com
Logging in as example
Traceback (most recent call last):
  File "/Users/dhellmann/Documents/PyMOTW/in_progress/imaplib/PyMOTW/imaplib/imaplib_connect_fail.py", line 29, in <module>
    connection.login(username, password)
  File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/imaplib.py", line 501, in login
    raise self.error(dat[-1])
imaplib.error: Authentication failed.

Example Configuration

The example account has 4 mailboxes, INBOX, Apple Mail To Do, Archive, and 2008 (a sub-folder of Archive). The mailbox hierarchy looks like:

  • INBOX
  • Apple Mail To Do
  • Archive
    • 2008

There is one unread message in the INBOX folder, and one read message in Archive/2008.

Listing Mailboxes

To retrieve the mailboxes available for an account, use the list() method.

import imaplib
from pprint import pprint
from imaplib_connect import open_connection

c = open_connection()
try:
    typ, data = c.list()
    print 'Response code:', typ
    print 'Response:'
    pprint(data)
finally:
    c.logout()

The return value is a tuple with a response code and the data returned by the server. The response code is OK, unless there has been an error. The data for list() is a sequence of strings containing flags, the hierarchy delimiter, and mailbox name for each mailbox.

$ python imaplib_list.py
Response code: OK
Response:
['(\\HasNoChildren) "." INBOX',
 '(\\HasNoChildren) "." "Apple Mail To Do"',
 '(\\HasChildren) "." "Archive"',
 '(\\HasNoChildren) "." "Archive.2008"']

Each response string can be split into 3 parts using re or csv (see IMAP Backup Script for an example using csv).

import imaplib
import re

from imaplib_connect import open_connection

list_response_pattern = re.compile(r'\((?P<flags>.*?)\) "(?P<delimiter>.*)" (?P<name>.*)')

def parse_list_response(line):
    flags, delimiter, mailbox_name = list_response_pattern.match(line).groups()
    mailbox_name = mailbox_name.strip('"')
    return (flags, delimiter, mailbox_name)

if __name__ == '__main__':
    c = open_connection()
    try:
        typ, data = c.list()
    finally:
        c.logout()
    print 'Response code:', typ

    for line in data:
        print 'Server response:', line
        flags, delimiter, mailbox_name = parse_list_response(line)
        print 'Parsed response:', (flags, delimiter, mailbox_name)

Notice that the server quotes the mailbox name if it includes spaces, but we need to strip those quotes to use the mailbox name in other calls back to the server later.

$ python imaplib_list_parse.py
Response code: OK
Server response: (\HasNoChildren) "." INBOX
Parsed response: ('\\HasNoChildren', '.', 'INBOX')
Server response: (\HasNoChildren) "." "Apple Mail To Do"
Parsed response: ('\\HasNoChildren', '.', 'Apple Mail To Do')
Server response: (\HasChildren) "." "Archive"
Parsed response: ('\\HasChildren', '.', 'Archive')
Server response: (\HasNoChildren) "." "Archive.2008"
Parsed response: ('\\HasNoChildren', '.', 'Archive.2008')

list() takes arguments to let you ask for mailboxes in part of the hierarchy. For example, to list sub-folders of Archive, you can pass a value as the directory argument:

import imaplib

from imaplib_connect import open_connection

if __name__ == '__main__':
    c = open_connection()
    try:
        typ, data = c.list(directory='Archive')
    finally:
        c.logout()
    print 'Response code:', typ

    for line in data:
        print 'Server response:', line

Only the single subfolder is returned:

$ python imaplib_list_subfolders.py
Response code: OK
Server response: (\HasNoChildren) "." "Archive.2008"

Alternately, to list folders matching a pattern you can pass the pattern argument:

import imaplib

from imaplib_connect import open_connection

if __name__ == '__main__':
    c = open_connection()
    try:
        typ, data = c.list(pattern='*Archive*')
    finally:
        c.logout()
    print 'Response code:', typ

    for line in data:
        print 'Server response:', line

In this case, both Archive and Archive.2008 are included in the response.

$ python imaplib_list_pattern.py
Response code: OK
Server response: (\HasChildren) "." "Archive"
Server response: (\HasNoChildren) "." "Archive.2008"

Mailbox Status

Use status() to ask for aggregated information about the contents. The standard defines these status conditions:

MESSAGES
The number of messages in the mailbox.
RECENT
The number of messages with the Recent flag set.
UIDNEXT
The next unique identifier value of the mailbox.
UIDVALIDITY
The unique identifier validity value of the mailbox.
UNSEEN
The number of messages which do not have the Seen flag set.

The status conditions must be formatted as a space separated string enclosed in parentheses, the encoding for a “list” in the IMAP4 specification.

import imaplib
import re

from imaplib_connect import open_connection
from imaplib_list_parse import parse_list_response

if __name__ == '__main__':
    c = open_connection()
    try:
        typ, data = c.list()
        for line in data:
            flags, delimiter, mailbox_name = parse_list_response(line)
            print c.status(mailbox_name, '(MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)')
    finally:
        c.logout()
        

The return value is the usual tuple containing a response code and a list of information from the server. In this case, the list contains a single string formatted with the name of the mailbox in quotes, then the status conditions and values in parentheses.

$ python imaplib_status.py
('OK', ['"INBOX" (MESSAGES 1 RECENT 0 UIDNEXT 3 UIDVALIDITY 1222003700 UNSEEN 1)'])
('OK', ['"Apple Mail To Do" (MESSAGES 0 RECENT 0 UIDNEXT 1 UIDVALIDITY 1222003706 UNSEEN 0)'])
('OK', ['"Archive" (MESSAGES 0 RECENT 0 UIDNEXT 1 UIDVALIDITY 1222003809 UNSEEN 0)'])
('OK', ['"Archive.2008" (MESSAGES 1 RECENT 0 UIDNEXT 2 UIDVALIDITY 1222003831 UNSEEN 0)'])

Selecting a Mailbox

The basic mode of operation, once the client is authenticated, is to select a mailbox and then interrogate the server regarding messages in the mailbox. The connection is stateful, so once a mailbox is selected all commands operate on messages in that mailbox until a new mailbox is selected.

import imaplib
import imaplib_connect

c = imaplib_connect.open_connection()
try:
    typ, data = c.select('INBOX')
    print typ, data
    num_msgs = int(data[0])
    print 'There are %d messages in INBOX' % num_msgs
finally:
    c.close()
    c.logout()

The response data contains the total number of messages in the mailbox.

$ python imaplib_select.py
OK ['1']
There are 1 messages in INBOX

If an invalid mailbox is specified, the response code is NO.

import imaplib
import imaplib_connect

c = imaplib_connect.open_connection()
try:
    typ, data = c.select('Does Not Exist')
    print typ, data
finally:
    c.logout()

The data contains an error message describing the problem.

$ python imaplib_select_invalid.py
NO ["Mailbox doesn't exist: Does Not Exist"]

Searching for Messages

Once the mailbox is selected, use search() to retrieve the ids of messages in the mailbox.

import imaplib
import imaplib_connect
from imaplib_list_parse import parse_list_response

c = imaplib_connect.open_connection()
try:
    typ, mailbox_data = c.list()
    for line in mailbox_data:
        flags, delimiter, mailbox_name = parse_list_response(line)
        c.select(mailbox_name, readonly=True)
        typ, msg_ids = c.search(None, 'ALL')
        print mailbox_name, typ, msg_ids        
finally:
    try:
        c.close()
    except:
        pass
    c.logout()

Message ids are assigned by the server, and are implementation dependent. The IMAP4 protocol makes a distinction between sequential ids for messages at a given point in time during a transaction and UID identifiers for messages, but not all servers seem to bother.

$ python imaplib_search_all.py
INBOX OK ['1']
Apple Mail To Do OK ['']
Archive OK ['']
Archive.2008 OK ['1']

In this case, INBOX and Archive.2008 each have a diffrerent message with id 1. The other mailboxes are empty.

Search Criteria

A variety of other search criteria can be used, including looking at dates for the message, flags, and other headers. Refer to section 6.4.4. of RFC 3501 for complete details.

As one example, to look for messages with 'test message 2' in the subject, the search criteria could be constructed as:

(SUBJECT "test message 2")

This example finds all messages with the title “test message 2” in all mailboxes:

import imaplib
import imaplib_connect
from imaplib_list_parse import parse_list_response

c = imaplib_connect.open_connection()
try:
    typ, mailbox_data = c.list()
    for line in mailbox_data:
        flags, delimiter, mailbox_name = parse_list_response(line)
        c.select(mailbox_name, readonly=True)
        typ, msg_ids = c.search(None, '(SUBJECT "test message 2")')
        print mailbox_name, typ, msg_ids        
finally:
    try:
        c.close()
    except:
        pass
    c.logout()

There is only one such message in the account, and it is in the INBOX.

$ python imaplib_search_subject.py
INBOX OK ['1']
Apple Mail To Do OK ['']
Archive OK ['']
Archive.2008 OK ['']

Search criteria can also be combined.

import imaplib
import imaplib_connect
from imaplib_list_parse import parse_list_response

c = imaplib_connect.open_connection()
try:
    typ, mailbox_data = c.list()
    for line in mailbox_data:
        flags, delimiter, mailbox_name = parse_list_response(line)
        c.select(mailbox_name, readonly=True)
        typ, msg_ids = c.search(None, '(FROM "Doug" SUBJECT "test message 2")')
        print mailbox_name, typ, msg_ids        
finally:
    try:
        c.close()
    except:
        pass
    c.logout()

The combination is treated as a logical and operation.

$ python imaplib_search_from.py
INBOX OK ['1']
Apple Mail To Do OK ['']
Archive OK ['']
Archive.2008 OK ['']

Fetching Messages

The identifiers returned by search() are used to retrieve the contents, or partial contents, of messages for further processing via fetch(). fetch() takes 2 arguments, the message ids to fetch and the portion(s) of the message to retrieve.

The message_ids argument is a comma separated list of ids ("1", "1,2") or id ranges (1:2). The message_parts argument is an IMAP list of message segment names. As with search criteria for search(), the IMAP protocol specifies named message segments so clients can efficiently retrieve only the parts of the message they actually need. For example, to print the headers of the messages in a mailbox, we could fetch() the headers using BODY.PEEK[HEADER].

Note

Another way to fetch the headers would be simply BODY[HEADERS], but that form implicitly marks the message as read, which is undesirable in many cases.

import imaplib
import pprint
import imaplib_connect

imaplib.Debug = 4
c = imaplib_connect.open_connection()
try:
    c.select('INBOX', readonly=True)
    typ, msg_data = c.fetch('1', '(BODY.PEEK[HEADER] FLAGS)')
    pprint.pprint(msg_data)
finally:
    try:
        c.close()
    except:
        pass
    c.logout()

The return value of fetch() has been partially parsed so it is somewhat harder to work with than the return value of list(). If we turn on debugging, we can see the complete interaction between the client and server to understand why this is so.

$ python imaplib_fetch_raw.py
  13:12.54 imaplib version 2.58
  13:12.54 new IMAP4 connection, tag=CFKH
  13:12.54 < * OK dovecot ready.
  13:12.54 > CFKH0 CAPABILITY
  13:12.54 < * CAPABILITY IMAP4rev1 SORT THREAD=REFERENCES MULTIAPPEND UNSELECT IDLE CHILDREN LISTEXT LIST-SUBSCRIBED NAMESPACE AUTH=PLAIN
  13:12.54 < CFKH0 OK Capability completed.
  13:12.54 CAPABILITIES: ('IMAP4REV1', 'SORT', 'THREAD=REFERENCES', 'MULTIAPPEND', 'UNSELECT', 'IDLE', 'CHILDREN', 'LISTEXT', 'LIST-SUBSCRIBED', 'NAMESPACE', 'AUTH=PLAIN')
  13:12.54 > CFKH1 LOGIN example "password"
  13:13.18 < CFKH1 OK Logged in.
  13:13.18 > CFKH2 EXAMINE INBOX
  13:13.20 < * FLAGS (\Answered \Flagged \Deleted \Seen \Draft $NotJunk $Junk)
  13:13.20 < * OK [PERMANENTFLAGS ()] Read-only mailbox.
  13:13.20 < * 2 EXISTS
  13:13.20 < * 1 RECENT
  13:13.20 < * OK [UNSEEN 1] First unseen.
  13:13.20 < * OK [UIDVALIDITY 1222003700] UIDs valid
  13:13.20 < * OK [UIDNEXT 4] Predicted next UID
  13:13.20 < CFKH2 OK [READ-ONLY] Select completed.
  13:13.20 > CFKH3 FETCH 1 (BODY.PEEK[HEADER] FLAGS)
  13:13.20 < * 1 FETCH (FLAGS ($NotJunk) BODY[HEADER] {595}
  13:13.20 read literal size 595
  13:13.20 < )
  13:13.20 < CFKH3 OK Fetch completed.
  13:13.20 > CFKH4 CLOSE
  13:13.21 < CFKH4 OK Close completed.
  13:13.21 > CFKH5 LOGOUT
  13:13.21 < * BYE Logging out
  13:13.21 BYE response: Logging out
  13:13.21 < CFKH5 OK Logout completed.
[('1 (FLAGS ($NotJunk) BODY[HEADER] {595}',
  'Return-Path: <dhellmann@example.com>\r\nReceived: from example.com (localhost [127.0.0.1])\r\n\tby example.com (8.13.4/8.13.4) with ESMTP id m8LDTGW4018260\r\n\tfor <example@example.com>; Sun, 21 Sep 2008 09:29:16 -0400\r\nReceived: (from dhellmann@localhost)\r\n\tby example.com (8.13.4/8.13.4/Submit) id m8LDTGZ5018259\r\n\tfor example@example.com; Sun, 21 Sep 2008 09:29:16 -0400\r\nDate: Sun, 21 Sep 2008 09:29:16 -0400\r\nFrom: Doug Hellmann <dhellmann@example.com>\r\nMessage-Id: <200809211329.m8LDTGZ5018259@example.com>\r\nTo: example@example.com\r\nSubject: test message 2\r\n\r\n'),
 ')']

The response from the FETCH command starts with the flags, then indicates that there are 595 bytes of header data. The client contructs a tuple with the response for the message, and then closes the sequence with a single string containing the ) the server sends at the end of the fetch response. Because of this formatting, it may be easier to fetch different pieces of information separately, or to recombine the response and parse it yourself.

import imaplib
import pprint
import imaplib_connect

c = imaplib_connect.open_connection()
try:
    c.select('INBOX', readonly=True)
    
    print 'HEADER:'
    typ, msg_data = c.fetch('1', '(BODY.PEEK[HEADER])')
    for response_part in msg_data:
        if isinstance(response_part, tuple):
            print response_part[1]
    
    print 'BODY TEXT:'
    typ, msg_data = c.fetch('1', '(BODY.PEEK[TEXT])')
    for response_part in msg_data:
        if isinstance(response_part, tuple):
            print response_part[1]

    print '\nFLAGS:'
    typ, msg_data = c.fetch('1', '(FLAGS)')
    for response_part in msg_data:
        print response_part
        print imaplib.ParseFlags(response_part)
finally:
    try:
        c.close()
    except:
        pass
    c.logout()

Fetching values separately has the added benefit of making it easy to use ParseFlags() to parse the flags from the response.

$ python imaplib_fetch_separately.py
HEADER:
Return-Path: <dhellmann@example.com>
Received: from example.com (localhost [127.0.0.1])
    by example.com (8.13.4/8.13.4) with ESMTP id m8LDTGW4018260
    for <example@example.com>; Sun, 21 Sep 2008 09:29:16 -0400
Received: (from dhellmann@localhost)
    by example.com (8.13.4/8.13.4/Submit) id m8LDTGZ5018259
    for example@example.com; Sun, 21 Sep 2008 09:29:16 -0400
Date: Sun, 21 Sep 2008 09:29:16 -0400
From: Doug Hellmann <dhellmann@example.com>
Message-Id: <200809211329.m8LDTGZ5018259@example.com>
To: example@example.com
Subject: test message 2


BODY TEXT:
second message


FLAGS:
1 (FLAGS ($NotJunk))
('$NotJunk',)

Whole Messages

As illustrated above, the client can ask the server for individual parts of the message separately. It is also possible to retrieve the entire message as an RFC 2822 formatted mail message and parse it with classes from the email module.

import imaplib
import email
import imaplib_connect

c = imaplib_connect.open_connection()
try:
    c.select('INBOX', readonly=True)
    
    typ, msg_data = c.fetch('1', '(RFC822)')
    for response_part in msg_data:
        if isinstance(response_part, tuple):
            msg = email.message_from_string(response_part[1])
            for header in [ 'subject', 'to', 'from' ]:
                print '%-8s: %s' % (header.upper(), msg[header])

finally:
    try:
        c.close()
    except:
        pass
    c.logout()

The parser in the email module make it very easy to access and manipulate messages. This example prints just a few of the headers for each message.

$ python imaplib_fetch_rfc822.py
SUBJECT : test message 2
TO      : example@example.com
FROM    : Doug Hellmann <dhellmann@example.com>

Uploading Messages

To add a new message to a mailbox, pass it to the append() method.

import imaplib
import time
import email.message
import imaplib_connect

new_message = email.message.Message()
new_message.set_unixfrom('pymotw')
new_message['Subject'] = 'subject goes here'
new_message['From'] = 'pymotw@example.com'
new_message['To'] = 'example@example.com'
new_message.set_payload('This is the body of the message.\n')

print new_message

c = imaplib_connect.open_connection()
try:
    c.append('INBOX', '', imaplib.Time2Internaldate(time.time()), str(new_message))
    
    c.select('INBOX')
    typ, [msg_ids] = c.search(None, 'ALL')
    for num in msg_ids.split():
        typ, msg_data = c.fetch(num, '(BODY.PEEK[HEADER])')
        for response_part in msg_data:
            if isinstance(response_part, tuple):
                print '\n%s:' % num
                print response_part[1]
        
finally:
    try:
        c.close()
    except:
        pass
    c.logout()
pymotw
Subject: subject goes here
From: pymotw@example.com
To: example@example.com

This is the body of the message.


1:
Return-Path: <dhellmann@example.com>
Received: from example.com (localhost [127.0.0.1])
    by example.com (8.13.4/8.13.4) with ESMTP id m8LDTGW4018260
    for <example@example.com>; Sun, 21 Sep 2008 09:29:16 -0400
Received: (from dhellmann@localhost)
    by example.com (8.13.4/8.13.4/Submit) id m8LDTGZ5018259
    for example@example.com; Sun, 21 Sep 2008 09:29:16 -0400
Date: Sun, 21 Sep 2008 09:29:16 -0400
From: Doug Hellmann <dhellmann@example.com>
Message-Id: <200809211329.m8LDTGZ5018259@example.com>
To: example@example.com
Subject: test message 2



2:
Return-Path: <doug.hellmann@example.com>
Message-Id: <0D9C3C50-462A-4FD7-9E5A-11EE222D721D@example.com>
From: Doug Hellmann <doug.hellmann@example.com>
To: example@example.com
Content-Type: text/plain; charset=US-ASCII; format=flowed; delsp=yes
Content-Transfer-Encoding: 7bit
Mime-Version: 1.0 (Apple Message framework v929.2)
Subject: lorem ipsum
Date: Sun, 21 Sep 2008 12:53:16 -0400
X-Mailer: Apple Mail (2.929.2)



3:
pymotw
Subject: subject goes here
From: pymotw@example.com
To: example@example.com

Moving and Copying Messages

Once a message is on the server, it can be moved or copied without downloading it using move() or copy(). These methods operate on message id ranges, just as fetch() does.

This example script creates a new mailbox under Archive and copies the read messages from INBOX into it.

import imaplib
import imaplib_connect

c = imaplib_connect.open_connection()
try:
    # Find the "SEEN" messages in INBOX
    c.select('INBOX')
    typ, [response] = c.search(None, 'SEEN')
    if typ != 'OK':
        raise RuntimeError(response)
    
    # Create a new mailbox, "Archive.Today"
    msg_ids = ','.join(response.split(' '))
    typ, create_response = c.create('Archive.Today')
    print 'CREATED Archive.Today:', create_response
    
    # Copy the messages
    print 'COPYING:', msg_ids
    c.copy(msg_ids, 'Archive.Today')
    
    # Look at the results
    c.select('Archive.Today')
    typ, [response] = c.search(None, 'ALL')
    print 'COPIED:', response
    
finally:
    c.close()
    c.logout()
    
$ python imaplib_archive_read.py
CREATED Archive.Today: ['Create completed.']
COPYING: 1,2
COPIED: 1 2

Running the same script again shows the importance to checking return codes. Instead of raising an exception, the call to create() to make the new mailbox reports that the mailbox already exists.

$ python imaplib_archive_read.py
CREATED Archive.Today: ['Mailbox exists.']
COPYING: 1,2
COPIED: 1 2 3 4

Deleting Messages

Although most modern mail clients use a “Trash folder” model for working with deleted messages, the messages are not usually moved into an actual folder. Instead, their flags are updated to add \Deleted. Emptying the trash is implemented through an EXPUNGE command. This example script finds the archived messages with “Lorem ipsum” in the subject, sets the deleted flag, then shows that the messages are still present in the folder by querying the server again.

import imaplib
import imaplib_connect
from imaplib_list_parse import parse_list_response

c = imaplib_connect.open_connection()
try:
    c.select('Archive.Today')

    # What ids are in the mailbox?
    typ, [msg_ids] = c.search(None, 'ALL')
    print 'Starting messages:', msg_ids
    
    # Find the message(s)
    typ, [msg_ids] = c.search(None, '(SUBJECT "Lorem ipsum")')
    msg_ids = ','.join(msg_ids.split(' '))
    print 'Matching messages:', msg_ids
    
    # What are the current flags?
    typ, response = c.fetch(msg_ids, '(FLAGS)')
    print 'Flags before:', response
    
    # Change the Deleted flag
    typ, response = c.store(msg_ids, '+FLAGS', r'(\Deleted)')
    
    # What are the flags now?
    typ, response = c.fetch(msg_ids, '(FLAGS)')
    print 'Flags after:', response
    
    # Really delete the message.
    typ, response = c.expunge()
    print 'Expunged:', response
    
    # What ids are left in the mailbox?
    typ, [msg_ids] = c.search(None, 'ALL')
    print 'Remaining messages:', msg_ids
    
finally:
    try:
        c.close()
    except:
        pass
    c.logout()

This example explicitly calls expunge() to remove the messages, but calling close() has the same effect. The difference is the client is not notified about the deletions when you call close().

$ python imaplib_delete_messages.py
Starting messages: 1 2 3 4
Matching messages: 1,3
Flags before: ['1 (FLAGS (\\Seen $NotJunk))', '3 (FLAGS (\\Seen \\Recent $NotJunk))']
Flags after: ['1 (FLAGS (\\Deleted \\Seen $NotJunk))',
'3 (FLAGS (\\Deleted \\Seen \\Recent $NotJunk))']
Expunged: ['1', '2']
Remaining messages: 1 2

See also

imaplib
The standard library documentation for this module.
What is IMAP?
imap.org description of the IMAP protocol
University of Washington IMAP Information Center
Good resource for IMAP information, along with source code.
RFC 3501
Internet Message Access Protocol
RFC 2822
Internet Message Format
IMAP Backup Script
A script to backup email from an IMAP server.
rfc822
The rfc822 module includes an RFC 822 / RFC 2822 parser
email
The email module for parsing email messages.
mailbox
Local mailbox parser.
ConfigParser
Read and write configuration files.
IMAPClient
A higher-level client for talking to IMAP servers, written by Menno Smits.