All posts
Guide·Jul 5, 2026·12 min read

The Gmail API: the complete developer guide

Everything about the Gmail API in one place: OAuth setup, scopes, sending, reading, push notifications, quotas, common errors - and when it's the wrong tool.

Dvir Atias

Dvir Atias

Founder

The Gmail API is Google's REST interface to Gmail mailboxes: read, send, search, label, and watch messages in a user's Gmail account. It is the right tool when your software needs to act on a real person's Gmail. It is routinely the wrong tool when developers reach for it because "we need to send/receive email from code" - a job it makes surprisingly painful. This guide covers both sides honestly: how to use it well, and when to use something else.

What the Gmail API actually is

The Gmail API (gmail.googleapis.com) operates on a single Google account's mailbox. Every request acts as a user who granted your app permission via OAuth 2.0. That single fact drives everything that follows:

  • There are no service accounts for consumer Gmail. Domain-wide delegation exists only for Google Workspace, configured by a Workspace admin.
  • Tokens expire and users revoke. Your app must store refresh tokens and survive re-consent flows.
  • Unverified apps hit a wall. Sensitive scopes (gmail.send, and especially the restricted gmail.readonly/gmail.modify) require Google's OAuth app verification - a security review process that can take weeks and, for restricted scopes, an annual third-party assessment for production use.

If your use case is "a user connects their own Gmail to my product," that cost is justified. If it's "my app or agent needs an email address," it isn't - skip to the last section.

  1. Create a project in the Google Cloud console and enable the Gmail API under APIs & Services.
  2. Configure the OAuth consent screen: app name, support email, and the scopes you need. In Testing mode you can add up to 100 test users without verification.
  3. Create an OAuth client ID (type: Web application or Desktop) and download the client secret JSON.

Scopes: request the minimum

ScopeGrantsVerification tier
gmail.sendSend only, no readingSensitive
gmail.readonlyRead everything, no changesRestricted
gmail.modifyRead, labels, trash - not permanent deleteRestricted
gmail.composeCreate drafts and sendSensitive
https://mail.google.com/Full access incl. deleteRestricted

Restricted scopes trigger the heaviest review. A classic mistake is requesting mail.google.com when gmail.send would do - it can add months to your launch.

Sending email

The API takes a full RFC 2822 message, base64url-encoded, in a field called raw:

import { google } from "googleapis";

const gmail = google.gmail({ version: "v1", auth: oauth2Client });

const message = [
  "From: me@gmail.com",
  "To: dest@example.com",
  "Subject: Hello from the Gmail API",
  "Content-Type: text/plain; charset=utf-8",
  "",
  "It works.",
].join("\r\n");

await gmail.users.messages.send({
  userId: "me",
  requestBody: {
    raw: Buffer.from(message).toString("base64url"),
  },
});

Yes - you are assembling MIME by hand (or with a library like nodemailer's MailComposer). HTML bodies, attachments, and threading headers (In-Reply-To, References) are all your responsibility. We cover the send path in more depth - including Python - in Gmail API: send email in Python and Node.js.

Reading and searching mail

Reading is a two-step affair: list returns IDs; you fetch each message separately.

const { data } = await gmail.users.messages.list({
  userId: "me",
  q: "is:unread from:stripe.com", // full Gmail search syntax
  maxResults: 20,
});

for (const { id } of data.messages ?? []) {
  const msg = await gmail.users.messages.get({ userId: "me", id, format: "full" });
  // msg.data.payload is a raw MIME tree: parts, nested parts,
  // base64url bodies, headers as an array of {name, value}...
}

The payload is a MIME tree you must walk yourself: multipart/alternative inside multipart/mixed, base64url-encoded bodies, attachments fetched via a third call (attachments.get). Budget real time for this - or see our guide to receiving email as JSON for what parsed inbound email can look like.

Push notifications (watch + Pub/Sub)

The Gmail API doesn't have webhooks. To react to new mail you must:

  1. Create a Google Cloud Pub/Sub topic and grant gmail-api-push@system.gserviceaccount.com publish rights.
  2. Call users.watch with the topic name.
  3. Handle Pub/Sub pushes - which contain only a historyId, not the message.
  4. Call users.history.list to diff what changed, then fetch messages.
  5. Renew the watch at least every 7 days - it silently expires.

We walk the full setup in Gmail webhook: real-time push when email arrives. It works, but it is five moving parts to get "email arrived → my code runs."

Quotas and limits

  • 250 quota units per user per second (messages.send costs 100 units; messages.get costs 5; list costs 5).
  • 1 billion units/day per project by default - rarely the binding limit.
  • Sending is also capped by the Gmail account's limits: ~500 recipients/day for consumer Gmail, 2,000 for Workspace. The API does not raise these.
  • 429/rateLimitExceeded and 403/userRateLimitExceeded demand exponential backoff.

That last point surprises teams: the Gmail API is not a bulk sender. For product email volumes you need a transactional provider - see our transactional email API guide.

Common errors

ErrorCauseFix
403 accessNotConfiguredAPI not enabled on the projectEnable Gmail API in the console
401 invalid_grantRefresh token revoked/expired (testing-mode tokens expire in 7 days)Re-run consent; publish the app
403 insufficientPermissionsMissing scope for the callRe-consent with the right scope
429 rateLimitExceededPer-user quota burstExponential backoff
400 failedPreconditionConsumer account calling a delegation-only flowUse OAuth user consent

When the Gmail API is the wrong tool

Use the Gmail API when the mailbox belongs to a human who connects their account to your product: an email client, a CRM syncing correspondence, a scheduling assistant reading a user's inbox with permission.

Do not use it to give your application or AI agent an email address. You would be building OAuth consent, token refresh, MIME assembly and parsing, Pub/Sub plumbing, and watch renewals - then submitting to a restricted-scope security review - to end up with a mailbox that is rate-limited for humans and owned by a Google account you have to babysit.

For that job, an email API built for programmatic inboxes is one call:

curl -X POST https://api.agenticemail.dev/v1/inboxes \
  -H "Authorization: Bearer am_..." \
  -d '{"username": "agent"}'

The inbox can send, receive, and reply; inbound mail arrives as parsed JSON over a signed webhook or WebSocket - no Pub/Sub, no MIME tree, no OAuth review. Read why agents need their own email infrastructure or start with the quickstart.

Talk to a real person