How to Connect OpenClaw to Gmail (with OAuth)
๐ค AI Agent? You can point your AI agent at this article and it can follow the steps to connect automatically. Everything it needs is here: the API endpoints, request/response formats, scopes, and working code samples. The TapAuth API is two calls โ
POST https://tapauth.ai/api/v1/grantsto create a grant, thenGET https://tapauth.ai/api/v1/token/{id}(with Bearer auth) to retrieve the access token. No API key needed โ your agent can follow the code examples below end-to-end.
OpenClaw is the open-source agent runtime that lets you build, deploy, and orchestrate AI agents with persistent identity and real tool access. It's the backbone for agents that do real work โ managing infrastructure, coordinating tasks, handling operations. But the moment an OpenClaw agent needs to read an email, send a reply, or process an inbox, it hits Google's OAuth wall. And Google's OAuth implementation has some of the trickiest gotchas in the industry.
TapAuth was built to solve exactly this. TapAuth is the token layer for AI agents โ one API call, and your OpenClaw agent gets a valid, scoped, user-approved Gmail token. No OAuth client setup. No redirect handling. No wrestling with Google's refresh token policies. Your agent asks for a token and gets one.
This guide walks through the manual Google OAuth flow for Gmail access, then shows how TapAuth simplifies it to two API calls. If you've implemented Google OAuth before, you already know where the pain is.
Why Google OAuth is deceptively hard for agents
Google's OAuth implementation follows the spec more closely than Slack's, but it introduces its own set of traps โ especially for AI agents that run unattended:
- One-shot refresh tokens. Google only issues a refresh token on the first authorization. If you don't capture and store it during the initial OAuth flow, you won't get another one unless the user revokes and re-authorizes. For an agent that might restart, redeploy, or lose state, missing that first refresh token means starting the entire auth flow over.
- The
access_type=offlinetrap. To get a refresh token at all, you must passaccess_type=offlinein the authorization URL. Forget this parameter (easy to do since it's not in the OAuth spec โ it's Google-specific) and you get an access token that expires in an hour with no way to renew it. Your agent works for 60 minutes, then dies. - Consent screen verification. If your app uses sensitive Gmail scopes like
gmail.modifyorgmail.readonly, Google requires your OAuth consent screen to go through a verification process. Until verified, only test users you've explicitly added can authorize. An unverified app shows a scary warning screen that most users won't click through. - Scope sensitivity tiers. Gmail scopes are classified as "restricted" by Google. Using them triggers additional security review requirements, including a third-party security assessment for production apps. This can take weeks and cost thousands of dollars โ a massive barrier for an agent that just needs to read emails.
- Token expiry with no warning. Google access tokens expire after exactly 3,600 seconds. There's no "expiring soon" signal โ the token works, then it doesn't. Your agent needs proactive refresh logic, not reactive error handling, or it'll hit 401s in the middle of operations.
These aren't obscure edge cases. They're the standard experience of connecting anything to Gmail via OAuth. For an OpenClaw agent that runs autonomously, each one of these can cause silent failures at 3 AM when nobody's watching.
The manual Gmail OAuth flow
Here's what implementing Google OAuth looks like for an OpenClaw agent. This covers the happy path โ production deployments need token persistence, refresh logic, and error recovery layered on top.
import express from 'express';
import { google } from 'googleapis';
const app = express();
const oauth2Client = new google.auth.OAuth2(
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET,
'http://localhost:3000/google/callback'
);
// Step 1: Generate authorization URL
// Must include access_type=offline or you won't get a refresh token
app.get('/google/auth', (req, res) => {
const authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline', // Google-specific โ REQUIRED for refresh tokens
prompt: 'consent', // Force consent to ensure refresh token is issued
scope: [
'https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/gmail.send',
],
});
res.redirect(authUrl);
});
// Step 2: Handle the OAuth callback
app.get('/google/callback', async (req, res) => {
const code = req.query.code as string;
if (!code) {
res.status(400).send('Authorization failed');
return;
}
try {
// Step 3: Exchange code for tokens
const { tokens } = await oauth2Client.getToken(code);
// CRITICAL: Save the refresh token NOW
// Google only sends it once โ on the first authorization
if (tokens.refresh_token) {
await saveRefreshToken(tokens.refresh_token);
console.log('Refresh token captured and stored');
} else {
console.warn('No refresh token received โ was this a re-authorization?');
// You might need to revoke and re-auth to get a new refresh token
}
oauth2Client.setCredentials(tokens);
res.send('OpenClaw agent connected to Gmail!');
} catch (error) {
console.error('Token exchange failed:', error);
res.status(500).send('Authentication failed');
}
});
// Step 4: Use the token โ with proactive refresh handling
async function listRecentEmails() {
// Check if access token is expired or about to expire
const tokenInfo = oauth2Client.credentials;
if (tokenInfo.expiry_date && tokenInfo.expiry_date < Date.now() + 60000) {
// Refresh proactively โ don't wait for a 401
const { credentials } = await oauth2Client.refreshAccessToken();
oauth2Client.setCredentials(credentials);
}
const gmail = google.gmail({ version: 'v1', auth: oauth2Client });
const response = await gmail.users.messages.list({
userId: 'me',
maxResults: 10,
q: 'is:unread',
});
return response.data.messages || [];
}
// Step 5: Read an email
async function readEmail(messageId: string) {
const gmail = google.gmail({ version: 'v1', auth: oauth2Client });
const message = await gmail.users.messages.get({
userId: 'me',
id: messageId,
format: 'full',
});
return message.data;
}
app.listen(3000);
Notice the defensive coding required: forcing prompt: 'consent' to ensure a refresh token, proactively checking expiry before every API call, and the warning when no refresh token comes back. This is all Google-specific logic that has nothing to do with reading emails. It's pure OAuth plumbing.
The TapAuth approach: two API calls, any provider
TapAuth is an access gateway for AI agents. It absorbs all provider-specific complexity โ Google's one-shot refresh tokens, Slack's nested responses, GitHub's device flow โ behind the same simple interface.
// Step 1: Create a grant โ tell TapAuth what you need
const grantResponse = await fetch('https://tapauth.ai/api/v1/grants', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TAPAUTH_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
provider: 'google',
scopes: ['gmail.readonly', 'gmail.send'],
}),
});
const { grant_id, grant_secret, approve_url } = await grantResponse.json();
// Step 2: User approves access (show this URL once)
console.log(`Approve Gmail access: ${approve_url}`);
// Step 3: Retrieve the token after approval
const tokenResponse = await fetch(
`https://tapauth.ai/api/v1/token/${grant_id}`,
{
headers: { 'Authorization': `Bearer ${grant_secret}` },
}
);
const { access_token } = await tokenResponse.json();
// Use the token directly with Gmail's API
const response = await fetch(
'https://gmail.googleapis.com/gmail/v1/users/me/messages?maxResults=10&q=is:unread',
{
headers: { Authorization: `Bearer ${access_token}` },
}
);
const emails = await response.json();
No Google OAuth client setup. No consent screen verification. No worrying about one-shot refresh tokens or proactive expiry checks. TapAuth manages the refresh token lifecycle โ when your agent requests a token, it always gets a valid one. The same two API calls work for connecting OpenClaw to Slack, GitHub, Notion, or any other provider.
For OpenClaw agents specifically, this is a natural fit. OpenClaw agents run autonomously and persistently โ they need tokens that work at 3 AM without human intervention. TapAuth's token management ensures that refresh tokens are always captured, stored securely, and used to issue fresh access tokens on demand.
What you can build with OpenClaw + Gmail
OpenClaw agents are designed for persistent, autonomous operation. With Gmail access, that opens up workflows that run continuously:
- Inbox triage agent: An OpenClaw agent that monitors your inbox, categorizes incoming mail, drafts responses to routine messages, and flags anything that needs human attention. It runs in the background, processing mail as it arrives.
- Customer support router: An agent that reads support emails, classifies them by urgency and topic, pulls relevant context from your knowledge base, and routes them to the right team โ or drafts a response for simple questions.
- Daily briefing: Every morning, your OpenClaw agent reads your unread emails, cross-references them with your calendar, and posts a prioritized summary to your preferred channel. "3 emails need replies today. The contract from Acme needs signing by Friday."
- Email-to-task pipeline: An agent that watches for emails matching certain patterns (invoices, action items, meeting follow-ups) and automatically creates tasks in Linear, Asana, or your project management tool of choice.
These are the kinds of workflows where OAuth failures are most painful โ the agent runs unattended, and a token expiry at 2 AM means eight hours of missed processing before anyone notices. TapAuth's automatic token refresh eliminates that failure mode entirely.
Scopes and permissions: what to request for Gmail
Google's Gmail scopes are classified as "restricted," which means extra scrutiny. Request the minimum your agent needs:
| Scope | What it allows | When to use it |
|---|---|---|
gmail.readonly | Read emails and metadata | Inbox monitoring, email summaries, triage |
gmail.send | Send emails (not modify existing) | Automated replies, notifications |
gmail.modify | Read, send, and modify (labels, archive) | Full inbox management, categorization |
gmail.labels | Create and manage labels | Custom categorization systems |
Start with gmail.readonly for agents that only need to read and summarize. Add gmail.send when the agent needs to send messages. Avoid gmail.modify unless your agent genuinely needs to archive, label, or delete messages โ broader scopes mean broader risk if something goes wrong.
TapAuth lets you configure scopes per grant, so your inbox monitoring agent can have read-only access while your email responder gets send permissions. Separate grants, separate trust boundaries, same simple API.
Let TapAuth handle Google's OAuth complexity
Google's OAuth is deceptively standard on the surface but full of gotchas underneath โ one-shot refresh tokens, mandatory offline access parameters, restricted scope reviews, and silent token expiry. For an OpenClaw agent that runs autonomously, any of these can cause silent failures that go unnoticed for hours.
TapAuth absorbs all of this complexity. Your OpenClaw agent makes two API calls โ create a grant, retrieve the token โ and gets a working Gmail token every time. Token refresh happens automatically. Scope management is per-grant. No Google Cloud Console setup in your agent code.
Get started with TapAuth โ connect your OpenClaw agent to Gmail in under five minutes and build the email workflows your users actually need.