What is IMAP?
The Internet Message Access Protocol (IMAP) is a standard internet protocol used by email clients to retrieve messages from a mail server. Unlike its predecessor POP3 (Post Office Protocol), IMAP allows users to access and manage their email messages directly on the server without downloading them to a local device first. This means you can read, organize, and delete emails across multiple devices while keeping everything synchronized.
IMAP was originally developed by Mark Crispin at the University of Washington in 1986. The current widely adopted version is IMAP4rev1, defined in RFC 3501. Modern IMAP implementations support encrypted connections via TLS (IMAPS on port 993 or STARTTLS on port 143), extensive folder management, server-side searching, and partial message fetching β making it the backbone of modern email infrastructure.
Key Characteristics of IMAP
- Server-centric model: Messages remain on the server until explicitly deleted
- Multi-device synchronization: Read status, flags, and folder structure sync across all clients
- Partial fetching: Retrieve only headers or specific MIME parts without downloading entire messages
- Server-side search: Search messages by criteria directly on the server
- Folder hierarchy: Support for nested folders and mailboxes (INBOX, Sent, Drafts, custom folders)
- Persistent flags: Messages can be marked as Seen, Answered, Flagged, Deleted, Draft
Why IMAP Matters
π Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Understanding IMAP is essential for any developer building email-related applications. Here's why it matters in practical terms:
1. Multi-Device Email Access
In today's world, users check email on phones, tablets, laptops, and desktops. IMAP ensures that reading an email on your phone marks it as read everywhere. Deleting from one device removes it everywhere. This synchronization is impossible with POP3's download-and-delete model.
2. Efficient Bandwidth Usage
IMAP clients can fetch only message headers (subject, from, date) when displaying an inbox list. The full body is retrieved only when the user opens a specific email. For large attachments, clients can fetch just the text part and download attachments on demand. This dramatically reduces bandwidth consumption compared to downloading everything upfront.
3. Server-Side Processing
Need to find all emails from a specific sender across 50,000 messages? With IMAP, you issue a SEARCH command and the server returns matching UIDs β no need to download and process everything client-side. This is critical for email migration tools, backup systems, and analytics pipelines.
4. Integration and Automation
IMAP enables automated email processing: ticket systems that create issues from incoming emails, monitoring tools that check for alert messages, and data extraction pipelines that parse structured email content. The protocol's standardized command set makes cross-platform integration reliable and predictable.
How IMAP Works: Protocol Mechanics
Connection and Ports
IMAP typically operates on two ports:
- Port 143: Standard IMAP, often used with STARTTLS to upgrade to an encrypted connection
- Port 993: IMAPS β IMAP over TLS/SSL directly (encrypted from the first byte)
Always prefer port 993 or STARTTLS on port 143 in production. Unencrypted IMAP sends credentials in plaintext and should never be used over untrusted networks.
Protocol State Machine
IMAP is a stateful protocol. Each connection progresses through distinct states:
- Non-Authenticated: Initial state upon connection. Only LOGIN, AUTHENTICATE, CAPABILITY, and STARTTLS commands are allowed
- Authenticated: After successful login. Commands that don't require a selected mailbox (SELECT, EXAMINE, CREATE, DELETE, RENAME, LIST, LSUB, SUBSCRIBE, UNSUBSCRIBE, APPEND, STATUS)
- Selected: After selecting a mailbox. Commands that operate on messages (FETCH, SEARCH, STORE, COPY, EXPUNGE, CLOSE)
- Logout: After LOGOUT command. Server closes the connection
Command-Response Tagging
Every client command is prefixed with a unique tag (usually an alphanumeric string like "A001", "A002"). Server responses are tagged with the same identifier, allowing the client to match responses to commands. Untagged responses (prefixed with "*") provide status updates, mailbox data, or search results. The server signals command completion with a tagged response ending in "OK" (success), "NO" (failure), or "BAD" (protocol error).
Practical Code Examples
Example 1: Raw IMAP via OpenSSL
This is the most fundamental way to interact with an IMAP server. Understanding raw commands gives you deep insight into how the protocol works before you abstract it away with libraries.
# Connect to Gmail IMAPS and authenticate
openssl s_client -connect imap.gmail.com:993 -crlf -quiet
# --- Server greets you with capability list ---
# * OK Gmail IMAP server ready
# 1. Login (tag: A001)
A001 LOGIN user@example.com "app-specific-password"
# A001 OK user@example.com authenticated
# 2. List available mailboxes
A002 LIST "" "*"
# * LIST (\HasNoChildren) "/" "INBOX"
# * LIST (\HasNoChildren) "/" "Sent"
# * LIST (\HasChildren) "/" "Projects"
# A002 OK List completed
# 3. Select INBOX
A003 SELECT INBOX
# * 150 EXISTS
# * 0 RECENT
# * OK [UIDVALIDITY 1234567] UIDs valid
# A003 OK [READ-WRITE] INBOX selected
# 4. Fetch headers of last 3 messages
A004 FETCH 148:150 (FLAGS INTERNALDATE RFC822.SIZE BODY.PEEK[HEADER.FIELDS (FROM SUBJECT DATE)])
# * 148 FETCH (FLAGS (\Seen) RFC822.SIZE 4567 BODY[HEADER.FIELDS...
# * 149 FETCH (FLAGS (\Seen) RFC822.SIZE 8912 BODY[HEADER.FIELDS...
# * 150 FETCH (FLAGS () RFC822.SIZE 2341 BODY[HEADER.FIELDS...
# A004 OK Fetch completed
# 5. Fetch full body of message 150
A005 FETCH 150 (BODY[TEXT])
# * 150 FETCH (BODY[TEXT] {2341}
# ...full message body...
# )
# A005 OK Fetch completed
# 6. Search for messages from a sender
A006 SEARCH FROM "newsletter@example.com"
# * SEARCH 12 45 89 134
# A006 OK Search completed
# 7. Logout
A007 LOGOUT
# * BYE Logging out
# A007 OK Logout completed
Example 2: Python imaplib β Connect, List Folders, Search, Fetch
Python's standard library includes imaplib, which provides a robust IMAP4 client. Here's a complete working example that connects to a server, lists folders, searches for unread messages, and fetches their content.
import imaplib
import email
from email.header import decode_header
import datetime
# Configuration - use environment variables in production
IMAP_HOST = "imap.example.com"
IMAP_PORT = 993
USERNAME = "user@example.com"
PASSWORD = "your-app-password" # Use app-specific passwords for Gmail
def connect_imap():
"""Establish a secure IMAP connection"""
# Use SSL directly on port 993
conn = imaplib.IMAP4_SSL(IMAP_HOST, IMAP_PORT)
# Alternatively, use STARTTLS on port 143:
# conn = imaplib.IMAP4(IMAP_HOST, 143)
# conn.starttls()
# Login
status, response = conn.login(USERNAME, PASSWORD)
if status != 'OK':
raise Exception(f"Login failed: {response}")
print("β Connected and authenticated")
return conn
def list_all_folders(conn):
"""List all mailboxes/folders with their attributes"""
status, raw_folders = conn.list()
if status != 'OK':
raise Exception("Failed to list folders")
folders = []
for line in raw_folders:
# Parse folder attributes and name
# Format: b'(\\HasNoChildren) "/" "FolderName"'
parts = line.decode().split('"')
if len(parts) >= 3:
folder_name = parts[-2] # The folder name
attrs = parts[0].strip('() ')
folders.append((folder_name, attrs))
print(f"\nFound {len(folders)} folders:")
for name, attrs in folders:
has_children = '\\HasChildren' in attrs
print(f" {'π' if has_children else 'π'} {name}")
return folders
def search_unread_messages(conn, folder="INBOX", limit=10):
"""Search for unread messages and return their UIDs"""
# Select folder with read-write access
status, response = conn.select(folder)
if status != 'OK':
raise Exception(f"Failed to select folder: {folder}")
# Count total messages
total = int(response[0].decode())
print(f"\nFolder '{folder}' has {total} messages")
# Search for UNSEEN messages
status, uid_list = conn.uid('search', None, 'UNSEEN')
if status != 'OK':
print("No unread messages found")
return []
# Parse UIDs from response
uids = uid_list[0].decode().split()
if not uids:
print("No unread messages")
return []
# Take the most recent ones (highest UIDs last)
uids = sorted([int(uid) for uid in uids], reverse=True)[:limit]
print(f"Found {len(uids)} unread messages, processing top {limit}")
return uids
def fetch_email_by_uid(conn, uid):
"""Fetch full email by UID and parse it"""
# Fetch the entire RFC822 message
status, data = conn.uid('fetch', str(uid).encode(), '(RFC822)')
if status != 'OK':
return None
# Parse raw email bytes
raw_email = data[0][1]
msg = email.message_from_bytes(raw_email)
# Extract headers with decoding
subject = decode_email_header(msg['Subject'])
from_addr = decode_email_header(msg['From'])
date_str = msg['Date']
# Extract body (handling multipart)
body = extract_email_body(msg)
return {
'uid': uid,
'subject': subject,
'from': from_addr,
'date': date_str,
'body': body[:500] + ('...' if len(body) > 500 else '') # Truncate for display
}
def decode_email_header(header):
"""Decode =?utf-8?Q?...?= encoded headers"""
if header is None:
return ""
decoded_parts = decode_header(header)
result = ""
for part, charset in decoded_parts:
if isinstance(part, bytes):
result += part.decode(charset or 'utf-8', errors='replace')
else:
result += part
return result
def extract_email_body(msg):
"""Extract plain text body from email, preferring text/plain"""
if msg.is_multipart():
for part in msg.walk():
content_type = part.get_content_type()
if content_type == 'text/plain':
payload = part.get_payload(decode=True)
if payload:
return payload.decode('utf-8', errors='replace')
# Fallback: return first part
payload = msg.get_payload(0, decode=True)
return payload.decode('utf-8', errors='replace') if payload else ""
else:
payload = msg.get_payload(decode=True)
return payload.decode('utf-8', errors='replace') if payload else ""
def mark_as_read(conn, uid):
"""Mark a message as seen using STORE command"""
conn.uid('store', str(uid).encode(), '+FLAGS', '\\Seen')
def main():
conn = connect_imap()
# List all folders
list_all_folders(conn)
# Search for unread messages
uids = search_unread_messages(conn, "INBOX", limit=5)
# Fetch and display each unread message
for uid in uids:
email_data = fetch_email_by_uid(conn, uid)
if email_data:
print(f"\n{'='*60}")
print(f"UID: {email_data['uid']}")
print(f"From: {email_data['from']}")
print(f"Subject: {email_data['subject']}")
print(f"Date: {email_data['date']}")
print(f"Body preview: {email_data['body'][:200]}")
# Mark as read after processing
mark_as_read(conn, uid)
print("β Marked as read")
# Cleanup
conn.logout()
print("\nβ Logged out")
if __name__ == "__main__":
main()
Example 3: Node.js with node-imap β Monitor New Emails in Real-Time
For real-time email monitoring, Node.js with the node-imap package provides an excellent event-driven approach. This example demonstrates setting up a mailbox listener that fires whenever new emails arrive.
// Install: npm install node-imap mailparser
const Imap = require('node-imap');
const { simpleParser } = require('mailparser');
const { EventEmitter } = require('events');
class EmailMonitor extends EventEmitter {
constructor(config) {
super();
this.config = {
user: config.user,
password: config.password,
host: config.host,
port: 993,
tls: true,
tlsOptions: { rejectUnauthorized: true },
keepAlive: true, // Prevent timeout disconnects
};
this.imap = new Imap(this.config);
this.setupEventHandlers();
}
setupEventHandlers() {
this.imap.on('ready', () => {
console.log('β IMAP connection ready');
this.openInbox();
});
this.imap.on('error', (err) => {
console.error('IMAP error:', err);
this.emit('error', err);
});
this.imap.on('end', () => {
console.log('Connection ended, reconnecting in 30s...');
setTimeout(() => this.connect(), 30000);
});
}
connect() {
console.log('Connecting to IMAP server...');
this.imap.connect();
}
openInbox() {
this.imap.openBox('INBOX', false, (err, mailbox) => {
if (err) {
console.error('Failed to open INBOX:', err);
this.emit('error', err);
return;
}
console.log(`INBOX opened: ${mailbox.messages.total} messages`);
// Listen for new mail arriving
this.imap.on('mail', (count) => {
console.log(`π¨ ${count} new message(s) arrived`);
this.fetchLatestMessages(count);
});
// Also listen for changes to existing messages (flag updates)
this.imap.on('update', (seqno, info) => {
console.log(`Message ${seqno} updated:`, info);
});
});
}
fetchLatestMessages(count) {
// Fetch the most recent 'count' messages
const fetchOptions = {
bodies: ['HEADER.FIELDS (FROM TO SUBJECT DATE)', 'TEXT'],
struct: true,
markSeen: false, // Don't auto-mark as read
};
// Calculate range: last 'count' messages
this.imap.search([['UNSEEN']], (err, results) => {
if (err || !results.length) return;
const fetch = this.imap.fetch(results, fetchOptions);
fetch.on('message', (msg, seqno) => {
console.log(`Processing message #${seqno}`);
let attributes = {};
msg.on('attributes', (attrs) => {
attributes = attrs;
});
msg.on('body', (stream, info) => {
let buffer = '';
stream.on('data', (chunk) => {
buffer += chunk.toString('utf8');
});
stream.on('end', async () => {
if (info.which === 'TEXT') {
// Parse the complete email
const parsed = await simpleParser(stream.buffer || buffer);
this.emit('newEmail', {
seqno,
uid: attributes.uid,
flags: attributes.flags,
subject: parsed.subject,
from: parsed.from?.value[0]?.address,
date: parsed.date,
text: parsed.text?.substring(0, 500),
html: parsed.html,
});
console.log(`β Parsed email: ${parsed.subject}`);
}
});
});
});
fetch.on('end', () => {
console.log('β Fetch complete');
});
});
}
disconnect() {
this.imap.end();
}
}
// Usage example
const monitor = new EmailMonitor({
user: 'user@example.com',
password: process.env.EMAIL_PASSWORD,
host: 'imap.example.com',
});
monitor.on('newEmail', (email) => {
console.log(`\nπ§ NEW EMAIL:`);
console.log(` From: ${email.from}`);
console.log(` Subject: ${email.subject}`);
console.log(` Date: ${email.date}`);
console.log(` Preview: ${email.text?.substring(0, 150)}`);
});
monitor.on('error', (err) => {
console.error('Monitor error:', err);
});
// Start monitoring
monitor.connect();
// Graceful shutdown
process.on('SIGINT', () => {
console.log('\nShutting down...');
monitor.disconnect();
process.exit(0);
});
Example 4: IMAP SEARCH with Advanced Criteria
The SEARCH command supports rich criteria for filtering messages. Here's how to construct complex searches across different programming environments.
# --- Raw IMAP search examples ---
# Search for messages from a specific address since a date
A001 SEARCH FROM "alerts@company.com" SINCE 01-Jan-2025
# Returns: * SEARCH 45 67 89
# Search for messages with attachments (larger than 100KB)
A002 SEARCH LARGER 100000
# Returns matching UIDs
# Search for messages with specific subject AND unseen
A003 SEARCH SUBJECT "invoice" UNSEEN
# Combines criteria with AND logic
# Search for messages NOT from a list of addresses
A004 SEARCH UNSEEN NOT FROM "newsletter@example.com" NOT FROM "spam@example.com"
# Search for messages flagged AND from a sender
A005 SEARCH FLAGGED FROM "boss@company.com"
# Complex search: unread, from specific domain, with subject keyword, since date
A006 SEARCH UNSEEN FROM "@example.com" SUBJECT "report" SINCE 15-Feb-2025
# Python: Advanced search with imaplib
import imaplib
from datetime import datetime, timedelta
conn = imaplib.IMAP4_SSL('imap.example.com', 993)
conn.login('user@example.com', 'password')
conn.select('INBOX')
# Search with multiple criteria
criteria = '(FROM "notifications@example.com" SUBJECT "alert" UNSEEN SINCE "20-Feb-2025")'
status, result = conn.uid('search', None, criteria)
uids = result[0].decode().split() if result[0] else []
# Search messages larger than 1MB with attachments
status, result = conn.uid('search', None, '(LARGER 1000000)')
# Search using NOT operator
status, result = conn.uid('search', None, '(UNSEEN NOT FROM "spam@example.com")')
# Search for messages within a date range using SINCE and BEFORE
status, result = conn.uid('search', None, '(SINCE "01-Jan-2025" BEFORE "01-Feb-2025")')
# Search by header fields
status, result = conn.uid('search', None, '(HEADER "X-Priority" "1")')
status, result = conn.uid('search', None, '(HEADER "List-Unsubscribe" "@")')
conn.logout()
Best Practices for IMAP Development
1. Always Use Encryption
Never transmit credentials over plaintext IMAP. Use port 993 (IMAPS) or STARTTLS on port 143. Verify TLS certificates properly β avoid disabling certificate validation in production code. For Gmail and most modern providers, TLS 1.2+ is required.
# Bad: Disabling certificate verification
conn = imaplib.IMAP4_SSL('imap.example.com', 993, ssl_context=ssl._create_unverified_context())
# Good: Use default verification
conn = imaplib.IMAP4_SSL('imap.example.com', 993)
# Better: Explicit SSL context with minimum TLS version
import ssl
context = ssl.create_default_context()
context.minimum_version = ssl.TLSVersion.TLSv1_2
conn = imaplib.IMAP4_SSL('imap.example.com', 993, ssl_context=context)
2. Use UIDs, Not Sequence Numbers
IMAP provides two ways to reference messages: sequence numbers (relative position in mailbox) and UIDs (unique, permanent identifiers). Always prefer UIDs for operations that span multiple connections. Sequence numbers change when messages are expunged; UIDs remain constant (unless the UIDVALIDITY changes). Use conn.uid('fetch', ...) and conn.uid('search', ...) patterns.
# Good: Using UIDs (stable across sessions)
status, result = conn.uid('fetch', '12345:12350', '(RFC822)')
# Avoid: Using sequence numbers for persistent operations
status, result = conn.fetch('1:5', '(RFC822)') # These numbers shift after expunge
3. Implement Proper Error Handling and Retry Logic
IMAP connections can drop due to network issues, server timeouts, or maintenance. Implement exponential backoff retry logic. Handle the full spectrum of server responses: OK (success), NO (failure β often permission-related), BAD (protocol error β check your command syntax), and BYE (server-initiated disconnect).
import time
import random
def imap_operation_with_retry(conn_func, max_retries=5, base_delay=1):
"""Execute an IMAP operation with exponential backoff retry"""
for attempt in range(max_retries):
try:
result = conn_func()
return result
except (imaplib.IMAP4.abort, imaplib.IMAP4.error, ConnectionError) as e:
if attempt == max_retries - 1:
raise
delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
print(f"Retry {attempt+1}/{max_retries} after {delay:.1f}s: {e}")
time.sleep(delay)
# Reconnect before retry
conn.logout()
conn.connect()
# Usage
result = imap_operation_with_retry(
lambda: conn.uid('search', None, 'UNSEEN')
)
4. Respect Rate Limits
Most email providers impose rate limits on IMAP connections. Gmail, for instance, limits you to approximately 15-20 simultaneous IMAP connections per account and throttles excessive command rates. Implement connection pooling, avoid tight loops of FETCH commands, and batch operations where possible. Use the IDLE command for real-time notifications instead of polling.
# Bad: Polling every 2 seconds
while True:
conn.select('INBOX')
# ... check for new messages
time.sleep(2)
# Good: Use IDLE for push-based notifications
conn.idle()
# Server sends updates in real-time without polling
# Exit IDLE with DONE command when you need to take action
5. Handle MIME and Encodings Properly
Email messages are complex MIME structures. A single message can contain plain text, HTML, inline images, and attachments β all in nested multipart containers. Always decode headers using the mechanisms provided (RFC 2047 for headers, RFC 2231 for parameter values). Handle character set conversions gracefully with fallback encodings.
# Decoding internationalized headers properly
from email.header import decode_header, make_header
raw_subject = '=?UTF-8?B?w5xibWVy?= '
decoded = str(make_header(decode_header(raw_subject)))
print(decoded) # Outputs: "Γbmer"
# Handling nested multipart messages
def extract_all_text_parts(msg):
"""Recursively extract all text parts from a MIME message"""
texts = []
if msg.is_multipart():
for part in msg.walk():
if part.get_content_type() == 'text/plain':
payload = part.get_payload(decode=True)
if payload:
charset = part.get_content_charset() or 'utf-8'
texts.append(payload.decode(charset, errors='replace'))
else:
payload = msg.get_payload(decode=True)
if payload:
texts.append(payload.decode('utf-8', errors='replace'))
return texts
6. Manage Mailbox State Carefully
When you SELECT a mailbox, the server provides state information: EXISTS (total messages), RECENT (new messages this session), UIDVALIDITY (epoch for UIDs), and UIDNEXT (next predicted UID). Cache UIDVALIDITY β if it changes, all your cached UIDs are invalidated and must be refreshed. After operations that modify messages (STORE, EXPUNGE), be aware that sequence numbers shift.
# Check UIDVALIDITY when selecting a mailbox
status, response = conn.select('INBOX')
# Parse response for UIDVALIDITY
# * OK [UIDVALIDITY 1234567890] UIDs valid
# Store this value and compare on subsequent SELECTs
uidvalidity = None
for line in response:
if b'UIDVALIDITY' in line:
uidvalidity = int(line.split(b'UIDVALIDITY')[1].split(b']')[0])
break
# Cache pattern
CACHED_UIDVALIDITY = load_from_cache()
if uidvalidity != CACHED_UIDVALIDITY:
print("UIDVALIDITY changed β invalidating local cache")
clear_cache()
save_to_cache(uidvalidity)
7. Secure Credential Management
Never hardcode passwords in source code. Use environment variables, secure credential stores, or configuration files with restricted permissions. For services like Gmail, generate app-specific passwords rather than using your main account password. For OAuth-enabled providers (Gmail, Microsoft 365), implement OAuth 2.0 authentication via the AUTHENTICATE XOAUTH2 SASL mechanism for token-based access without storing passwords.
# Environment variable approach
import os
EMAIL_PASSWORD = os.environ.get('EMAIL_PASSWORD')
if not EMAIL_PASSWORD:
raise ValueError("Set EMAIL_PASSWORD environment variable")
# OAuth 2.0 for Gmail (simplified)
def generate_xoauth2_token(client_id, client_secret, refresh_token, user_email):
"""Generate XOAUTH2 SASL token for IMAP authentication"""
# Use google-auth-library or similar to get access token
access_token = get_access_token(client_id, client_secret, refresh_token)
auth_string = f"user={user_email}\x01auth=Bearer {access_token}\x01\x01"
return auth_string.encode('utf-8')
conn = imaplib.IMAP4_SSL('imap.gmail.com', 993)
conn.authenticate('XOAUTH2', lambda x: generate_xoauth2_token(...))
Common IMAP Pitfalls and How to Avoid Them
- Forgetting to LOGOUT: Always issue LOGOUT before closing the connection. This ensures flags are properly synchronized and the server cleans up the session gracefully.
- Ignoring UIDVALIDITY: If you cache UIDs and the UIDVALIDITY changes, all cached data is invalid. Always check UIDVALIDITY on SELECT and invalidate caches when it differs.
- Fetching entire messages for list views: Use BODY.PEEK[HEADER] or BODY.PEEK[HEADER.FIELDS (SUBJECT FROM DATE)] to get just what you need for inbox display. PEEK variants don't set the \Seen flag.
- Not handling concurrent access: Multiple clients accessing the same mailbox can cause race conditions. Use HIGHESTMODSEQ and CONDSTORE extensions (RFC 7162) for conditional operations.
- Hardcoding folder names: Different servers use different folder hierarchies. Gmail uses [Gmail]/Sent Mail; Exchange uses Sent Items. Always LIST folders and let users map them.
- Assuming ASCII email content: Modern email is UTF-8 throughout. Always decode headers with charset handling and handle 8-bit content transfer encodings properly.
Conclusion
IMAP remains the cornerstone protocol for modern email access, powering everything from personal email clients to enterprise-scale email processing pipelines. Its server-centric model, rich command set for partial fetching and server-side search, and robust synchronization capabilities make it indispensable for any application that handles email.
As a developer, understanding IMAP at both the protocol level and through practical library usage gives you the foundation to build reliable, efficient email integrations. The raw command examples in this guide demystify the protocol's inner workings, while the Python and Node.js examples provide production-ready patterns you can adapt directly. By following the best practices outlined here β using encryption, preferring UIDs, implementing proper retry logic, handling MIME correctly, and managing credentials securely β you'll create email applications that are both robust and maintainable.
The protocol continues to evolve with extensions like CONDSTORE for conditional modification, QRESYNC for quick resynchronization, and COMPRESS for bandwidth optimization. However, the core concepts covered in this guide form the bedrock of all IMAP development and will serve you well regardless of which extensions you later adopt.