Of course! imapclient is a fantastic and user-friendly Python library for interacting with IMAP (Internet Message Access Protocol) email servers. It's a wrapper around the standard library's imaplib, but it provides a much cleaner, more intuitive, and Pythonic API.
Here's a comprehensive guide covering everything from installation to practical examples.
What is IMAP and Why Use imapclient?
- IMAP (Internet Message Access Protocol) is a standard protocol for accessing email from a mail server. Unlike POP3, which downloads emails and deletes them from the server, IMAP allows you to manage emails directly on the server. You can read, flag, move, and delete emails, and these changes will be synced across all your devices.
- Python's Standard Library (
imaplib) is powerful but very low-level. It works with raw bytes and string literals, making it verbose and error-prone. imapclientsimplifies this immensely. It handles the complexities of the IMAP protocol, manages connections, and provides easy-to-use methods for common tasks. It automatically handles encoding and decoding, so you mostly work with standard Python strings and data types.
Installation
First, you need to install the library. It's available on PyPI.
pip install imapclient
You might also want to install the pyzmail library, which imapclient can use to help parse email messages into a more readable format.
pip install pyzmail
Basic Workflow
Using imapclient typically follows these steps:
- Connect to the IMAP server.
- Log in with your credentials.
- Select a folder (e.g., 'INBOX').
- Search for emails.
- Fetch the emails you want.
- Process the email data.
- Log out and disconnect.
Core Examples
Let's walk through the most common tasks. We'll use a placeholder email and password. Never hard-code credentials in your final application. Use environment variables or a configuration file.
import imapclient
import email
import getpass # For secure password input
# --- 1. Connection and Login ---
# Replace with your IMAP server details
# Common providers:
# Gmail: 'imap.gmail.com'
# Outlook: 'outlook.office365.com'
# Yahoo: 'imap.mail.yahoo.com'
IMAP_SERVER = 'imap.gmail.com'
EMAIL_ACCOUNT = 'your_email@gmail.com'
PASSWORD = getpass.getpass("Enter your password: ")
# Use 'with' statement for automatic cleanup (logout and disconnect)
try:
with imapclient.IMAPClient(IMAP_SERVER, ssl=True) as client:
print(f"Successfully connected to {IMAP_SERVER}")
# Login
client.login(EMAIL_ACCOUNT, PASSWORD)
print("Login successful.")
# --- 2. Select a Folder ---
# List all available folders
folders = client.list_folders()
print("\nAvailable folders:")
for folder in folders:
print(f" - {folder.decode()}")
# Select the 'INBOX'
client.select_folder('INBOX')
print("\nSelected INBOX.")
# --- 3. Search for Emails ---
# Search for all emails in the inbox
# The search criteria uses IMAP's own syntax (see RFC 3501)
# 'ALL' is a common criterion
messages = client.search(['ALL'])
print(f"\nFound {len(messages)} emails in the INBOX.")
if not messages:
print("No emails found. Exiting.")
exit()
# --- 4. Fetch Email Data ---
# Fetch data for the first 5 emails found
# We'll fetch the UID, flags, and envelope (subject, from, to)
# We use a slice to get only the first 5 UIDs
first_five_uids = messages[:5]
# The 'fetch' method takes a list of UIDs and a list of data parts to fetch
# 'BODY.PEEK[HEADER]' fetches just the header without marking the email as read
# 'FLAGS' fetches the email's flags (e.g., Seen, Answered)
response = client.fetch(first_five_uids, ['BODY.PEEK[HEADER]', 'FLAGS'])
# --- 5. Process the Fetched Data ---
print("\n--- Processing First 5 Emails ---")
for uid, data in response.items():
# The 'BODY[HEADER]' part contains the raw header bytes
header_bytes = data[b'BODY[HEADER]']
# Parse the header into a message object
msg = email.message_from_bytes(header_bytes)
# Extract common fields
subject = msg.get('Subject', 'No Subject')
from_ = msg.get('From', 'Unknown Sender')
date = msg.get('Date', 'No Date')
# Decode subject and from_ (they might be encoded)
# A simple way to decode is to use the email library's policy
subject = email.header.decode_header(subject)[0][0]
if isinstance(subject, bytes):
subject = subject.decode('utf-8', errors='ignore')
from_ = email.header.decode_header(from_)[0][0]
if isinstance(from_, bytes):
from_ = from_.decode('utf-8', errors='ignore')
print(f"\nUID: {uid}")
print(f" From: {from_}")
print(f" Subject: {subject}")
print(f" Date: {date}")
print(f" Flags: {data[b'FLAGS']}")
# --- Example: Marking an email as read ---
print("\n--- Marking an email as read ---")
first_email_uid = messages[0]
# Add the '\\Seen' flag to the email
client.add_flags([first_email_uid], [b'\\Seen'])
print(f"Marked email UID {first_email_uid} as read.")
# --- Example: Deleting an email ---
# print("\n--- Deleting an email ---")
# last_email_uid = messages[-1]
# client.add_flags([last_email_uid], [b'\\Deleted'])
# print(f"Marked email UID {last_email_uid} for deletion.")
# # The email is not actually deleted until you 'expunge'
# client.expunge()
# print("Expunged deleted emails.")
except imapclient.exceptions.IMAPClientError as e:
print(f"An IMAP error occurred: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
Key imapclient Features and Methods
Here are some of the most useful methods and concepts:
| Method/Feature | Description | Example |
|---|---|---|
select_folder(folder_name) |
Selects a mailbox (folder) to work on. | client.select_folder('INBOX') |
search(criteria) |
Searches for emails matching criteria. Returns a list of UIDs. | client.search(['FROM', 'sender@example.com'])client.search(['SINCE', '01-Jan-2025'])client.search(['UNSEEN']) |
fetch(uids, data_parts) |
Fetches data for specific emails. UIDs can be a single UID or a list. | client.fetch(123, ['BODY.PEEK[HEADER]'])client.fetch([123, 124, 125], ['ENVELOPE', 'FLAGS']) |
get_flags(uids) |
A shortcut to get only the flags for emails. | flags = client.get_flags([123, 124]) |
add_flags(uids, flags) |
Adds flags to emails. | client.add_flags([123], [b'\\Seen', b'\\Flagged']) |
remove_flags(uids, flags) |
Removes flags from emails. | client.remove_flags([123], [b'\\Seen']) |
delete_messages(uids) |
A shortcut to add the \\Deleted flag. |
client.delete_messages([123, 124]) |
expunge() |
Permanently removes all emails marked with \\Deleted. |
client.expunge() |
copy(uids, dest_folder) |
Copies emails to another folder. | client.copy([123], 'Archive') |
move(uids, dest_folder) |
Moves emails to another folder (combines copy + delete). | client.move([123], 'Archive') |
folder_status(folder, data) |
Gets status information for a folder. | status = client.folder_status('INBOX', ['MESSAGES', 'UNSEEN'])print(f"You have {status[b'MESSAGES']} total messages.") |
idle() |
Puts the connection into IDLE mode to wait for server push notifications. | client.idle()# ... wait for new mail ...responses = client.idle_done() |
Searching Emails (The criteria)
The search() method is powerful. The criteria are based on the IMAP protocol. Here are the most common ones:
ALL: All messages in the folder.ANSWERED: Messages with the\Answeredflag set.DELETED: Messages with the\Deletedflag set.DRAFT: Messages with the\Draftflag set.FLAGGED: Messages with the\Flaggedflag set.NEW: Messages that are both unseen and not flagged as\Recent.OLD: Messages that are not new.RECENT: Messages with the\Recentflag set.SEEN: Messages with the\Seenflag set.UNSEEN: Messages that do not have the\Seenflag set.SUBJECT "string": Messages with "string" in the subject.FROM "string": Messages with "string" in the sender.TO "string": Messages with "string" in the "To" header.CC "string": Messages with "string" in the "Cc" header.BCC "string": Messages with "string" in the "Bcc" header.SINCE "date": Messages sent since the given date. Date format:DD-MMM-YYYY(e.g.,01-Jan-2025).BEFORE "date": Messages sent before the given date.ON "date": Messages sent on the given date.UID uid_set: Messages with UIDs matching the set.
You can combine criteria using lists. imapclient will correctly format them for the IMAP server.
# Find all unseen emails from a specific sender since January 1st, 2025 criteria = ['UNSEEN', 'FROM', 'boss@mycompany.com', 'SINCE', '01-Jan-2025'] unseen_emails_from_boss = client.search(criteria)
Fetching Email Body Content
Fetching the body is a bit more involved because emails can be multipart (with both HTML and plain text versions) and attachments.
Here’s how to get the plain text body of an email:
# Assume 'client' is logged in and 'uid' is the email UID
# We use BODY.PEEK[] to avoid marking the email as read
# [] means the entire body
response = client.fetch(uid, ['BODY.PEEK[]'])
# The raw email content is in the response
raw_email_bytes = response[uid][b'BODY[]']
# Parse the raw email content
msg = email.message_from_bytes(raw_email_bytes)
def get_email_body(message):
if message.is_multipart():
# If it's multipart, iterate through the parts
for part in message.walk():
content_type = part.get_content_type()
if content_type == "text/plain":
# Found the plain text part
charset = part.get_content_charset() or 'utf-8'
return part.get_payload(decode=True).decode(charset, errors='ignore')
else:
# If it's not multipart, it's just the body
charset = message.get_content_charset() or 'utf-8'
return message.get_payload(decode=True).decode(charset, errors='ignore')
body = get_email_body(msg)
print("\n--- Email Body ---")
print(body)
This example handles both simple emails and complex multipart emails, finding the first text/plain part it encounters.
