Python’s imaplib module implements the IMAP4rev1 protocol for reading and managing mail on a server. import imaplib. IMAP4_SSL: imap = imaplib.IMAP4_SSL("imap.gmail.com", 993) — TLS on port 993. IMAP4: imap = imaplib.IMAP4("imap.corp", 143) — plain or STARTTLS. login: imap.login("[email protected]", "password"). select: imap.select("INBOX") → (OK, [count]); imap.select(readonly=True) for read-only. search: typ, data = imap.search(None, "UNSEEN") → UIDs as space-separated bytes; criteria: "ALL", 'FROM "alice"', 'SUBJECT "report"', 'SINCE "01-Jan-2025"', "(UNSEEN FLAGGED)". fetch: typ, data = imap.fetch(uid, "(RFC822)") → raw message bytes; "(ENVELOPE)" for headers only; "(FLAGS)" for flag list; "(BODY[TEXT])" for body only. store: imap.store(uid, "+FLAGS", "\\Seen") — mark read; "-FLAGS" to clear; "\\Deleted" to soft-delete. expunge: imap.expunge() — permanently deletes \Deleted messages. copy: imap.copy(uid_set, "Archive"). list: imap.list() → all folders. create/delete/rename: imap.create("NewFolder"). IMAP UID vs sequence: imap.uid("SEARCH", None, "UNSEEN") — UID-stable commands. logout: imap.logout(). Claude Code generates inbox monitors, email archivers, unread-count dashboards, and automatic labeling pipelines.
CLAUDE.md for imaplib
## imaplib Stack
- Stdlib: import imaplib, ssl; from email.parser import BytesParser; import email.policy
- Connect: imap = imaplib.IMAP4_SSL("imap.example.com", 993)
- Auth: imap.login(user, password)
- Select: imap.select("INBOX")
- Search: _, data = imap.uid("SEARCH", None, "UNSEEN")
- Fetch: _, raw = imap.uid("FETCH", uid, "(RFC822)")
- Parse: BytesParser(policy=email.policy.default).parsebytes(raw[0][1])
- End: imap.logout()
imaplib IMAP Mailbox Pipeline
# app/imaputil.py — connect, search, fetch, parse, flag, archive, folder ops
from __future__ import annotations
import email.policy
import imaplib
import ssl
import time
from contextlib import contextmanager
from dataclasses import dataclass, field
from email.message import EmailMessage
from email.parser import BytesParser
from typing import Any, Generator, Iterator
_PARSER = BytesParser(policy=email.policy.default)
# ─────────────────────────────────────────────────────────────────────────────
# 1. Connection helpers
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class IMAPConfig:
host: str
port: int = 993
username: str = ""
password: str = ""
ssl: bool = True
timeout: float = 30.0
def _open_imap(cfg: IMAPConfig) -> imaplib.IMAP4:
"""Open and authenticate an IMAP connection."""
if cfg.ssl:
ctx = ssl.create_default_context()
conn = imaplib.IMAP4_SSL(cfg.host, cfg.port, ssl_context=ctx)
else:
conn = imaplib.IMAP4(cfg.host, cfg.port)
conn.login(cfg.username, cfg.password)
return conn
@contextmanager
def imap_session(cfg: IMAPConfig) -> Generator[imaplib.IMAP4, None, None]:
"""
Context manager that opens, yields, and logs out an IMAP connection.
Example:
with imap_session(cfg) as imap:
counts = folder_counts(imap)
"""
conn = _open_imap(cfg)
try:
yield conn
finally:
try:
conn.logout()
except Exception:
pass
# ─────────────────────────────────────────────────────────────────────────────
# 2. Search and fetch
# ─────────────────────────────────────────────────────────────────────────────
def search_uids(imap: imaplib.IMAP4, criteria: str, folder: str = "INBOX") -> list[bytes]:
"""
Search a folder and return a list of UID byte strings.
Example:
uids = search_uids(imap, "UNSEEN")
uids = search_uids(imap, '(FROM "[email protected]" UNSEEN)')
"""
imap.select(folder, readonly=True)
typ, data = imap.uid("SEARCH", None, criteria) # type: ignore[arg-type]
if typ != "OK" or not data or not data[0]:
return []
return [uid for uid in data[0].split() if uid]
def fetch_raw(imap: imaplib.IMAP4, uid: bytes, parts: str = "(RFC822)") -> bytes | None:
"""
Fetch raw message data for a single UID.
Example:
raw = fetch_raw(imap, b"42")
msg = BytesParser(policy=email.policy.default).parsebytes(raw)
"""
typ, data = imap.uid("FETCH", uid, parts) # type: ignore[arg-type]
if typ != "OK" or not data or not data[0]:
return None
item = data[0]
if isinstance(item, tuple):
return item[1]
return None
def fetch_message(imap: imaplib.IMAP4, uid: bytes) -> EmailMessage | None:
"""
Fetch and parse a single message by UID.
Example:
msg = fetch_message(imap, b"42")
print(msg["Subject"])
"""
raw = fetch_raw(imap, uid)
if raw is None:
return None
return _PARSER.parsebytes(raw) # type: ignore[return-value]
def iter_messages(
imap: imaplib.IMAP4,
criteria: str = "ALL",
folder: str = "INBOX",
batch_size: int = 20,
) -> Iterator[tuple[bytes, EmailMessage]]:
"""
Yield (uid, EmailMessage) for all messages matching criteria.
Example:
for uid, msg in iter_messages(imap, "UNSEEN"):
print(uid, msg["Subject"])
"""
uids = search_uids(imap, criteria, folder)
imap.select(folder, readonly=True)
for i in range(0, len(uids), batch_size):
batch = uids[i : i + batch_size]
uid_set = b",".join(batch)
typ, data = imap.uid("FETCH", uid_set, "(RFC822)") # type: ignore[arg-type]
if typ != "OK" or not data:
continue
idx = 0
for item in data:
if isinstance(item, tuple):
uid = batch[idx] if idx < len(batch) else b"?"
msg = _PARSER.parsebytes(item[1])
yield uid, msg # type: ignore[misc]
idx += 1
# ─────────────────────────────────────────────────────────────────────────────
# 3. Flag and folder operations
# ─────────────────────────────────────────────────────────────────────────────
def mark_seen(imap: imaplib.IMAP4, uid: bytes, folder: str = "INBOX") -> None:
"""Mark message as read (sets \\Seen flag)."""
imap.select(folder)
imap.uid("STORE", uid, "+FLAGS", "\\Seen") # type: ignore[arg-type]
def mark_unseen(imap: imaplib.IMAP4, uid: bytes, folder: str = "INBOX") -> None:
"""Remove \\Seen flag."""
imap.select(folder)
imap.uid("STORE", uid, "-FLAGS", "\\Seen") # type: ignore[arg-type]
def delete_message(imap: imaplib.IMAP4, uid: bytes, folder: str = "INBOX",
expunge: bool = True) -> None:
"""
Mark message as deleted and optionally expunge.
Example:
delete_message(imap, b"55", expunge=True)
"""
imap.select(folder)
imap.uid("STORE", uid, "+FLAGS", "\\Deleted") # type: ignore[arg-type]
if expunge:
imap.expunge()
def move_message(imap: imaplib.IMAP4, uid: bytes, dest_folder: str,
src_folder: str = "INBOX") -> None:
"""
Copy message to dest_folder then delete from src_folder.
Example:
move_message(imap, b"42", "Archive")
"""
imap.select(src_folder)
imap.uid("COPY", uid, dest_folder) # type: ignore[arg-type]
imap.uid("STORE", uid, "+FLAGS", "\\Deleted") # type: ignore[arg-type]
imap.expunge()
def list_folders(imap: imaplib.IMAP4) -> list[str]:
"""
Return a list of all mailbox folder names.
Example:
folders = list_folders(imap)
print(folders) # ['INBOX', 'Sent', 'Archive', ...]
"""
typ, data = imap.list()
folders: list[str] = []
if typ == "OK":
for item in data:
if isinstance(item, bytes):
# format: b'(\\HasNoChildren) "/" "FolderName"'
parts = item.decode(errors="replace").rsplit('"', 2)
if len(parts) >= 2:
folders.append(parts[-2].strip().strip('"'))
return folders
def folder_counts(imap: imaplib.IMAP4, folders: list[str] | None = None) -> dict[str, int]:
"""
Return {folder: total_count} for each folder.
Example:
counts = folder_counts(imap)
print(counts["INBOX"])
"""
targets = folders or list_folders(imap)
result: dict[str, int] = {}
for folder in targets:
try:
typ, data = imap.select(folder, readonly=True)
if typ == "OK" and data and data[0]:
result[folder] = int(data[0].decode())
except Exception:
result[folder] = -1
return result
# ─────────────────────────────────────────────────────────────────────────────
# 4. Summary and stats helpers
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class MessageSummary:
uid: bytes
subject: str
from_: str
date: str
size: int # RFC822 fetch size in bytes
def __str__(self) -> str:
return f"[{self.uid.decode()}] {self.date[:16]:16s} {self.from_[:25]:25s} {self.subject[:50]}"
def list_summaries(
imap: imaplib.IMAP4,
criteria: str = "ALL",
folder: str = "INBOX",
limit: int = 50,
) -> list[MessageSummary]:
"""
Return lightweight summaries (ENVELOPE only — no body fetch) for matching messages.
Example:
for s in list_summaries(imap, "UNSEEN", limit=20):
print(s)
"""
uids = search_uids(imap, criteria, folder)[-limit:]
if not uids:
return []
imap.select(folder, readonly=True)
uid_set = b",".join(uids)
typ, data = imap.uid("FETCH", uid_set, "(ENVELOPE RFC822.SIZE)") # type: ignore[arg-type]
if typ != "OK" or not data:
return []
summaries: list[MessageSummary] = []
uid_iter = iter(uids)
for item in data:
if not isinstance(item, tuple):
continue
uid = next(uid_iter, b"?")
raw = item[1].decode(errors="replace")
# Very simple extraction from IMAP ENVELOPE response string
subject = _extract_envelope_field(raw, "subject") or "(no subject)"
from_ = _extract_envelope_field(raw, "from") or ""
date = _extract_envelope_field(raw, "date") or ""
size_match = __import__("re").search(r"RFC822\.SIZE (\d+)", raw)
size = int(size_match.group(1)) if size_match else 0
summaries.append(MessageSummary(uid=uid, subject=subject, from_=from_,
date=date, size=size))
return summaries
def _extract_envelope_field(envelope_str: str, field: str) -> str:
"""
Rough extraction of a named field value from a raw IMAP ENVELOPE string.
Not a full IMAP parser — adequate for display purposes.
"""
import re
# Try to find quoted string after field name keyword (case-insensitive)
pattern = re.compile(
r'\b' + re.escape(field) + r'\b[^"]*"([^"]*)"', re.IGNORECASE
)
m = pattern.search(envelope_str)
return m.group(1).strip() if m else ""
# ─────────────────────────────────────────────────────────────────────────────
# Demo (no live IMAP server required — demo shows API shapes only)
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== imaplib demo ===")
# ── Demonstrate config and context manager shape ──────────────────────────
print("\n--- IMAPConfig ---")
cfg = IMAPConfig(
host="imap.gmail.com",
port=993,
username="[email protected]",
password="app-password",
ssl=True,
)
print(f" host={cfg.host} port={cfg.port} ssl={cfg.ssl}")
# ── Show MessageSummary __str__ with fake data ────────────────────────────
print("\n--- MessageSummary ---")
summaries = [
MessageSummary(b"1", "Quarterly report", "[email protected]", "Mon, 1 Apr 2025", 24576),
MessageSummary(b"2", "Re: meeting notes", "[email protected]", "Tue, 2 Apr 2025", 8192),
MessageSummary(b"3", "Invoice #1042", "[email protected]", "Wed, 3 Apr 2025", 4096),
]
for s in summaries:
print(f" {s}")
# ── search_uids shapes ────────────────────────────────────────────────────
print("\n--- search criteria examples ---")
criteria_examples = [
"ALL",
"UNSEEN",
'(FROM "[email protected]")',
'(SUBJECT "report" UNSEEN)',
'(SINCE "01-Jan-2025" BEFORE "01-Apr-2025")',
"(FLAGGED)",
]
for c in criteria_examples:
print(f" search_uids(imap, {c!r})")
# ── folder ops shapes ────────────────────────────────────────────────────
print("\n--- folder operation shapes ---")
print(" list_folders(imap) → ['INBOX', 'Sent', 'Archive']")
print(" folder_counts(imap) → {'INBOX': 42, 'Sent': 201}")
print(" move_message(imap, b'42', 'Archive') → moves UID 42 to Archive")
print(" delete_message(imap, b'55', expunge=True) → permanent delete")
print("\n=== done (no live IMAP connection — shapes only) ===")
For the email / email.parser alternative — email.parser.BytesParser is the companion module for decoding the raw bytes that imaplib fetches; the two work together: imaplib provides the IMAP transport and email provides the MIME parser — they are complementary, not alternatives; fetch_message() above always passes raw bytes through BytesParser. For the IMAPClient (imapclient PyPI) alternative — imapclient (PyPI) wraps imaplib with a high-level Pythonic API that returns native Python types instead of raw byte tuples, handles UID vs. sequence numbers transparently, and provides IDLE push-notification support with idle_check()/idle_done() — use imapclient in production applications where the low-level imaplib response parsing becomes unwieldy; use imaplib directly when zero dependencies matter, for simple search-and-fetch scripts, or when building a library that must not carry PyPI dependencies. The Claude Skills 360 bundle includes imaplib skill sets covering IMAPConfig dataclass, imap_session() context manager, search_uids()/fetch_raw()/fetch_message()/iter_messages() search and retrieval, mark_seen()/mark_unseen()/delete_message()/move_message() flag and folder operations, list_folders()/folder_counts() folder management, and MessageSummary with list_summaries() for ENVELOPE-only listing. Start with the free tier to try IMAP4 mailbox patterns and imaplib pipeline code generation.