slice icon Context Slice

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 pageToken from response for next page
  • resultSizeEstimate gives 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=Cc

Parallelism: 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:bob
  • from: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 "";
}