Guide
IMAP vs Gmail API vs Graph API
You're building an email integration and need to pick a protocol. IMAP is 38 years old and works everywhere. Gmail API gives you labels, push notifications, and batch operations — but locks you into Google. Microsoft Graph covers 400 million Outlook users but requires Azure AD. This guide compares all three on auth, search, push notifications, rate limits, and code complexity, then shows how a unified CLI handles all of them with one command.
Written by Caleb Geene Director, Site Reliability Engineering
For CLI commands referenced in this guide, see the full command reference. The key commands are nylas email list, nylas email send, nylas email search, and nylas auth login.
What are the three main email integration protocols?
Over 4.4 billion people used email in 2024, according to Radicati Group's Email Statistics Report. Developers building email integrations face a fragmented ecosystem: three protocols dominate, each with different auth models, capabilities, and provider lock-in tradeoffs. IMAP (RFC 9051) is the open standard. The Gmail API is Google's proprietary REST interface. Microsoft Graph is Microsoft's unified API surface covering Outlook, Teams, OneDrive, and more.
The gap between them isn't cosmetic. IMAP uses persistent TCP connections and a text-based command protocol. Gmail API and Graph are both REST APIs with JSON payloads, but they authenticate differently, paginate differently, and expose different feature sets. A working Gmail API integration won't send a single message through Outlook, and vice versa.
The table below compares all three across the dimensions that matter most when building an email integration.
| Feature | IMAP | Gmail API | Microsoft Graph |
|---|---|---|---|
| Auth method | Username + app password, OAuth 2.0 SASL | OAuth 2.0 (Google Cloud Console) | OAuth 2.0 (Azure AD / Entra ID) |
| Read messages | FETCH command, raw MIME | GET messages, parsed JSON | GET messages, parsed JSON |
| Search | SEARCH/ESEARCH (server-side, limited) | q parameter (Gmail search syntax) | $search and $filter OData params |
| Push notifications | IDLE (one mailbox at a time) | Cloud Pub/Sub webhooks | Subscriptions / webhooks |
| Batch operations | None (one command per round-trip) | Up to 100 requests per batch | JSON batching (up to 20 requests) |
| Rate limits | Provider-specific (no standard) | 250 quota units/user/second | 10,000 requests/10 min/mailbox |
| Attachment handling | MIME multipart parsing | Base64 JSON field or separate GET | Separate GET per attachment |
| Send email | SMTP (separate protocol) | messages.send (raw or parsed) | sendMail action on message resource |
| Provider lock-in | None (works with any IMAP server) | Google only | Microsoft only |
How does IMAP work for email integration?
IMAP has been the standard email access protocol since RFC 3501 in 2003, with the updated IMAP4rev2 spec published as RFC 9051 in 2021. It works with every major provider: Gmail, Outlook, Yahoo, iCloud, FastMail, and any self-hosted mail server running Dovecot or Cyrus. That universality is its primary advantage. One codebase can read email from any provider that supports the protocol.
The protocol operates over persistent TCP connections (typically port 993 with TLS). You authenticate, SELECT a mailbox, and issue commands like FETCH, SEARCH, and STORE. The server responds with untagged data followed by a tagged completion response. IDLE mode keeps the connection open and pushes notifications when new messages arrive, but it works on only one mailbox per connection.
Here's a Python example using imaplib to list the 10 most recent inbox messages:
import imaplib
import email
# Connect and authenticate
conn = imaplib.IMAP4_SSL("imap.gmail.com", 993)
conn.login("user@gmail.com", "app-password-here")
conn.select("INBOX")
# Search for all messages, take the last 10
_, data = conn.search(None, "ALL")
msg_ids = data[0].split()[-10:]
for msg_id in msg_ids:
_, msg_data = conn.fetch(msg_id, "(RFC822)")
msg = email.message_from_bytes(msg_data[0][1])
print(f"From: {msg['From']} Subject: {msg['Subject']}")
conn.logout()That's 15 lines for a basic inbox listing. In production, you'd add error handling, connection pooling, MIME parsing for multipart bodies, and reconnect logic for dropped connections. The real pain points with IMAP are:
- Connection management: Each IMAP connection holds a TCP socket. Servers enforce per-user connection limits (Gmail allows 15 simultaneous connections).
- No batch operations: Every FETCH is a round-trip. Downloading 100 message headers means 100 commands, not one.
- MIME parsing: Messages arrive as raw RFC 5322 text. You parse headers, decode Base64 or quoted-printable body parts, and handle nested multipart structures yourself.
- Inconsistent implementations: Gmail maps IMAP folders to labels. Yahoo doesn't support CONDSTORE. Each provider's IMAP has quirks that break assumptions.
How does the Gmail API work?
Google processes over 300 billion emails per day across Gmail's 1.8 billion active users, according to their 2024 Transparency Report. The Gmail API exposes that infrastructure as a REST service. Unlike IMAP, the API returns parsed JSON instead of raw MIME, uses labels instead of folders, and supports batch operations that group up to 100 requests into a single HTTP call.
Authentication requires an OAuth 2.0 client configured in the Google Cloud Console. You request scopes like gmail.readonly or gmail.send, redirect the user to Google's consent screen, and exchange the authorization code for access and refresh tokens. Google enforces quota limits at 250 units per user per second, where a message list costs 5 units and a message get costs 5 units.
Here's a curl example that lists the 10 most recent messages:
# List 10 recent messages (requires valid OAuth access token)
curl -s "https://gmail.googleapis.com/gmail/v1/users/me/messages?maxResults=10" \
-H "Authorization: Bearer $ACCESS_TOKEN" | jq '.messages[].id'
# Get a specific message with parsed body
curl -s "https://gmail.googleapis.com/gmail/v1/users/me/messages/MSG_ID?format=full" \
-H "Authorization: Bearer $ACCESS_TOKEN" | jq '{from: .payload.headers[] | select(.name=="From") | .value, subject: .payload.headers[] | select(.name=="Subject") | .value}'Gmail API's strongest features for developers:
- Labels over folders: A message can have multiple labels (think tags, not directories).
INBOX,UNREAD, and custom labels all coexist on the same message. - Gmail search syntax: The
qparameter accepts the same queries users type into Gmail:from:boss@company.com after:2026/01/01 has:attachment. - Push via Pub/Sub: Instead of polling, you set up a Cloud Pub/Sub watch that triggers a webhook when the mailbox changes. No open connections needed.
- History-based sync: The
history.listendpoint returns only changes since a givenhistoryId, so you don't re-download the entire inbox on every sync.
The tradeoff is total lock-in. Gmail API code doesn't work against Outlook, Yahoo, or any other provider. If your product serves users across multiple email providers, you need a second integration.
How does Microsoft Graph handle email?
Microsoft 365 has over 400 million paid commercial seats, according to Microsoft's Q2 FY2025 earnings report. The Microsoft Graph Mail API is the only supported way to programmatically access Outlook mailboxes after Microsoft deprecated Basic Auth for Exchange Online in October 2022. Authentication goes through Azure AD (now Entra ID) using MSAL, with application or delegated permission flows.
Graph stands out from the other two protocols on delta queries. Instead of polling for all messages or tracking history IDs, you call the delta endpoint and get back only the messages that changed since your last sync token. This cuts sync time from minutes to milliseconds for mailboxes that receive a few messages per hour.
Here's a curl example that lists recent messages from an Outlook mailbox:
# List 10 recent messages with specific fields
curl -s "https://graph.microsoft.com/v1.0/me/messages?\$top=10&\$select=subject,from,receivedDateTime" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Prefer: outlook.body-content-type=text" | jq '.value[] | {subject, from: .from.emailAddress.address}'
# Delta query: get only changes since last sync
curl -s "https://graph.microsoft.com/v1.0/me/mailFolders/inbox/messages/delta" \
-H "Authorization: Bearer $ACCESS_TOKEN" | jq '.value | length'Key capabilities for developers building against Graph:
- Delta sync: Call
/messages/deltato get only new or changed messages. Store the@odata.deltaLinkand use it for subsequent requests. - OData query parameters:
$filter,$search,$select,$orderby, and$expandlet you shape responses server-side. Fetch only the fields you need instead of downloading full message objects. - Webhook subscriptions: Create a subscription on a mailbox folder and receive POST notifications when messages arrive, update, or get deleted. Subscriptions expire after 3 days for mail resources and must be renewed.
- Rate limits: 10,000 requests per 10 minutes per application per mailbox. That's generous for most use cases, but batch-heavy migrations can hit it. Graph returns
429 Too Many Requestswith aRetry-Afterheader when you exceed the limit.
Like the Gmail API, Graph is provider-locked. It covers Outlook.com, Microsoft 365, and on-premises Exchange (with hybrid configuration), but nothing outside the Microsoft ecosystem.
How does authentication compare across protocols?
Authentication is where the three protocols diverge most sharply. According to the Okta developer survey, OAuth integration is the number one pain point cited by API developers, with 47% reporting that token management adds at least 2 weeks to a project. Each protocol handles auth differently, and the complexity scales with the number of providers you support.
IMAP: The simplest option for a single user. Connect with a username and an app-specific password. Gmail requires generating an app password in Google Account settings (2FA must be enabled first). Yahoo uses a similar flow. For production apps with multiple users, you need OAuth 2.0 XOAUTH2 SASL, which means implementing the same OAuth flow as the Gmail API but for IMAP connections. Google deprecated plain password IMAP access for third-party apps in May 2022.
Gmail API: Create a project in Google Cloud Console, configure the OAuth consent screen, add the Gmail scopes you need, and generate credentials. Users see a consent screen listing the permissions you requested. Google's auth documentation lists 12 scopes for Gmail alone, from gmail.readonly to gmail.settings.sharing. Apps requesting sensitive scopes go through a verification review that takes 4-6 weeks.
Microsoft Graph: Register an app in Azure AD (Entra ID), configure API permissions, and set up the redirect URI. Graph uses two permission models: delegated (user signs in) and application (daemon, no user present). Application permissions require admin consent, which means an Azure AD admin must approve your app before it can access any mailbox in the organization. The permissions reference lists separate entries for Mail.Read, Mail.ReadWrite, Mail.Send, and their application-level equivalents.
If your product supports Gmail and Outlook users, you maintain two OAuth flows, two token refresh mechanisms, two sets of scopes, and two admin approval processes. Adding Yahoo or iCloud means adding IMAP/SMTP with app passwords as a third codepath.
How do search and sync differ across the three?
Search quality varies dramatically. A 2023 study by Superhuman's engineering team found that Gmail's server-side search returns results in under 200ms for mailboxes with 100,000+ messages, while IMAP SEARCH on the same mailbox took 3-8 seconds depending on the provider's indexing. Microsoft Graph's $search parameter uses Exchange's full-text index and performs similarly to Gmail for indexed fields.
IMAP SEARCH: The SEARCH command supports field-based queries like SEARCH FROM "alice@example.com" SINCE 01-Jan-2026. It returns a list of sequence numbers. There's no full-text body search in the base RFC 3501 spec; some providers extend it, others don't. Gmail's IMAP SEARCH actually runs against Gmail's search index, so it's faster than most IMAP servers, but the query syntax is still limited to IMAP keywords.
Gmail API search: Uses the same query syntax as the Gmail web interface. from:hr@company.com subject:benefits has:attachment larger:5M works as a query parameter. It searches the full body, headers, and attachment filenames. Results come back as message IDs that you fetch in a second request (or batch-fetch up to 100).
Graph search: The $search parameter uses KQL (Keyword Query Language): $search="from:hr@company.com AND subject:benefits". The $filter parameter handles structured queries: $filter=receivedDateTime ge 2026-01-01. You can combine both, but $search results can't be sorted (Microsoft returns them in relevance order only).
Sync strategies: IMAP tracks message state through sequence numbers and UIDs. A full sync requires downloading all UIDs in a mailbox and comparing them to your local store. Gmail API uses historyId for incremental sync: store the last historyId, then call history.list to get only changes since that point. Graph's delta queries work similarly but return full message objects with changes, not just IDs of changed messages.
When should you use each protocol?
The right protocol depends on four factors: which email providers your users have, whether you need push notifications, how many messages you process per sync, and how much auth infrastructure you want to maintain. Based on the tradeoffs above, here's a decision matrix with 5 common scenarios.
| Scenario | Best protocol | Why |
|---|---|---|
| Gmail-only product with push and batch needs | Gmail API | Pub/Sub push, batch up to 100, Gmail search syntax, history-based sync |
| Microsoft 365 enterprise with delta sync | Microsoft Graph | Delta queries, OData filtering, Azure AD SSO, webhook subscriptions |
| Multi-provider support, simple read access | IMAP | Universal compatibility, no vendor dependency, works with self-hosted servers |
| Multi-provider with push, search, and send | Unified API | One auth flow, one response format, one SDK — abstracts protocol differences |
| Internal tool, single self-hosted mail server | IMAP | No cloud dependency, app password auth, direct TCP connection |
When to use IMAP: You need multi-provider support and your use case is read-heavy with limited search requirements. You don't want to register apps with Google or Microsoft, and your users can provide app passwords or you're connecting to a self-hosted server. IMAP is also the right choice for internal tools that access a known mail server directly.
When to use Gmail API: Your product targets Gmail users exclusively and you need labels, push notifications via Pub/Sub, batch operations, or the full Gmail search syntax. The setup cost of Google Cloud Console, OAuth consent screen, and scope verification is justified by the feature set.
When to use Microsoft Graph: Your users are on Microsoft 365 or Outlook.com and you need delta sync, webhook subscriptions, or OData query parameters. Enterprise customers with Azure AD SSO make Graph the natural choice.
When to use a unified API: You support users across Gmail, Outlook, Yahoo, iCloud, and IMAP providers, and you don't want to maintain 3+ separate integrations with different auth flows, pagination formats, and error handling. A unified layer like Nylas normalizes all three protocols behind one REST API and one OAuth flow.
How does a unified CLI handle all three protocols?
Nylas CLI abstracts protocol differences by routing through a unified API that speaks IMAP, Gmail API, and Microsoft Graph under the hood. A single nylas email list command returns the same JSON structure whether the connected account is Gmail, Outlook, Yahoo, iCloud, or a self-hosted IMAP server. There's no protocol-specific code in your workflow.
Authenticate once, then use the same commands across providers:
# Authenticate with your Nylas API key (works for all providers)
nylas auth login
# List recent emails — works with Gmail, Outlook, Yahoo, iCloud, IMAP
nylas email list --limit 10 --json
# Search across any provider with one syntax
nylas email search "quarterly report" --json
# Send an email (provider-agnostic)
nylas email send --to "team@example.com" --subject "Sync notes" --body "Attached." --yesThe CLI handles the protocol translation you'd otherwise build yourself. When a Gmail account runs nylas email search, it routes through the Gmail API with the q parameter. When an Outlook account runs the same command, it uses Graph's $search. When a Yahoo account runs it, IMAP SEARCH handles the query. You write one command. The routing happens server-side.
For AI agents and automation scripts, this means you don't branch on provider type. A cron job that checks a shared inbox works identically whether that inbox is hosted on Gmail, Exchange, or Dovecot. See the cron email guide for a full walkthrough.
Next steps
Pick the protocol that matches your provider mix and feature needs. If you want multi-provider support without maintaining separate codepaths, try the CLI with a free Nylas account:
# Install
brew install nylas/nylas-cli/nylas
# Authenticate
nylas auth login
# List emails from any provider
nylas email list --limit 5 --json- Full command reference for all email, calendar, and contact commands
- Getting started guide for install and initial setup
- Send email from the terminal for detailed send workflows
- MCP vs API for AI agents if you're building an agent that needs email access
- Nylas developer docs for the full API reference