All posts
Guide·Jul 4, 2026·7 min read

Gmail API send email: a working Python and Node.js guide

Gmail API send email tutorial with working Python and Node.js code, OAuth setup, base64url encoding, quotas, and when an API-first inbox fits better.

Dvir Atias

Dvir Atias

Founder

The Gmail API send email flow is short: enable the Gmail API in a Google Cloud project, create OAuth credentials, get a token carrying the gmail.send scope, then POST a base64url-encoded RFC 2822 message to the users.messages.send endpoint. In Python that is a few lines with google-api-python-client; in Node.js it is the googleapis package. The hard part is the one-time OAuth consent screen and keeping tokens fresh - not the send call itself.

This guide walks the full Gmail API send email flow end to end, with code you can paste and run, and then it gets honest about where the Gmail API stops being the right tool - specifically for AI agents and multi-inbox automation.

What do you need before you can send email with the Gmail API?

Setup is a one-time thing per project. Do these in order:

  1. Create or pick a Google Cloud project and enable the Gmail API from the API Library.
  2. Configure the OAuth consent screen. Choose External (any Google account) or Internal (Workspace only), add the https://www.googleapis.com/auth/gmail.send scope, and add test users while the app is unverified.
  3. Create OAuth client credentials (Desktop or Web application) and download the credentials.json file.
  4. Run the OAuth flow once to exchange the user's consent for an access token plus a refresh token, and store the refresh token so you never have to show the consent screen again.

The gmail.send scope is the least-privileged option - it can send but cannot read the mailbox. It is also a restricted scope, which means a production app that ships to real users needs to pass Google's OAuth verification and, for restricted scopes, an annual security assessment.

How do you send an email with the Gmail API in Python?

With the token from step 4 stored as token.json, the send itself is short. The one detail people miss is that the message body must be a full RFC 2822 message, base64url-encoded, in the raw field:

import base64
from email.message import EmailMessage
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build

creds = Credentials.from_authorized_user_file(
    "token.json",
    ["https://www.googleapis.com/auth/gmail.send"],
)
service = build("gmail", "v1", credentials=creds)

message = EmailMessage()
message["To"] = "recipient@example.com"
message["From"] = "me"
message["Subject"] = "Sent with the Gmail API"
message.set_content("Hello from the Gmail API.")

raw = base64.urlsafe_b64encode(message.as_bytes()).decode()
sent = service.users().messages().send(userId="me", body={"raw": raw}).execute()
print(sent["id"])

urlsafe_b64encode is what produces base64url (the URL and filename-safe alphabet). Standard base64 will be rejected. When the stored credentials carry a refresh token, google-api-python-client refreshes the expired access token for you on the next call, so you do not manage token expiry by hand.

How do you send email with the Gmail API in Node.js?

The Node flow mirrors the Python one. Assume oAuth2Client is already authorized with the gmail.send scope from your OAuth callback:

import { google } from "googleapis";

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

const message =
  "To: recipient@example.com\r\n" +
  "Subject: Sent with the Gmail API\r\n" +
  "Content-Type: text/plain; charset=utf-8\r\n\r\n" +
  "Hello from the Gmail API.";

