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://localhostas 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:
- Your server sends the Refresh Token to Google's OAuth2 endpoint
- Google returns a temporary Access Token (valid for 1 hour)
- Your server uses the Access Token to call the Gmail API send endpoint
- 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.sendis 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.