Engineering

How to Send Emails from Your Web App Using Gmail API

A complete guide to setting up Gmail OAuth2 for sending emails from your admin dashboard — no third-party email service needed.

The Gmail API lets your web application send emails programmatically from a real Gmail or Google Workspace address using OAuth2 authentication. Unlike third-party services like SendGrid or Resend, emails sent via Gmail API appear in your Sent folder and replies come back to your inbox — making it ideal for low-volume transactional emails from admin dashboards.

This guide walks through the complete setup: creating OAuth2 credentials, obtaining a refresh token, and implementing the send function in TypeScript.

Why Use Gmail API Instead of a Third-Party Email Service?

When building an admin dashboard that needs to reply to contact form submissions, you have three options:

Approach Pros Cons
mailto: links Zero setup Leaves the app, can't track replies
Third-party services (Resend, SendGrid) High deliverability, analytics Monthly cost, emails not in Gmail Sent
Gmail API with OAuth2 Free, emails in Sent folder, real address One-time setup complexity

Gmail API is the best fit when you're a small team sending low-volume emails (under 500/day) and want replies to land in your real inbox.

How to Set Up Gmail API OAuth2

Step 1: Enable the Gmail API

Go to Google Cloud Console → APIs & Services → Library → search "Gmail API" → click Enable.

You need a GCP project. If you're already using Google Cloud for other services (authentication, analytics), use the same project.

Step 2: Create an OAuth2 Desktop Client

In GCP Console → Google Auth Platform → Clients → Create Client:

  • Application type: Desktop app (this allows http://localhost as a redirect URI)
  • Name it something descriptive like my-app-gmail

You'll get a Client ID and Client Secret. Save both — the secret cannot be viewed again later.

Step 3: Obtain a Refresh Token

This is the key step. You authorize your Gmail account once and receive a long-lived refresh token.

Generate the authorization URL:

https://accounts.google.com/o/oauth2/auth
  ?client_id=YOUR_CLIENT_ID
  &redirect_uri=http://localhost
  &response_type=code
  &scope=https://www.googleapis.com/auth/gmail.send
  &access_type=offline
  &prompt=consent

Open this in a browser, sign in with the Gmail account you want to send from, and approve. The browser redirects to localhost?code=AUTHORIZATION_CODE.

Exchange the authorization code for tokens:

curl -X POST "https://oauth2.googleapis.com/token" \
  -d "code=AUTHORIZATION_CODE" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "redirect_uri=http://localhost" \
  -d "grant_type=authorization_code"

The response includes a refresh_token. This token doesn't expire unless you manually revoke it or delete the OAuth client.

Step 4: Store Credentials as Environment Variables

GMAIL_CLIENT_ID=your-client-id
GMAIL_CLIENT_SECRET=your-client-secret
GMAIL_REFRESH_TOKEN=your-refresh-token
GMAIL_SENDER_EMAIL=you@yourdomain.com

Never commit these to source control. Add them to your deployment platform's environment variables (Render, Vercel, etc.).

How Gmail OAuth2 Token Exchange Works at Runtime

Every time your app sends an email, this flow executes:

  1. Your server sends the Refresh Token to Google's OAuth2 endpoint
  2. Google returns a temporary Access Token (valid for 1 hour)
  3. Your server uses the Access Token to call the Gmail API send endpoint
  4. Gmail sends the email from your address

The refresh token never leaves your server. The access token is short-lived and disposable.

How to Send Email with Gmail API in TypeScript

Here's a minimal implementation for a Next.js API route:

async function getAccessToken(): Promise<string> {
  const res = await fetch("https://oauth2.googleapis.com/token", {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      client_id: process.env.GMAIL_CLIENT_ID!,
      client_secret: process.env.GMAIL_CLIENT_SECRET!,
      refresh_token: process.env.GMAIL_REFRESH_TOKEN!,
      grant_type: "refresh_token",
    }),
  });
  const data = await res.json();
  return data.access_token;
}

async function sendEmail(to: string, subject: string, body: string) {
  const accessToken = await getAccessToken();
  const raw = Buffer.from(
    `To: ${to}\r\nSubject: ${subject}\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n${body}`
  ).toString("base64url");

  await fetch("https://gmail.googleapis.com/gmail/v1/users/me/messages/send", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${accessToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ raw }),
  });
}

The email must be RFC 2822 formatted and base64url-encoded. The gmail.send scope only allows sending — it cannot read, delete, or list emails.

Gmail API Security Considerations

  • Minimal scope: gmail.send is the most restrictive Gmail scope — it only permits sending
  • Server-side only: Refresh tokens must stay in server environment variables, never exposed to the browser
  • Google Workspace: Internal apps auto-approve consent, so the authorization flow is seamless
  • Rate limits: Gmail API allows 100 quota units per second. A single send costs 100 units, so effectively 1 email per second

How to Revoke Gmail API Access

Three options, from least to most destructive:

Action How Effect
Remove env var Delete GMAIL_REFRESH_TOKEN from your deploy platform App stops sending, can re-add to restore
Revoke token Google Account → Security → Third-party apps → Remove access Token invalidated, must re-authorize
Delete OAuth client GCP Console → Google Auth Platform → Delete the client All credentials permanently invalidated

Frequently Asked Questions

Does the Gmail API refresh token expire? No. Gmail API refresh tokens are long-lived and don't expire unless you manually revoke them, delete the OAuth client, or the user removes app access from their Google Account settings.

Can I send emails from a Google Workspace address? Yes. This approach works with both regular Gmail and Google Workspace accounts. For Workspace, internal apps auto-approve the OAuth consent screen.

What's the Gmail API daily sending limit? For regular Gmail accounts, the limit is 500 emails per day. For Google Workspace accounts, it's 2,000 emails per day. For higher volumes, use a dedicated email service.

Is this approach free? Yes. The Gmail API itself has no cost. You only pay if you exceed Google Cloud's free tier for API calls, which is unlikely for low-volume email sending.

What happens if I need to send HTML emails? Change the Content-Type header in the raw email from text/plain to text/html, then include your HTML in the body. The Gmail API accepts any valid RFC 2822 email format.


We built this for MachineFriendly's admin dashboard to reply to website migration inquiries directly. If you're building something similar, get in touch.

Stay updated

Get new articles in your inbox.

No spam, just technical articles and product updates.