How to Read Emails via API: A Developer's Guide to Inbox Access
Scope: This guide covers sync APIs for reading users' existing inboxes - Gmail API, Microsoft Graph, IMAP, and unified layers like Unipile. This is distinct from transactional services (SendGrid, Mailgun) that send outbound email from your domain.
Build products that read user emails programmatically. From a single GET /api/v1/emails call to real-time webhooks, this guide covers every approach with working code in Node.js, Python, and cURL.
// Read emails with one unified API call
const response = await fetch(
'https://api3.unipile.com:PORT/api/v1/emails',
{
headers: {
'X-API-KEY': process.env.UNIPILE_API_KEY,
'account_id': accountId
}
}
);
const { emails } = await response.json();
// Same JSON shape for Gmail, Outlook & IMAP
emails.forEach(email => {
console.log(email.subject, email.from_attendee);
});What is a Read Email API?
A read email API is an HTTP interface that lets your application access, retrieve, and process emails from a user's existing mailbox - without storing passwords or building provider-specific integrations. It is the foundation of any product that needs inbox visibility: CRM sync, AI email copilots, support automation, or compliance archiving.
A read email API exposes endpoints to authenticate against a user's mailbox (via OAuth 2.0 or IMAP credentials), list inbox messages, fetch full email bodies with attachments, and subscribe to real-time delivery events. It operates on the user's existing Gmail, Outlook, or IMAP account - not a domain you control. This distinguishes it from transactional email APIs (SendGrid, Mailgun, Resend), which send outbound email on behalf of your application.
Connect to users' existing inboxes. Read, sync, and react to emails in real time. Auth via OAuth 2.0 or IMAP. The user grants access to their own mailbox.
Send outbound emails from a domain you control. Used for receipts, notifications, password resets. No inbox access.
Why Reading Emails Programmatically Matters
The ability to read user emails via API unlocks a category of products that were simply impossible with SMTP alone. Here are the four primary use cases driving adoption in 2026.
CRM and Sales Engagement Sync
Sales tools need to see every email thread between a rep and a prospect - automatically, without manual logging. A read email API pulls conversations directly from the rep's inbox and syncs them to your CRM in real time.
AI Agents and Email Copilots
Large language models need context. By feeding your AI agent a real-time stream of inbox emails, you can build copilots that draft replies, summarise threads, extract action items, and prioritise conversations automatically.
Customer Support Automation
Support teams receive thousands of emails a day. A read email API lets your platform classify incoming requests, route them to the right queue, and trigger automated responses - all before a human ever opens a ticket.
Compliance, Archiving, and eDiscovery
Regulated industries must retain and index every email. A read email API provides the programmatic access needed to continuously archive inboxes, flag policy violations, and produce email records on demand for legal review.
Native APIs for Reading Emails: Gmail, Outlook, and IMAP
Each major email provider exposes its own read API with different endpoints, authentication models, and capabilities. Here is what each looks like in practice.
Gmail API
OAuth 2.0The Gmail API is a REST API built on top of Google's infrastructure. It uses users.messages.list to paginate through a mailbox and users.messages.get to fetch a full message. It supports push notifications via Google Pub/Sub, making real-time inbox monitoring feasible without polling. Rate limit: 250 quota units per user per second. Note: Gmail requires OAuth 2.0 - API keys do not work for mailbox access. See why Gmail rejects API keys.
# List inbox messages (Gmail API)
curl -X GET \
"https://gmail.googleapis.com/gmail/v1/users/me/messages?labelIds=INBOX&maxResults=20" \
-H "Authorization: Bearer ACCESS_TOKEN"
# Fetch a single email with full body
curl -X GET \
"https://gmail.googleapis.com/gmail/v1/users/me/messages/MESSAGE_ID?format=full" \
-H "Authorization: Bearer ACCESS_TOKEN"Microsoft Graph (Outlook and Microsoft 365)
OAuth 2.0Microsoft Graph is the unified API for all Microsoft 365 services including Outlook personal accounts, Exchange Online, and Microsoft 365 business mailboxes. The /me/messages endpoint returns messages with full body content in a single request. Pagination uses $skip and $top OData parameters. See the full Microsoft Graph email integration guide for details.
# List inbox messages (Microsoft Graph)
curl -X GET \
"https://graph.microsoft.com/v1.0/me/messages?\$top=20&\$filter=parentFolderId eq 'inbox'" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json"
# Fetch a single message with body
curl -X GET \
"https://graph.microsoft.com/v1.0/me/messages/MESSAGE_ID?\$select=subject,body,from,receivedDateTime" \
-H "Authorization: Bearer ACCESS_TOKEN"$select and Accept headers.IMAP
Universal ProtocolIMAP (Internet Message Access Protocol) is the universal email protocol supported by virtually every mail server. It is not a REST API but a stateful TCP connection over port 993 (TLS). You issue commands like FETCH, SEARCH, and IDLE over the connection. For a deeper dive, see our IMAP API integration guide.
import imaplib, email
# Connect and authenticate
mail = imaplib.IMAP4_SSL('imap.example.com')
mail.login('user@example.com', 'app_password')
mail.select('INBOX')
# Search for unseen messages
status, message_ids = mail.search(None, 'UNSEEN')
for msg_id in message_ids[0].split():
_, data = mail.fetch(msg_id, '(RFC822)')
msg = email.message_from_bytes(data[0][1])
print(msg['subject'], msg['from'])IDLE command, which keeps a connection open and waits for server notifications. This does not scale easily beyond a few hundred concurrent accounts.Yahoo Mail, ProtonMail, Zoho, and other providers all support IMAP as a universal fallback, so they are covered by the IMAP approach above. Some (like Yahoo) also expose limited proprietary APIs, but none match the capabilities of the Gmail API or Microsoft Graph. For most multi-provider products, IMAP is the universal fallback for any mailbox not covered by Gmail or Outlook OAuth. A unified email API handles this negotiation automatically.
The Hidden Complexity of Reading Emails at Scale
Building a read email API integration against a single provider for a demo takes an afternoon. Building one that works reliably across all providers at production scale is a different challenge entirely. Here is what the engineering actually involves.
OAuth Flow Per Provider
Each provider has its own OAuth 2.0 implementation, consent screen requirements, scopes, and token lifecycle. Supporting Gmail and Outlook means maintaining two separate OAuth apps, two developer consoles, two token refresh strategies, and two sets of compliance requirements (Google CASA Tier 2, Microsoft Publisher Verification).
Pagination Differences
Each provider paginates differently. Gmail uses opaque page tokens. Microsoft Graph uses OData $skip and nextLink cursors. IMAP uses numeric UID ranges. Implementing a consistent pagination abstraction across all three requires non-trivial adapter code.
MIME Parsing
Emails arrive as raw MIME documents with nested multipart boundaries, base64 or quoted-printable encoding, multiple character sets, and inline attachments. Gmail returns base64url-encoded parts. IMAP returns raw RFC 822. Neither gives you a clean plain-text body without parsing the full MIME tree.
Rate Limits and Backoff
Gmail enforces 250 quota units per user per second (listing costs 5 units, fetching 5 units). Microsoft Graph throttles at 10,000 requests per 10 minutes per app per tenant. Both return 429 errors that require exponential backoff with jitter. At 1,000 linked accounts, rate limit management becomes a full engineering problem.
Real-Time: Webhooks vs Polling vs IDLE
Getting notified when a new email arrives requires completely different mechanisms per provider. Gmail uses Google Pub/Sub push subscriptions that must be renewed every 7 days. Microsoft Graph uses change notification subscriptions with a maximum lifetime of 4,230 minutes. IMAP uses the IDLE command, which keeps a persistent TCP connection open per account.
Thread Inconsistencies Across Providers
Gmail groups messages into threads natively. Microsoft Graph has a conversationId field but threads behave differently from Gmail. IMAP has no native threading - you reconstruct threads by matching References and In-Reply-To headers manually. Building a cross-provider unified thread view requires significant normalization logic.
Read Email API Architecture: 3 Approaches Compared
There is no single "correct" architecture for reading emails. The right approach depends on how many providers you need to support, your engineering bandwidth, and your scale requirements. Here is an honest comparison.
| Approach | Pros | Cons | When to use |
|---|---|---|---|
| Direct provider APIs Gmail API, Microsoft Graph |
Free tier, full feature access, no intermediary latency | OAuth setup per provider, MIME parsing, separate rate limit management, no cross-provider normalization | Single provider only |
| Self-hosted IMAP imaplib, node-imap |
Universal protocol, works with any mailbox, no OAuth app required | Stateful TCP connections, no push notifications (polling or IDLE), connection pool management, slow at scale | Legacy / on-prem only |
| Unified read email API Unipile |
One endpoint for all providers, normalized JSON response, managed OAuth, unified webhooks, built-in retry logic | Additional API call cost per linked account, external dependency | Multi-provider products |
If every user in your product uses Gmail, build directly against the Gmail API. You get full feature parity, no additional cost, and access to Gmail-specific features like labels, threads, and Pub/Sub push.
On-premises mail servers, enterprise Exchange deployments without Graph API access, or any scenario where OAuth is not available. Use IMAP as a fallback, not a primary strategy for new products.
When your users have Gmail, Outlook, and IMAP mailboxes and you need one consistent read email API across all of them, a unified layer like Unipile eliminates the N-provider integration problem entirely.
Reading Emails with Unipile's Unified Read Email API
Unipile abstracts Gmail API, Microsoft Graph, and IMAP behind a single read email API. One OAuth flow, one endpoint, one normalized JSON shape - regardless of which provider your user's mailbox lives on. Here is how to get your first inbox read in under 5 minutes.
Generate a hosted authentication URL from your Unipile dashboard. Send this link to your user - they complete the OAuth consent flow for their provider (Gmail, Outlook, or IMAP credentials) on Unipile's hosted page. No OAuth app setup, no redirect URI configuration on your end. See the unified email API guide for the full auth flow.
# Generate a hosted auth link for your user
curl -X POST \
"https://api3.unipile.com:PORT/api/v1/hosted/accounts/link" \
-H "X-API-KEY: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"EMAIL","name":"user@example.com","success_redirect_url":"https://yourapp.com/connected"}'
# Response: { "url": "https://auth.unipile.com/..." }
# Send this URL to your user - they complete OAuth on their providerOnce the user has linked their account, call the emails endpoint with their account_id. The response is identical whether the underlying mailbox is Gmail, Outlook, or IMAP.
# List inbox emails (works for Gmail, Outlook, and IMAP)
curl -X GET \
"https://api3.unipile.com:PORT/api/v1/emails?account_id=ACCOUNT_ID&limit=50" \
-H "X-API-KEY: YOUR_API_KEY"
# Filter by folder
curl "...?account_id=ACCOUNT_ID&folder=INBOX&limit=50" -H "X-API-KEY: YOUR_API_KEY"
# Filter unread only
curl "...?account_id=ACCOUNT_ID&unread=true" -H "X-API-KEY: YOUR_API_KEY"Fetch a full email by ID. Unipile returns a decoded, normalized object with plain text body, HTML body, and attachment metadata - no MIME parsing required on your end.
# Fetch a single email with full body + attachments
curl -X GET \
"https://api3.unipile.com:PORT/api/v1/emails/EMAIL_ID" \
-H "X-API-KEY: YOUR_API_KEY"
# Response fields (always normalized):
# { "id", "subject", "date", "from_attendee", "to_attendees",
# "body", "body_plain", "attachments": [{ "id", "filename", "size" }] }Register a webhook endpoint in your Unipile dashboard. Unipile abstracts Gmail Pub/Sub, Microsoft Graph change notifications, and IMAP IDLE into a single email.received event. No subscription renewal, no IDLE connection pool to manage.
// Unipile calls your endpoint when a new email arrives
// Same event for Gmail, Outlook, and IMAP users
app.post('/webhooks/email', (req, res) => {
const { event, account_id, email_id } = req.body;
if (event === 'email.received') {
// Fetch the full email details
processNewEmail(account_id, email_id);
}
res.sendStatus(200);
});The full Email API Guide covers authentication, all endpoints, pagination, attachment downloads, security, and compliance in detail.
Reading Emails: Code Examples by Language
Production-ready snippets for reading emails with Unipile's unified read email API. All examples read from Gmail, Outlook, and IMAP accounts with the same code.
import fetch from 'node-fetch';
const BASE = 'https://api3.unipile.com:PORT/api/v1';
const API_KEY = process.env.UNIPILE_API_KEY!;
interface Email {
id: string;
subject: string;
date: string;
from_attendee: { display_name: string; identifier: string };
body: string;
body_plain: string;
attachments: { id: string; filename: string; size: number }[];
}
// List inbox emails - works for Gmail, Outlook, and IMAP
async function listEmails(accountId: string, limit = 50) {
const res = await fetch(
`${BASE}/emails?account_id=${accountId}&limit=${limit}&folder=INBOX`,
{ headers: { 'X-API-KEY': API_KEY } }
);
const data = await res.json();
return data.items as Email[];
}
// Fetch a single email with full body + attachments
async function getEmail(emailId: string) {
const res = await fetch(`${BASE}/emails/${emailId}`,
{ headers: { 'X-API-KEY': API_KEY } }
);
return await res.json() as Email;
}
// Usage
const emails = await listEmails('acc_01abc...');
emails.forEach(e => console.log(e.subject, e.from_attendee.identifier));import os
import requests
BASE = "https://api3.unipile.com:PORT/api/v1"
HEADERS = {"X-API-KEY": os.environ["UNIPILE_API_KEY"]}
def list_emails(account_id: str, limit: int = 50) -> list:
"""List inbox emails - works for Gmail, Outlook, and IMAP."""
resp = requests.get(
f"{BASE}/emails",
headers=HEADERS,
params={
"account_id": account_id,
"limit": limit,
"folder": "INBOX",
},
)
resp.raise_for_status()
return resp.json()["items"]
def get_email(email_id: str) -> dict:
"""Fetch a single email with body and attachments."""
resp = requests.get(f"{BASE}/emails/{email_id}", headers=HEADERS)
resp.raise_for_status()
return resp.json()
# Usage
emails = list_emails("acc_01abc...")
for email in emails:
print(email["subject"], email["from_attendee"]["identifier"])
# Fetch full body for first email
full = get_email(emails[0]["id"])
print(full["body_plain"])package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
)
type Email struct {
ID string `json:"id"`
Subject string `json:"subject"`
BodyPlain string `json:"body_plain"`
}
type ListResponse struct {
Items []Email `json:"items"`
}
func listEmails(accountID string) ([]Email, error) {
url := "https://api3.unipile.com:PORT/api/v1/emails?account_id=" + accountID
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("X-API-KEY", os.Getenv("UNIPILE_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
defer resp.Body.Close()
var result ListResponse
json.NewDecoder(resp.Body).Decode(&result)
return result.Items, nil
}
func main() {
emails, _ := listEmails("acc_01abc...")
for _, e := range emails {
fmt.Println(e.Subject)
}
}Real-Time Email Reading: Webhooks vs Polling
Knowing when a new email arrives is as important as being able to read it. There are three mechanisms available, each with very different operational characteristics at scale.
Polling
Your application calls the list emails endpoint on a timer (every 30s, every 5 minutes). Simple to implement but burns quota, introduces latency, and does not scale beyond a handful of accounts.
Native Provider Webhooks
Gmail Pub/Sub and Microsoft Graph change notifications push events to your server immediately. Fast and quota-efficient - but each requires separate setup, separate renewal logic, and different event schemas.
Unipile Unified Webhooks
Unipile abstracts all provider push mechanisms behind a single email.received event. One endpoint receives notifications for Gmail, Outlook, and IMAP accounts alike - with automatic subscription renewal handled internally.
Under the hood, Unipile manages Gmail Pub/Sub subscriptions, Microsoft Graph change notification subscriptions, and IMAP IDLE connections - per linked account, automatically renewed. Your application registers one webhook URL and receives one normalized event regardless of the underlying provider.
Security and Compliance When Reading User Emails
Accessing user emails carries significant security and legal responsibilities. Here are the four areas you must address before shipping a read email API integration to production.
OAuth Scope Minimisation
Always request the minimum OAuth scope required. For reading emails, use read-only scopes - never request send or compose permissions if your application only needs inbox access. Gmail read-only scope is gmail.readonly. Microsoft Graph equivalent is Mail.Read. Requesting broad scopes triggers stricter Google and Microsoft review processes and reduces user trust at the consent screen.
Token Storage Best Practices
OAuth access tokens and refresh tokens are credentials. Store them encrypted at rest using AES-256 or equivalent, never in plaintext in your database. Rotate encryption keys on a schedule. Never log tokens in application logs. Implement token revocation when a user disconnects their account - call the provider's revocation endpoint, do not simply delete the database record.
GDPR and Data Residency
Email bodies often contain personal data covered by GDPR. You must document in your privacy policy exactly what email data you collect, retain, process, and for how long. Implement a data deletion flow that removes email content when a user requests erasure. If you store email content in your own infrastructure, consider data residency requirements for EU customers.
Google CASA and Microsoft Publisher Verification
Applications requesting sensitive Gmail scopes (including gmail.readonly) must complete Google's CASA Tier 2 security assessment before being allowed beyond the 100-user test cap. Microsoft requires Publisher Verification for apps requesting certain Graph scopes. Both processes take weeks - plan accordingly before your launch date. Using Unipile inherits these certifications from the platform layer.
Unipile handles Gmail CASA Tier 2 certification, Microsoft Publisher Verification, SOC 2 Type II audit, and GDPR data processing agreements at the platform level. Products built on Unipile inherit these certifications rather than completing them independently.
Read Email API Pricing: Free Tiers and Cost Models
The free tiers for native provider APIs are generous at low scale, but the hidden costs appear when you need to support multiple providers, manage token refresh at volume, or hit rate limit ceilings. Here is an honest breakdown.
| Provider | Free tier | Cost model | Rate limit | Hidden costs |
|---|---|---|---|---|
| Gmail API | Free with quotas | Quota units per request. No per-account billing | 250 units/sec/user, 1B units/day | CASA Tier 2 review, MIME parsing, Pub/Sub renewal logic |
| Microsoft Graph | Free with throttling | Included with Microsoft 365 tenant. No per-call fee | 10,000 req/10 min/app/tenant | Publisher Verification process, subscription renewal, per-tenant OAuth apps |
| IMAP (self-hosted) | Free protocol | No API cost. Infrastructure cost for connection pools | Provider-specific, ~10-20 connections/account | Server infrastructure, IDLE connection management, no push support |
| Unipile | 7-day free trial | Per linked account per month. See free email API tier | Managed internally, built-in retry logic | API call cost per account - offset by eliminated OAuth/MIME engineering |
Starting with a free read email API? The Gmail API and Microsoft Graph both offer free access within quota limits. Unipile offers a 7-day trial with no credit card required, covering Gmail, Outlook, and IMAP linked accounts.
Start free trialCommon Pitfalls When Building a Read Email Integration
These are the mistakes that consistently cause production incidents for teams shipping their first read email API integration. Learn them before you hit them.
Gmail's 250 quota units per second per user sounds generous until you have 500 accounts and need to do an initial inbox sync. Listing 500 messages costs 2,500 units; fetching each full message costs another 2,500. Initial syncs for large mailboxes can exhaust daily quotas in hours.
OAuth refresh tokens expire silently - Google revokes them after 6 months of inactivity, after a password change, or when a user revokes access from their Google Account settings. If your token refresh logic does not detect a 401 error and surface it to the user, your application will simply stop reading emails with no visible error.
last_sync_at timestamp and alert when it exceeds your expected sync interval.Gmail labels and IMAP folders are not equivalent concepts. Gmail's INBOX label does not include messages that have been archived (removed from INBOX but not deleted). Microsoft Graph's inbox folder excludes focused inbox vs other pane splits unless you query both. Teams frequently discover they are missing 20-40% of messages due to mismatched folder assumptions.
ALL messages (not just INBOX) and filtering by date on your side.Email bodies arrive in a wide range of character encodings: UTF-8, ISO-8859-1, Windows-1252, Shift-JIS. Gmail returns base64url-encoded parts. IMAP returns quoted-printable encoded parts. Decoding one encoding as another produces corrupted body text that is invisible in your local test environment (which likely only sends ASCII emails).
Content-Transfer-Encoding and re-encode to UTF-8. Test with Japanese, Arabic, and emoji-heavy email content specifically.Building a unified thread view across Gmail, Outlook, and IMAP accounts requires normalising three completely different threading models. Gmail has native thread IDs. Outlook has conversation IDs that behave differently. IMAP has no native threading at all - you reconstruct threads from Message-ID, References, and In-Reply-To headers, which are not always present or correct.
Read Email API - Frequently Asked Questions
The most common questions from developers building their first read email API integration.
gmail.readonly scope, obtain an access token via OAuth consent, then call GET /gmail/v1/users/me/messages to list messages and GET /users/me/messages/{id}?format=full to fetch individual emails. Messages are returned as base64url-encoded MIME parts that you must decode.GET /api/v1/emails) that returns normalized JSON for Gmail, Outlook, and IMAP accounts alike. You authenticate users once via a hosted OAuth link, and Unipile handles the provider-specific OAuth flows, MIME parsing, and real-time webhook abstraction behind a single consistent interface. See our Email API Guide for the complete reference./messages/{id}/attachments. IMAP requires parsing the MIME tree to identify attachment parts. With Unipile, attachment metadata (filename, size, MIME type) is included in the email response and attachment content is available via a dedicated endpoint - no MIME parsing required.email.received webhook event that fires for Gmail, Outlook, and IMAP accounts with automatic subscription management handled internally.Retry-After header. Implement exponential backoff with jitter for production reliability. IMAP limits are provider-specific, typically 10-20 concurrent connections per account.