Gmail API Usage Guide
Authentication
Gmail API uses OAuth2. Token is available via PLACEHOLDER_TOKEN header:
const res = await fetch(url, {
headers: { Authorization: "Bearer PLACEHOLDER_TOKEN" }
});Message Listing (Pagination)
List endpoint returns message IDs only (fast, lightweight):
GET https://gmail.googleapis.com/gmail/v1/users/me/messages
?maxResults=100
&q=<gmail_search_query>
&pageToken=<next_page_token>Best practices:
- Request 100 messages per page (maximum)
- Use
pageTokenfrom response for next page resultSizeEstimategives approximate total matches
Message Fetching (Parallel)
Fetch individual messages with format parameter:
GET https://gmail.googleapis.com/gmail/v1/users/me/messages/{id}
?format=metadata (headers + snippet only, fast)
?format=full (includes body, slower)
?format=raw (RFC 2822 format)With format=metadata, specify which headers:
&metadataHeaders=Subject
&metadataHeaders=From
&metadataHeaders=To
&metadataHeaders=Date
&metadataHeaders=CcParallelism: Fetch 20-25 messages concurrently for optimal throughput:
const CONCURRENCY = 25;
for (let i = 0; i < ids.length; i += CONCURRENCY) {
const batch = ids.slice(i, i + CONCURRENCY);
const results = await Promise.all(batch.map(id => fetchMessage(id)));
}Rate Limits
Gmail API quotas:
- 250 quota units per user per second
- Message list = 5 units
- Message get = 5 units
Stay under ~50 requests/second per user to avoid 429 errors.
Common Search Queries
Gmail search syntax for the q parameter:
| Query | Description |
|---|---|
from:someone@example.com |
From specific sender |
to:someone@example.com |
To specific recipient |
in:sent |
Sent messages |
in:inbox |
Inbox messages |
newer_than:7d |
Last 7 days |
older_than:30d |
More than 30 days ago |
after:2024/01/01 |
After specific date |
has:attachment |
Has attachments |
is:unread |
Unread only |
-category:promotions |
Exclude promotions |
subject:keyword |
Subject contains |
"exact phrase" |
Exact phrase match |
Combine with AND/OR:
from:alice to:bob(implicit AND)from:alice OR from:bobfrom:alice -from:bob(NOT)
Message Structure
Response structure for format=metadata:
{
"id": "...",
"threadId": "...",
"labelIds": ["INBOX", "UNREAD"],
"snippet": "Preview text...",
"sizeEstimate": 1234,
"payload": {
"headers": [
{"name": "Subject", "value": "..."},
{"name": "From", "value": "Name <email@example.com>"},
{"name": "To", "value": "..."},
{"name": "Date", "value": "..."}
]
}
}For format=full, payload.body.data contains base64-encoded content.
MIME parts are in payload.parts for multipart messages.
Parsing Utilities
Extract email from header:
function extractEmail(header) {
if (!header) return "unknown";
const match = header.match(/<([^>]+)>/);
return match ? match[1].toLowerCase() : header.toLowerCase().trim();
}Extract name from header:
function extractName(header) {
if (!header) return "Unknown";
const match = header.match(/^([^<]+)</);
return match ? match[1].trim().replace(/"/g, "") : header.split("@")[0];
}Get header value:
function getHeader(msg, name) {
const header = msg.payload?.headers?.find(
h => h.name.toLowerCase() === name.toLowerCase()
);
return header ? header.value : "";
}Extract body text from MIME:
function extractBodyText(payload) {
if (payload.body?.data) {
return Buffer.from(payload.body.data, "base64").toString("utf-8");
}
if (payload.parts) {
for (const part of payload.parts) {
if (part.mimeType === "text/plain" && part.body?.data) {
return Buffer.from(part.body.data, "base64").toString("utf-8");
}
// Check nested parts
if (part.parts) {
for (const nested of part.parts) {
if (nested.mimeType === "text/plain" && nested.body?.data) {
return Buffer.from(nested.body.data, "base64").toString("utf-8");
}
}
}
}
}
return "";
}