Get your dream built 10x faster

How to integrate Gmail with OpenClaw

We build custom applications 5x faster and cheaper 🚀

Book a Free Consultation
4.9
Clutch rating 🌟
600+
Happy partners
17+
Countries served
190+
Team members
Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Stuck on an error? Book a 30-minute call with an engineer and get a direct fix + next steps. No pressure, no commitment.

How to integrate Gmail with OpenClaw

A correct, production-ready integration between Gmail and OpenClaw is explicit and has three parts: (1) Google Cloud OAuth setup to get a client ID/secret and the right Gmail scopes, (2) an external OAuth callback + token store that exchanges codes for refresh tokens and keeps them in a secure secret store, and (3) a ClawHub-installed OpenClaw skill configured to use those secrets and to call Gmail’s REST API (or receive Pub/Sub push notifications) from the skill runtime. Do not rely on the agent runtime for durable token storage, long-running polling, or webhook receiving — run those components externally and inject credentials into the skill at runtime.

 

High-level steps (one-paragraph checklist)

 

  • Create a Google Cloud project and OAuth consent screen; register an OAuth Client (web) and record client_id and client_secret.
  • Decide scopes (e.g., https://www.googleapis.com/auth/gmail.send, .../gmail.readonly), and request only what you need.
  • Implement an external OAuth flow (auth URL → user grants access → your callback exchanges code for tokens) and persist the refresh token in a secure secret store (ClawHub secrets or an external vault).
  • Install/configure the skill in ClawHub, supplying client_id, client_secret, and a reference to the stored refresh token (in ClawHub secrets or via environment variables) — do not hardcode tokens in code.
  • From the skill, obtain an access token by exchanging the refresh token against Google's token endpoint, then call Gmail’s REST endpoints with Authorization: Bearer .
  • For inbound events, prefer Gmail push via Google Cloud Pub/Sub (requires a Cloud Pub/Sub subscription and a reliable external forwarder if you want webhooks) or use periodic polling outside the agent for reliability.

 

Google setup — OAuth and scopes

 

  • Create a Google Cloud project and configure OAuth consent. Use a production-ready consent screen if you will access non-test user accounts.
  • Create OAuth credentials (type: Web application) and add your external server's OAuth redirect URI (e.g., https://your-server.example.com/oauth2callback).
  • Pick minimal Gmail scopes needed. Common ones:
    • https://www.googleapis.com/auth/gmail.readonly — read-only access
    • https://www.googleapis.com/auth/gmail.send — send email
    • https://www.googleapis.com/auth/gmail.modify — read and modify (labels, etc.)
  • If you need push notifications, set up Gmail watch to a Cloud Pub/Sub topic and grant Gmail the Pub/Sub publisher permission. Beware: Pub/Sub requires an external, durable component to pull and forward notifications to the skill runtime.

 

Implementing the OAuth flow and token storage (vendor-neutral, example code)

 

Run a small external web service to do the OAuth redirect and token exchange. Store the refresh\_token in a secure secret store (ClawHub secrets, HashiCorp Vault, AWS Secrets Manager, or encrypted DB). The agent runtime should only receive the refresh token (or preferably the long-lived secret reference), not the client secret embedded in code.

const express = require('express');
const fetch = global.fetch; // Node 18+; otherwise use node-fetch or axios
const app = express();
app.use(express.urlencoded({ extended: true }));

// Step A: Redirect user to Google's authorization endpoint
app.get('/link-google', (req, res) => {
  const client_id = process.env.GOOGLE_CLIENT\_ID;
  const redirect_uri = process.env.OAUTH_REDIRECT\_URI; // e.g. https://your-server.example.com/oauth2callback
  const scope = encodeURIComponent('https://www.googleapis.com/auth/gmail.readonly https://www.googleapis.com/auth/gmail.send');
  const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${client_id}&redirect_uri=${encodeURIComponent(redirect_uri)}&response_type=code&scope=${scope}&access_type=offline&prompt=consent`;
  res.redirect(authUrl);
});

// Step B: OAuth callback — exchange code for tokens
app.post('/oauth2callback', express.urlencoded({ extended: true }), async (req, res) => {
  const code = req.body.code || req.query.code;
  const tokenRes = await fetch('https://oauth2.googleapis.com/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      client_id: process.env.GOOGLE_CLIENT\_ID,
      client_secret: process.env.GOOGLE_CLIENT\_SECRET,
      code,
      redirect_uri: process.env.OAUTH_REDIRECT\_URI,
      grant_type: 'authorization_code'
    })
  });
  const tokens = await tokenRes.json();
  // tokens includes access_token, refresh_token, expires_in, scope, token_type
  // Persist tokens.refresh\_token securely (external secret store)
  await saveRefreshTokenForUser(req.userId, tokens.refresh\_token); // implement securely
  res.send('Google account linked');
});

app.listen(3000);

Notes:

  • Include access\_type=offline and prompt=consent when you need a refresh token for production refreshes.
  • Store the refresh token securely and record which Google account it belongs to so your skill can use the correct refresh token for the right user.

 

Refreshing tokens and calling Gmail APIs from the skill

 

The skill should not keep long-term state inside the agent runtime. On each invocation, the skill should:

  • Pull the refresh token (or a secret reference) from ClawHub secrets/environment variables.
  • Exchange the refresh_token for an access_token at https://oauth2.googleapis.com/token with grant_type=refresh_token.
  • Call Gmail REST endpoints with Authorization: Bearer <access\_token>.

Example: refresh token exchange and list messages (generic HTTP):

// Refresh token request
POST https://oauth2.googleapis.com/token
Content-Type: application/x-www-form-urlencoded

client_id=...&client_secret=...&refresh_token=REFRESH_TOKEN&grant_type=refresh_token

// Successful response contains access_token and expires_in:
{
  "access\_token": "ya29....",
  "expires\_in": 3599,
  "scope": "...",
  "token\_type": "Bearer"
}

// Call Gmail to list messages
GET https://gmail.googleapis.com/gmail/v1/users/me/messages?maxResults=10
Authorization: Bearer ya29....

 

Handling inbound mail (polling vs push)

 

  • Polling: Implement a scheduled external worker (outside the agent) that uses stored tokens and periodically calls Gmail messages.list. This is simpler but less real-time and must handle quotas/rate limits.
  • Push via Pub/Sub: Use Gmail's watch API to deliver change notifications to a Cloud Pub/Sub topic. You then run a durable subscriber that pulls Pub/Sub messages and forwards minimal events to your skill (or triggers a job). This is recommended for real-time at scale, but requires a Cloud Pub/Sub setup and a reliable forwarder.
  • Do not rely on the OpenClaw agent to be the public webhook endpoint long-term; agent runtimes are not guaranteed to be externally reachable or persistently running — host the webhook receiver on a durable external service and then call the skill or inject events into a queue the skill can process.

 

ClawHub and skill configuration (principles)

 

  • Install the skill in ClawHub and provide configuration values as secrets or environment variables — e.g., GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET (if necessary), and the refresh token reference or secret name.
  • Prefer injecting only the refresh token value or a secret reference into the skill. If ClawHub supports secret reference resolution, use that instead of embedding raw secrets in code/config.
  • Keep any heavy-lifting (scheduling, webhook endpoints, message queues, persistent state) outside the agent runtime. The skill should be mostly stateless and focused on short-lived API calls using valid access tokens.

 

Security and compliance

 

  • Limit scopes to the minimum required.
  • Rotate client secrets and refresh tokens periodically and build token-rotation procedures (re-authenticate users when necessary).
  • Store tokens in an encrypted secret manager; restrict access to only the components that need them.
  • Validate webhooks and inbound requests (verify JWTs, Pub/Sub message authenticity, or use signed payloads) before acting on them.

 

Debugging checklist (practical steps)

 

  • Check HTTP responses from Google: token endpoint errors are explicit (invalid_grant, invalid_client, etc.).
  • Confirm you have a refresh token — Google may not return one if consent wasn’t requested or if the user account is a test account and OAuth consent status is restricted.
  • Verify OAuth redirect URIs exactly match what’s configured in Google Cloud.
  • Confirm scopes granted by the user match what your requests need.
  • Check quota and Gmail API error codes (rateLimitExceeded, userRateLimitExceeded, etc.).
  • Inspect your external server logs for webhook delivery and your ClawHub/skill logs for failed API calls or authentication failures.

 

When to move work outside the agent runtime

 

  • Persistent storage of refresh tokens, user mappings, and message offsets MUST be external.
  • Long-running background jobs (polling Gmail every few seconds/minutes) should run in a scheduler or worker (e.g., Cloud Run, Kubernetes CronJob, AWS Lambda scheduled invocations), not inside the ephemeral agent runtime.
  • Webhook receivers and Pub/Sub subscribers should be deployed to durable services and then forward events to the skill or a processing queue.

 

Minimal example: list 10 messages using Node (after you obtain access token)

 

const fetch = global.fetch; // Node 18+

async function listMessages(accessToken) {
  const res = await fetch('https://gmail.googleapis.com/gmail/v1/users/me/messages?maxResults=10', {
    headers: {
      'Authorization': `Bearer ${accessToken}`
    }
  });
  if (!res.ok) {
    const err = await res.text();
    throw new Error('Gmail API error: ' + err);
  }
  const json = await res.json();
  return json.messages || [];
}

// Usage in the skill runtime:
// // 1) Get refresh token from secret store (injected into env or retrieved via API)
// // 2) Exchange refresh token for access token at oauth2.googleapis.com/token
// // 3) Call listMessages(accessToken)

Book Your Free 30‑Minute Migration Call

Speak one‑on‑one with a senior engineer about your no‑code app, migration goals, and budget. In just half an hour you’ll leave with clear, actionable next steps—no strings attached.

Book a Free Consultation

Troubleshooting Gmail and OpenClaw Integration

1

Why Does OpenClaw Gmail Connector Fail OAuth2 Token Refresh With "invalid_grant" Error?

Direct answer: The Gmail connector fails with "invalid_grant" when Google refuses the refresh request — typically because the refresh token is revoked/expired, never issued (no offline scope), already consumed, or the client credentials/redirect settings don’t match what Google expects. Also check clock skew, consent changes, or storing the wrong token.

 

What to check

 
  • Verify stored refresh_token exists and wasn’t revoked (user revoked access).
  • Ensure offline access & scope (access_type=offline and correct scopes when initial consent happened).
  • Confirm client_id/secret and redirect URI match the Google Console and your OpenClaw env vars.
  • Inspect logs and Google error response for subcodes and timestamps (clock skew).

 

Token refresh example

 
// Node.js fetch refresh example
const params = new URLSearchParams({
  client_id: process.env.GOOGLE_CLIENT_ID,
  client_secret: process.env.GOOGLE_CLIENT_SECRET,
  grant_type: 'refresh_token',
  refresh_token: storedRefreshToken
});
const res = await fetch('https://oauth2.googleapis.com/token', { method: 'POST', body: params });
const body = await res.json();
// handle body.error like "invalid_grant"

2

Why Are Gmail Push Notifications Not Reaching OpenClaw Event Router (HTTP 403/410)?

Direct answer: A 403 or 410 from Google means Google’s push delivery (usually via Cloud Pub/Sub push) tried to POST to your OpenClaw Event Router but the router rejected it or the subscription is invalid/removed. Common root causes are authentication/validation mismatches (OIDC token audience or channel token), an unreachable or mis‑typed HTTPS endpoint, deleted/expired Pub/Sub subscription (410), or the router returning non‑200 responses or failing TLS/ALPN checks.

 

Common Causes & Immediate Checks

 
  • Auth mismatch: Pub/Sub push uses an Authorization JWT if you configured oidcToken. Ensure the router accepts that audience/service account or disable validation temporarily.
  • Endpoint rejected: Router must respond 200 quickly. Inspect router logs for request headers, status codes and body.
  • Subscription gone: 410 often means the push subscription was deleted — confirm in GCP and recreate if needed.
  • TLS or network: Verify public HTTPS, valid certificate, no firewall blocking Google IPs, correct URL path.
  • Gmail watch state: Ensure your Gmail watch call is active and points to the Pub/Sub topic tied to the push subscription.

 

Practical fixes

 
  • Check OpenClaw Event Router logs for the exact header (Authorization / X-Goog-*) and response body.
  • Confirm Pub/Sub pushConfig.oidcToken settings and make the router accept that audience, or use a push endpoint that ignores auth for testing.
  • Recreate the push subscription if it’s deleted; re-run Gmail watch() if notifications stopped.
  • Use curl from outside (simulate Google) to verify endpoint returns 200 and accepts the JWT header.

3

How To Prevent MailSync From Dropping Gmail Labels When Importing Messages Into OpenClaw Schema?

Direct answer: Read Gmail's labelIds when pulling messages (use the Gmail API with appropriate OAuth scopes), map those label IDs to a persistent labels/tags field in your OpenClaw message schema, and include that mapping when creating or updating records so MailSync never discards existing labels.

 

Practical steps

 
  • Ensure OAuth scopes: include gmail.readonly or mail.google.com so labelIds are returned.
  • Fetch labelIds from messages.get and persist them in your schema (array or relation).
  • On update merge incoming labels with existing ones instead of replacing.

 

Example (Node)

 

const {google} = require('googleapis');
// // fetch message with labels
const msg = await gmail.users.messages.get({userId:'me', id:messageId, format:'metadata', fields:'id,labelIds'});
const labels = msg.data.labelIds || [];
// // send to OpenClaw endpoint, preserve on update by merging server-side
await fetch(process.env.OPENCLAW_MESSAGE_ENDPOINT, {
method:'POST',
headers:{'Authorization':Bearer ${process.env.OPENCLAW_API_KEY},'Content-Type':'application/json'},
body:JSON.stringify({messageId:msg.data.id, labels})
});

4

How To Handle Large Gmail Attachments That Time Out OpenClaw AttachmentService And Trigger RetryPolicy?

Direct answer: Detect large Gmail attachments before downloading, stream them out of the agent runtime into external object storage (S3/GCS), return a pointer to the AttachmentService, and adjust the RetryPolicy so timeouts on large downloads are not retried blindly but trigger staged backoff or manual handling.

 

Practical steps

 
  • Pre-check message metadata (sizeEstimate) via Gmail API; set threshold via env var.
  • Offload large downloads: stream into S3/GCS from a worker skill or external service and store a URL/pointer.
  • Change AttachmentService to accept pointers; keep runtime download for small attachments only.
  • Tune RetryPolicy: exponential backoff, mark size-related timeouts as non-retriable, surface error codes for manual retry.
  • Use logs, request IDs and API responses to debug and iterate.
Book a Free Consultation

Still stuck?
Copy this prompt into ChatGPT and get a clear, personalized explanation.

This prompt helps an AI assistant understand your setup and guide you through the fix step by step, without assuming technical knowledge.

AI AI Prompt


Recognized by the best

Trusted by 600+ businesses globally

From startups to enterprises and everything in between, see for yourself our incredible impact.

RapidDev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with.

They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

Arkady
CPO, Praction
Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost.

He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Donald Muir
Co-Founder, Arc
RapidDev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space.

They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Mat Westergreen-Thorne
Co-CEO, Grantify
RapidDev is an excellent developer for custom-code solutions.

We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Emmanuel Brown
Co-Founder, Church Real Estate Marketplace
Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 

This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Samantha Fekete
Production Manager, Media Production Company
The pSEO strategy executed by RapidDev is clearly driving meaningful results.

Working with RapidDev has delivered measurable, year-over-year growth. Comparing the same period, clicks increased by 129%, impressions grew by 196%, and average position improved by 14.6%. Most importantly, qualified contact form submissions rose 350%, excluding spam.

Appreciation as well to Matt Graham for championing the collaboration!

Michael W. Hammond
Principal Owner, OCD Tech

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.Â