Of course! Using Python's built-in imaplib library is the standard way to interact with IMAP (Internet Message Access Protocol) email servers. It allows you to programmatically check, search, fetch, and manage emails in your inbox.

This guide will walk you through the process step-by-step, from connecting to the server to fetching and parsing emails.
What is IMAP?
First, a quick primer:
- IMAP (Internet Message Access Protocol) is a protocol for accessing email on a remote server.
- Unlike POP3 (which downloads emails to your local machine and often deletes them from the server), IMAP keeps emails on the server and allows you to manage them (read, delete, move, flag) from multiple devices.
- This makes it ideal for Python scripts that need to interact with a live mailbox.
Step 1: The Essentials - Connecting and Logging In
Before you can do anything, you need to establish a secure connection to your email provider's IMAP server and authenticate.
Important: Always use SSL/TLS to secure your connection. Most providers now require this.

Common IMAP Server Ports:
- SSL/TLS (Recommended): Port
993 - STARTTLS (Less common now): Port
143
Here's a basic connection and login template:
import imaplib
import email
from email.header import decode_header
# --- Configuration ---
# Replace with your email provider's IMAP server
IMAP_SERVER = "imap.gmail.com" # Example for Gmail
IMAP_PORT = 993
# Replace with your credentials
# For Gmail, you might need to use an "App Password" if 2FA is enabled
EMAIL_ACCOUNT = "your_email@gmail.com"
EMAIL_PASSWORD = "your_app_password_or_real_password"
def connect_to_mailbox():
"""Connects to the IMAP server and logs in."""
try:
# Create an IMAP4_SSL object to connect securely
mail = imaplib.IMAP4_SSL(IMAP_SERVER, IMAP_PORT)
# Login to your account
mail.login(EMAIL_ACCOUNT, EMAIL_PASSWORD)
print("Successfully logged in!")
# Select the mailbox you want to work with (e.g., 'INBOX')
mail.select('inbox')
return mail
except imaplib.IMAP4.error as e:
print(f"IMAP Error: {e}")
return None
except Exception as e:
print(f"An unexpected error occurred: {e}")
return None
# --- Usage ---
if __name__ == "__main__":
my_mail = connect_to_mailbox()
if my_mail:
# Don't forget to logout when you're done!
my_mail.logout()
Step 2: Searching for Emails
IMAP has a powerful search mechanism. You can search by criteria like sender, subject, date, and whether an email has been read.
The mail.search(None, 'criteria') method is used.
- The first argument is usually
NoneorimaplibCharset. - The second argument is the search criteria.
Common Search Criteria:
'UNSEEN': Unread emails.'SEEN': Read emails.'FROM "sender@example.com"': From a specific sender.'SUBJECT "your keyword"': Emails with a specific keyword in the subject.'BEFORE "01-Jan-2025"': Emails received before a certain date.'SINCE "01-Jan-2025"': Emails received after a certain date.'(UNSEEN FROM "boss@company.com")': Combining criteria (AND logic).
Note: The search returns a byte string of email UIDs, separated by spaces.

def search_emails(mail, search_criteria):
"""Searches for emails based on the given criteria."""
status, data = mail.search(None, search_criteria)
if status != 'OK':
print("Email search failed.")
return []
# data[0] is a byte string of space-separated UIDs
# We split it to get a list of UIDs
email_ids = data[0].split()
print(f"Found {len(email_ids)} emails matching the criteria.")
return email_ids
# --- Usage ---
if __name__ == "__main__":
my_mail = connect_to_mailbox()
if my_mail:
# Find all unread emails
unread_ids = search_emails(my_mail, 'UNSEEN')
# Find emails from a specific sender
# sender_ids = search_emails(my_mail, 'FROM "news@python.org"')
my_mail.logout()
Step 3: Fetching Email Content
Once you have a list of email UIDs, you can fetch the content of a specific email using mail.fetch(uid, '(RFC822)').
'RFC822'is a standard that fetches the entire raw email message.- The result is a complex structure, but the email body is usually in
data[0][1].
def fetch_email_by_id(mail, email_id):
"""Fetches and parses a single email by its ID."""
status, data = mail.fetch(email_id, '(RFC822)')
if status != 'OK':
print(f"Failed to fetch email {email_id}.")
return None
# The raw email content is in data[0][1]
raw_email = data[0][1]
# Parse the raw email string into an email message object
email_message = email.message_from_bytes(raw_email)
return email_message
# --- Usage ---
if __name__ == "__main__":
my_mail = connect_to_mailbox()
if my_mail:
unread_ids = search_emails(my_mail, 'UNSEEN')
if unread_ids:
# Fetch the first unread email
first_email_id = unread_ids[0]
email_msg = fetch_email_by_id(my_mail, first_email_id)
if email_msg:
print("\n--- Email Fetched ---")
print(f"From: {get_email_header(email_msg, 'From')}")
print(f"Subject: {get_email_header(email_msg, 'Subject')}")
print(f"Date: {get_email_header(email_msg, 'Date')}")
print("-" * 20)
# Get the body
body = get_email_body(email_msg)
print("Body:")
print(body[:500] + "...") # Print first 500 chars
my_mail.logout()
Step 4: Parsing the Email (The Fun Part)
An email can have a complex structure with multiple parts (plain text, HTML, attachments). The email library is essential here.
Helper Functions for Parsing
Here are some robust helper functions to handle common tasks like decoding headers and extracting the body.
def get_email_header(msg, header):
"""Decodes and returns a specific email header."""
value = msg.get(header, '')
decoded_value = decode_header(value)[0][0]
if isinstance(decoded_value, bytes):
# Decode bytes to string using UTF-8, with a fallback
return decoded_value.decode('utf-8', errors='ignore')
return decoded_value
def get_email_body(msg):
"""Extracts the body text from an email message object."""
if msg.is_multipart():
# If it's a multipart email, iterate through its parts
for part in msg.walk():
content_type = part.get_content_type()
content_disposition = str(part.get("Content-Disposition"))
# Find the plain text part
if content_type == "text/plain" and "attachment" not in content_disposition:
try:
body = part.get_payload(decode=True).decode()
return body
except:
continue
else:
# If it's not multipart, just get the payload
try:
body = msg.get_payload(decode=True).decode()
return body
except:
pass
return "Could not decode email body."
def get_email_attachments(msg):
"""Finds and returns a list of attachment filenames."""
attachments = []
for part in msg.walk():
content_disposition = str(part.get("Content-Disposition"))
if "attachment" in content_disposition:
filename = part.get_filename()
if filename:
attachments.append(filename)
return attachments
Complete Example: Find and Read an Email
Here is a full script that combines all the steps to find an email from a specific sender, print its subject, and save its attachments.
import imaplib
import email
from email.header import decode_header
import os
# --- Configuration ---
IMAP_SERVER = "imap.gmail.com"
IMAP_PORT = 993
EMAIL_ACCOUNT = "your_email@gmail.com"
EMAIL_PASSWORD = "your_app_password"
SEARCH_SENDER = "news@python.org" # Example: Python.org newsletter
SAVE_ATTACHMENTS_TO = "attachments"
# --- Helper Functions (from above) ---
def get_email_header(msg, header):
value = msg.get(header, '')
decoded_value = decode_header(value)[0][0]
return decoded_value.decode('utf-8', errors='ignore') if isinstance(decoded_value, bytes) else decoded_value
def get_email_body(msg):
if msg.is_multipart():
for part in msg.walk(): 