const raw = Buffer.from(message)
  .toString("base64")
  .replace(/\+/g, "-")
  .replace(/\//g, "_")
  .replace(/=+$/, "");

const res = await gmail.users.messages.send({
  userId: "me",
  requestBody: { raw },
});
console.log(res.data.id);

Node has no built-in base64url encoder for this shape, so the three replace calls convert standard base64 into base64url and strip padding. Pass oAuth2Client a stored refresh token and googleapis handles the access-token refresh automatically.

Gmail API send email quotas and limits

Two different ceilings apply and people confuse them. The API quota governs how fast you can call the endpoint. The sending quota governs how many messages the account is allowed to send per day, and it is tied to the account type, not the API.

LimitValue (as of July 2026)
messages.send cost100 quota units per call
Per-user rate limit250 quota units per user per second (moving average)
Free Gmail daily sendabout 500 messages per day
Google Workspace daily sendabout 2,000 messages per day
Recipients per messageup to 500 via the API
Auth scopehttps://www.googleapis.com/auth/gmail.send
EndpointPOST /gmail/v1/users/{userId}/messages/send

Note that Google revised the Gmail API quota model on May 1, 2026. Cloud projects that used the API between November 2025 and April 2026 keep their previous quotas; projects created on or after that date fall under the new limits. When you exceed the per-second rate you get a 429, which is safe to retry with backoff. When you hit the daily send cap you get a 403 dailyLimitExceeded, which cannot be retried until the rolling 24-hour window resets.

When is the Gmail API the wrong tool for sending email?

The Gmail API is the right choice when you are acting on a user's own mailbox with their consent - a client that reads and sends as the signed-in person, a productivity add-on, a CRM that files a rep's real conversations. That is what it was built for, and nothing here changes that.

It becomes the wrong tool the moment your sender is software that needs its own identity at scale. AI agents and multi-inbox automation run straight into the parts of Gmail that assume a human is present:

  • Per-user OAuth. Every mailbox you send from needs its own OAuth grant. There is no clean "give my agent an address" primitive - only "borrow a person's account."
  • Consent screens and verification. The gmail.send scope is restricted, so shipping to real users means Google review and a security assessment. A consent screen is a human interruption in a system meant to remove humans from the loop.
  • Token expiry. Refresh tokens for apps still in testing expire in about seven days, and refresh tokens can be revoked when a user changes their password. Re-auth is another human in the loop.
  • Terms and daily caps. Consumer Gmail is not built for automated or bulk sending. Traffic that looks like a bot gets throttled, flagged, or the account gets suspended, and the 500-per-day cap is a hard wall.

If your real requirement is "give this agent, or each of my users' agents, a working email address," an API-first inbox is a better fit. We wrote about that model in why AI agents need their own email infrastructure.

What an API-first inbox looks like instead

With AgenticEmail, an inbox is a resource you create with one call - no OAuth, no consent screen:

curl -X POST https://api.agenticemail.dev/v1/inboxes \
  -H "Authorization: Bearer $AGENTICEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "username": "support-agent" }'

That returns a live address like support-agent@inbox.agenticemail.dev, and custom domains are supported on paid plans. Sending is a single POST with plain fields, no RFC 2822 assembly and no base64url step:

curl -X POST https://api.agenticemail.dev/v1/inboxes/ibx_123/messages/send \
  -H "Authorization: Bearer $AGENTICEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "to": "user@example.com", "subject": "Hello", "text": "Sent from an agent inbox." }'

Inbound mail arrives as a webhook or a WebSocket event with the message already parsed, and keys can be scoped per inbox so a leaked credential touches one inbox and nothing else. The five-minute version of that setup is in give your agent an inbox, and the free tier is on the pricing page.

Frequently asked questions

Is the Gmail API free?

Yes. There is no per-call charge for the Gmail API itself. You are bound by the sending quotas above and by normal Google Cloud project usage, but the API calls do not cost money.

Can I send email with the Gmail API without OAuth?

Not for a standard Gmail account - the Gmail API requires OAuth 2.0. The only no-consent-screen path is a service account with domain-wide delegation, which works only inside Google Workspace and only after a Workspace admin authorizes it. If you want to send without any OAuth flow, use a dedicated email API instead.

What are the Gmail API sending limits?

Roughly 500 messages per day on a free Gmail account and 2,000 per day on Google Workspace, plus a per-user rate limit of 250 quota units per second, where each send costs 100 units. See the table above for the full list as of July 2026.

Why does my Gmail API message need base64url encoding?

The users.messages.send endpoint takes a complete RFC 2822 message in the raw field, encoded with base64url (the URL and filename-safe alphabet), not standard base64. Standard base64 output will be rejected, which is why the Python and Node samples above use urlsafe_b64encode and the manual character replacements.

Can the Gmail API send on behalf of many users?

Only through per-user OAuth grants, or Workspace domain-wide delegation for accounts inside one organization. There is no way to mint independent sending identities for arbitrary external users. If you need many separate inboxes - one per agent, customer, or task - an API-first inbox is the simpler model.

Talk to a real person