Python's imaplib module lets you connect to IMAP servers to read, search, and manage email. Unlike POP3, IMAP keeps emails on the server and supports folders.

Basic Connection

import imaplib
 
# Connect with SSL
mail = imaplib.IMAP4_SSL('imap.gmail.com')
mail.login('you@gmail.com', 'app-password')
 
# List folders
status, folders = mail.list()
for folder in folders:
    print(folder.decode())
 
mail.logout()

Reading Inbox

import imaplib
import email
from email.header import decode_header
 
mail = imaplib.IMAP4_SSL('imap.gmail.com')
mail.login('you@gmail.com', 'app-password')
 
# Select inbox
mail.select('INBOX')
 
# Search for all emails
status, messages = mail.search(None, 'ALL')
message_ids = messages[0].split()
 
# Get latest 5 emails
for msg_id in message_ids[-5:]:
    status, data = mail.fetch(msg_id, '(RFC822)')
    raw_email = data[0][1]
    msg = email.message_from_bytes(raw_email)
    
    # Decode subject
    subject, encoding = decode_header(msg['Subject'])[0]
    if isinstance(subject, bytes):
        subject = subject.decode(encoding or 'utf-8')
    
    print(f"From: {msg['From']}")
    print(f"Subject: {subject}")
    print("---")
 
mail.logout()

Searching Emails

import imaplib
 
mail = imaplib.IMAP4_SSL('imap.gmail.com')
mail.login('you@gmail.com', 'app-password')
mail.select('INBOX')
 
# Search criteria
status, msgs = mail.search(None, 'UNSEEN')           # Unread
status, msgs = mail.search(None, 'FROM', '"sender@example.com"')
status, msgs = mail.search(None, 'SUBJECT', '"invoice"')
status, msgs = mail.search(None, 'SINCE', '"01-Jan-2026"')
status, msgs = mail.search(None, 'BEFORE', '"31-Dec-2026"')
 
# Combined search
status, msgs = mail.search(None, 'UNSEEN', 'FROM', '"boss@company.com"')
 
# All criteria
status, msgs = mail.search(None, '(OR FROM "alice" FROM "bob")')

Reading Email Body

import imaplib
import email
 
def get_email_body(msg):
    """Extract text body from email message."""
    if msg.is_multipart():
        for part in msg.walk():
            content_type = part.get_content_type()
            disposition = str(part.get('Content-Disposition'))
            
            if content_type == 'text/plain' and 'attachment' not in disposition:
                return part.get_payload(decode=True).decode()
    else:
        return msg.get_payload(decode=True).decode()
    return None
 
mail = imaplib.IMAP4_SSL('imap.gmail.com')
mail.login('you@gmail.com', 'app-password')
mail.select('INBOX')
 
status, messages = mail.search(None, 'ALL')
latest_id = messages[0].split()[-1]
 
status, data = mail.fetch(latest_id, '(RFC822)')
msg = email.message_from_bytes(data[0][1])
 
body = get_email_body(msg)
print(body)

Downloading Attachments

import imaplib
import email
import os
 
def save_attachments(msg, download_folder='attachments'):
    os.makedirs(download_folder, exist_ok=True)
    
    for part in msg.walk():
        if part.get_content_maintype() == 'multipart':
            continue
        if part.get('Content-Disposition') is None:
            continue
        
        filename = part.get_filename()
        if filename:
            filepath = os.path.join(download_folder, filename)
            with open(filepath, 'wb') as f:
                f.write(part.get_payload(decode=True))
            print(f"Saved: {filepath}")
 
mail = imaplib.IMAP4_SSL('imap.gmail.com')
mail.login('you@gmail.com', 'app-password')
mail.select('INBOX')
 
status, data = mail.fetch(b'123', '(RFC822)')
msg = email.message_from_bytes(data[0][1])
save_attachments(msg)

Working with Folders

import imaplib
 
mail = imaplib.IMAP4_SSL('imap.gmail.com')
mail.login('you@gmail.com', 'app-password')
 
# List all folders
status, folders = mail.list()
for folder in folders:
    print(folder.decode())
 
# Select a folder
mail.select('INBOX')
mail.select('[Gmail]/Sent Mail')
mail.select('[Gmail]/Drafts')
 
# Create folder
mail.create('MyFolder')
 
# Delete folder
mail.delete('MyFolder')
 
# Rename folder
mail.rename('OldName', 'NewName')

Managing Emails

import imaplib
 
mail = imaplib.IMAP4_SSL('imap.gmail.com')
mail.login('you@gmail.com', 'app-password')
mail.select('INBOX')
 
# Mark as read
mail.store(b'123', '+FLAGS', '\\Seen')
 
# Mark as unread
mail.store(b'123', '-FLAGS', '\\Seen')
 
# Star/flag email
mail.store(b'123', '+FLAGS', '\\Flagged')
 
# Delete (move to trash)
mail.store(b'123', '+FLAGS', '\\Deleted')
mail.expunge()  # Permanently remove deleted
 
# Move to folder (copy + delete)
mail.copy(b'123', 'Archive')
mail.store(b'123', '+FLAGS', '\\Deleted')
mail.expunge()

Context Manager Pattern

import imaplib
from contextlib import contextmanager
 
@contextmanager
def imap_connection(host, user, password):
    mail = imaplib.IMAP4_SSL(host)
    try:
        mail.login(user, password)
        yield mail
    finally:
        try:
            mail.close()
            mail.logout()
        except:
            pass
 
# Usage
with imap_connection('imap.gmail.com', 'user', 'pass') as mail:
    mail.select('INBOX')
    status, messages = mail.search(None, 'UNSEEN')
    print(f"Unread: {len(messages[0].split())}")

Fetch Options

import imaplib
 
mail.select('INBOX')
 
# Just headers (faster)
status, data = mail.fetch(b'123', '(BODY[HEADER])')
 
# Just specific headers
status, data = mail.fetch(b'123', '(BODY[HEADER.FIELDS (FROM SUBJECT DATE)])')
 
# Full message
status, data = mail.fetch(b'123', '(RFC822)')
 
# Envelope data
status, data = mail.fetch(b'123', '(ENVELOPE)')
 
# Size only
status, data = mail.fetch(b'123', '(RFC822.SIZE)')

Gmail-Specific

import imaplib
 
mail = imaplib.IMAP4_SSL('imap.gmail.com')
mail.login('you@gmail.com', 'app-password')
 
# Gmail uses X-GM-RAW for Gmail search syntax
status, messages = mail.search(None, 'X-GM-RAW', '"has:attachment"')
status, messages = mail.search(None, 'X-GM-RAW', '"in:anywhere"')
status, messages = mail.search(None, 'X-GM-RAW', '"label:important"')

Error Handling

import imaplib
 
try:
    mail = imaplib.IMAP4_SSL('imap.gmail.com')
    mail.login('you@gmail.com', 'wrong-password')
except imaplib.IMAP4.error as e:
    print(f"IMAP error: {e}")
except ConnectionRefusedError:
    print("Could not connect to server")
finally:
    try:
        mail.logout()
    except:
        pass

Summary

imaplib provides full IMAP access:

  • IMAP4_SSL() for secure connections
  • search() for finding emails
  • fetch() for downloading messages
  • store() for managing flags
  • select() for choosing folders

Combine with the email module to parse message content and attachments. For Gmail, use App Passwords with 2FA enabled.

React to this post: