Comparisons · September 29, 2026

Gmail MCP Refresh Tokens Expiring Every 7 Days

Why a Gmail MCP refresh token expires every 7 days, what Google requires to stop it, and the other causes that break the same connection on a different clock.

You connected Gmail to an MCP client, it worked for a week, and then every tool call started failing. You re-authorised, it worked for another week, and it failed again. The client logs usually show invalid_grant with “Token has been expired or revoked”, and nothing in your code changed. When a Gmail MCP refresh token expires every 7 days, that is not a bug in the client or in the server. It is Google applying a documented rule to the OAuth project you created during setup.

This article settles which rule it is, what Google’s own pages say it takes to lift it, how to tell it apart from the half-dozen other causes that produce the same error on a different schedule, and what changes if the agent has an inbox of its own instead. If you are still deciding between those two models, start with connecting Gmail versus giving the agent its own inbox. Every Google statement below was read on Google’s pages on 29 September 2026.

Why does the Gmail MCP refresh token expire every 7 days?

Because your Google Cloud OAuth consent screen has a user type of External and a publishing status of Testing. Google’s OAuth 2.0 overview, under “Refresh token expiration”, states it in one sentence: a project “with an OAuth consent screen configured for an external user type and a publishing status of ‘Testing’ is issued a refresh token expiring in 7 days, unless the only OAuth scopes requested are a subset of name, email address, and user profile.”

Gmail scopes are never in that exempt subset, so every Gmail integration in Testing status gets the short token. Google’s Manage App Audience help page describes the same rule from the other side: “Authorizations by a test user will expire seven days from the time of consent. If your OAuth client requests an offline access type and receives a refresh token, that token will also expire.” The clock starts at consent, not at last use. Refreshing every hour does not extend it.

The detail most write-ups miss is how people end up in Testing in the first place. Google’s own Gmail MCP server configuration guide (last updated 18 September 2026, and marked as a Developer Preview that requires membership in the Google Workspace Developer Preview Program) tells you to “select Internal. If you can’t select Internal, select External”, and then, for External, to add yourself under Test users. Internal is only offered to projects associated with a Google Cloud Organization, which a project created under a personal Google account is not, so anyone following that guide without an organisation lands exactly on External plus Testing. The guide never mentions publishing status, production, OAuth app verification or the seven-day expiry. For a project outside an organisation, the setup Google documents for its own Gmail MCP server is the setup that expires.

The scopes in that guide matter too. It asks for gmail.readonly and gmail.compose. Google’s Gmail API scopes page (last updated 10 September 2026) classifies both as Restricted, the tier with the heaviest review, which is why the obvious fix is less obvious than it looks.

Tell the causes apart by when the connection breaks

Google’s page does not document a distinct error for each cause, so the most useful diagnostic you have is timing. The same invalid_grant means different things depending on when it arrived.

When it breaksCause (quoted where the wording is Google’s)Fix
Exactly 7 days after each consentExternal user type, publishing status “Testing”Publish the app, or use Internal (sections below)
About an hour after every loginNo refresh token was issued at all (see the next section)Fix the authorization request, not the consent screen
Right after a password change“The user changed passwords and the refresh token contains Gmail scopes”Re-authorise
Right after a new authorisation elsewhereOver the limit of 100 refresh tokens per Google Account per OAuth client IDStop minting a new token per session or per machine
After six months of inactivity“The refresh token has not been used for six months”Re-authorise; use it at least occasionally
At a time the user chose“The user granted time-based access to your app and the access expired”Re-authorise with a longer grant
Whenever an admin changes a setting“If an admin set any of the services requested in your app’s scopes to Restricted”Ask the Workspace admin to trust the app
At an arbitrary moment“The user has revoked your app’s access”Re-authorise, and ask why

All of these come from the same “Refresh token expiration” section of Google’s OAuth 2.0 overview, which opens with the instruction to write code that anticipates “the possibility that a granted refresh token might no longer work”.

Two of those rows deserve more precision than they usually get.

The 100-token limit is per Google Account per client ID, and it fails silently. Google’s wording is that “creating a new refresh token automatically invalidates the oldest refresh token without warning.” If you run the same OAuth client on a laptop, a desktop and a server, and each re-consents on its own schedule, the 101st consent kills the oldest token with no error at the time. It surfaces later, on whichever machine held the oldest one. Google adds that the limit does not apply to service accounts, and that a larger, unstated limit applies across all clients. Some guides still cite a 50-token cap; Google’s page says 100.

Session control is a Google Cloud setting, not a Gmail one. The same page says administrators “of GCP organizations might require frequent reauthentication of users while they access GCP resources”, and lists the session length as a cause “for Google Cloud Platform APIs”. It does not say this applies to Gmail scopes, so do not diagnose a Gmail MCP failure as a session-control policy unless your integration also calls Cloud APIs. The Workspace-side admin cause Google does list for Gmail is the one about a service being set to Restricted.

The loop that is not the 7-day rule: the client never got a refresh token

If the connection dies roughly an hour after every login rather than a week, publishing status is not your problem. The client never received a refresh token, so it has nothing to renew with once the access token lapses.

Google only issues one when the authorization request asks for it. The web server flow documentation says “the refresh token is only returned if your application set the access_type parameter to offline”, and its Node.js example notes that the refresh token “is only returned on the first authorization”. A second consent without prompt=consent can come back with an access token and nothing else.

MCP clients get this wrong in public, and it is worth knowing what the failure looks like. An issue opened on 18 September 2026 against the oh-my-pi agent, #12438, traced repeated hourly re-authorisation against Google-hosted MCP servers to the client omitting access_type=offline, with stored credentials showing expires - created_at == 3599. The generic MCP form of the same bug is a client that never requests the offline_access scope from an authorization server that gates refresh tokens on it; openclaw #160611 is one such report. Neither is fixable from the Google Cloud console.

The check takes a minute. Look at what the client stored after consent. If there is no refresh_token field at all, fix the authorization request. If there is one and it stops working seven days after consent, go back to the consent screen.

What publishing to production actually requires

Publishing is one button, and for a personal integration Google’s pages are more permissive than many guides suggest. What they require depends on who will sign in.

Pressing Publish. On the Manage App Audience page, a project “is considered In production after selecting the Publish app button.” The button itself does not wait for review. The same page does say a published project “may be subject to verification” before it may request sensitive or restricted scopes, and “should complete the verification process” if it meets Google’s verification criteria, so the question is whether you meet them. The seven-day rule on the OAuth overview is attached to Testing status and nothing else.

What unverified production costs you. Google displays an “unverified apps” warning when an app requests sensitive or restricted scopes before completing verification, and its Unverified apps page caps such an app at “100 new users in total, after the app presents the unverified app screen”. The Manage App Audience page adds that this cap “applies over the entire lifetime of the project, and it cannot be reset or changed.”

When verification is not needed. Google’s When is verification not needed page lists personal use, “fewer than 100 users”, as an exception, noting that those users “will be allowed to click through” the unverified warning. It also lists internal use inside a Workspace or Cloud Identity organisation, and apps an administrator has marked as trusted in the Google Admin console. The exception is not limited to sensitive scopes: the restricted-scope verification page repeats it, saying that if only you, or a few users all known personally to you, use the app, you “might be comfortable with advancing through the unverified app screen”.

So a solo builder wiring their own Gmail into their own MCP client can, by Google’s text, publish the project and click through the warning without entering review, even with the restricted gmail.readonly and gmail.compose scopes. Two limits apply. The account itself must be allowed to authorise an unverified app; the Manage App Audience page notes that a Workspace can control which third-party apps reach its data and that Advanced Protection may block most non-Google apps. And Google does not say that publishing extends tokens already issued under Testing, so authorise once more after you publish rather than relying on an undocumented behaviour.

When it gets expensive. Ship the integration to other people and the restricted scopes take over. Google’s restricted scope verification page (last updated 19 August 2026) requires a security assessment under the App Defense Alliance’s CASA framework for “every app that requests access to Google users’ restricted data and has the ability to access data from or through a third-party server”, repeated “at least every 12 months” after the assessor’s Letter of Assessment. Brand verification “typically takes 2-3 business days”; the restricted-scope process “can potentially take several weeks”. That is the expensive step, and it is the one that applies to the gmail.readonly plus gmail.compose pair Google’s MCP guide uses.

One scope sits outside it. The scopes page lists gmail.send as Sensitive, not Restricted. An integration that only sends, and never reads, faces sensitive-scope verification rather than the restricted-scope assessment. An agent that cannot read its replies is rarely what anyone wants, but it is a real option for a notification sender.

The Internal user type, if you are a Workspace organisation

Internal is the legitimate escape, and only for organisations. Google’s Manage App Audience page says projects “associated with a Google Cloud Organization can configure Internal users to limit authorization requests to members of the organization.” The seven-day rule is scoped to the External user type, and the verification exceptions include internal use where “the project must be owned by the organization, and its OAuth Consent Screen must be configured for internal use.”

Three conditions have to hold at once:

  1. The Google Cloud project belongs to your Workspace or Cloud Identity organisation, not to someone’s personal account.
  2. Every person who will authorise is a member of that organisation.
  3. The Workspace admin has not set Gmail to Restricted for your app, which Google lists as a separate cause of refresh-token failure.

A consumer @gmail.com account meets none of them, which is why the openclaw gogcli issue #121 (opened 27 January 2026) lists moving the mail to paid Workspace as one of its three options. The reporter had assumed Production was blocked by restricted-scope review; the maintainer’s closing answer pointed back to publishing and the personal-use exception above. For a Workspace team connecting its own staff mailboxes, Internal is usually the right answer and it involves no review at all.

What an agent-owned inbox changes, and what it does not

If the mailbox belongs to the agent rather than to a person, Google is not in the authorisation path. There is no Google Cloud project, no consent screen publishing status, no restricted-scope review and no CASA assessment, because no Google account is being accessed. That is the whole of the structural difference, and it is real.

It does not mean the credential lives forever. Our remote MCP endpoint, https://mcp.emailforagents.ai/mcp, accepts only an OAuth connection grant. An API key is rejected there by design, so there is no long-lived static secret to fall back on; the OAuth versus API key trap covers what that rejection looks like. The grant has its own clocks, read from source on 29 September 2026:

So this product also makes you re-consent periodically: every 30 days instead of every 7. That is deliberate, so that a forgotten integration ages out rather than holding access indefinitely, but it means a long-running agent client still needs a human to run the authorisation flow once a month. If your goal was “never re-authorise”, neither route gives you that over MCP.

The differences that remain are about who sets the clock and whether you can see it.

QuestionGmail, TestingGmail, publishedGmail, InternalAgent-owned inbox
Forced re-consent interval7 daysNone statedNone stated30 days
Google review requiredNoBeyond personal useNoNo Google account involved
Works for a consumer Gmail accountYesYesNoNot applicable
Which mailbox the agent operates inA person’sA person’sA person’sIts own

The 30-day expiry is a property of one grant you created, visible on the connection record, and it does not depend on anyone else’s publishing status. One thing we checked because of the hourly-loop bug above: the authorization library this server uses, @cloudflare/workers-oauth-provider 0.10.3, issues a refresh token on the code exchange whenever a refresh lifetime is configured (dist/oauth-provider.js:1955), and this server configures one (refreshTokenTTL: 30 * 24 * 3600, workers/mcp/src/index.ts:135). Nothing on that path checks for offline_access, so a client that forgets to ask for it still gets a refresh token here. Whether a given client then stores and rotates it correctly is something to test, not assume; we do not certify any client.

When the Gmail route is still the right one

Stay with Gmail when the work is in a specific person’s mailbox. If the agent is triaging a founder’s inbox, drafting replies in their existing threads, or searching years of their history, a new address is the wrong tool, and the seven-day problem has solutions that cost less than migrating. Token expiry is also not the only way Google stops an agent that acts through a person’s account; the four enforcement paths behind “Gmail blocked my bot” are worth ruling out before you blame the consent screen.

Pick the fix that matches who you are:

Choose an inbox the agent owns when the agent is a system rather than an assistant to one person: an intake address, a support queue, a fleet of agents that should not each occupy a human’s mailbox. The Gmail API comparison sets out where Google’s side is stronger, including quota and third-party assurance we do not have, and the wider comparisons hub covers the other providers.

If you do move, the client needs remote MCP with OAuth and S256 PKCE for the connection to work at all. The six tools are list_inboxes, list_threads, list_messages, get_message, create_draft and send_message, and the grant’s 30-day expiry is the date to put in your calendar. The integrations page lists the client paths, and revoking an agent’s access covers ending a grant before it expires on its own.


Keep reading

All guides · Documentation · Comparisons