the oauth problem: why gmail integration is so painful for agents

the oauth problem: why gmail integration is so painful for agents

OAuth tokens, app passwords, IMAP config, refresh token rotation. Here's why connecting an agent to Gmail is broken and what to do instead.

Samuel Chenard
Samuel ChenardCo-founder

You want your agent to read email. That's it. Read incoming messages, maybe send a reply. Should take five minutes, right?

If you've tried connecting an AI agent to Gmail, you know the answer. What starts as "just set up OAuth" turns into a multi-day detour through Google Cloud console screens, consent screen configurations, token refresh logic, and rate limit errors that show up at 2 AM on a Saturday.

Let's walk through what it actually takes. Not the happy path from the docs. The real path.

Step 1: create a Google Cloud project#

Before your agent can touch a single email, you need a Google Cloud project. Head to console.cloud.google.com, create a new project, and enable the Gmail API. That means navigating to APIs & Services, searching for the Gmail API, clicking Enable, and waiting for provisioning.

Already three clicks deeper than you wanted to be.

This is where things start to get tedious. Google requires you to configure an OAuth consent screen before you can create credentials. You'll fill out your app name, support email, authorized domains, privacy policy URL, and terms of service URL.

Your agent doesn't have a privacy policy. It doesn't have terms of service. It doesn't have a homepage. But Google needs all three before it'll let you proceed.

You also need to choose between "Internal" (only available if you have a Google Workspace account) and "External" user types. Most individual developers pick External — and that's where the trap is.

Warning

External apps in "Testing" mode have refresh tokens that expire after 7 days. Your agent will silently lose access every week until you push the app to production and complete Google's verification process.

Step 3: create OAuth credentials and select scopes#

Now you create an OAuth 2.0 Client ID. You pick "Web application" or "Desktop app" depending on your setup. You configure redirect URIs. You select scopes.

For reading email, the minimum scope is gmail.readonly. But many frameworks request https://mail.google.com/ — the full-access scope — because it's easier than figuring out exactly which granular permissions you need. Google's own documentation notes that you "must only request the smallest set of scopes" necessary, but when you're just trying to get something working, broad permissions win.

https://www.googleapis.com/auth/gmail.readonly
https://www.googleapis.com/auth/gmail.send
https://www.googleapis.com/auth/gmail.modify
https://mail.google.com/

Each scope triggers different verification requirements. Sensitive scopes need review. Restricted scopes (like full mail access) require a third-party security audit. The audit can cost tens of thousands of dollars and take weeks.

Step 4: implement the token exchange#

You have credentials. Now your code needs to:

  1. Redirect the user to Google's auth endpoint
  2. Handle the callback with an authorization code
  3. Exchange that code for an access token and refresh token
  4. Store both tokens securely
  5. Use the access token for API calls
  6. Detect when the access token expires (every 60 minutes)
  7. Use the refresh token to get a new access token
  8. Handle the case where the refresh token itself expires or gets revoked
# This is the "simple" version
from google.auth.transport.requests import Request
from google_auth_oauthlib.flow import InstalledAppFlow

SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]

flow = InstalledAppFlow.from_client_secrets_file("credentials.json", SCOPES)
creds = flow.run_local_server(port=0)

# Now store these creds somewhere safe
# And refresh them before every single API call
if creds and creds.expired and creds.refresh_token:
    creds.refresh(Request())
# But what if refresh_token is None?
# What if it was revoked?
# What if the user changed their password?

This "simple" version still requires a browser window to open for the initial consent flow. Your headless agent running on a server can't do that without additional workarounds.

Step 5: handle everything that goes wrong#

Here's where most developers lose a weekend. Refresh tokens can stop working for any of these reasons:

  • The user revoked access in their Google account settings
  • The token hasn't been used in six months
  • The user changed their Google password (this revokes all Gmail-scoped tokens)
  • You've exceeded 100 refresh tokens per user per client ID
  • Your app is still in "Testing" mode and the 7-day expiry hit
  • Google silently rotated the token and you didn't store the new one

Each failure mode returns the same generic invalid_grant error. Good luck figuring out which one you hit.

Warning

There is a hard limit of 100 refresh tokens per Google account per OAuth client ID. If your agent provisions tokens for multiple users, earlier tokens silently stop working when you cross this threshold. Google does not notify you.

The app password escape hatch (that isn't)#

Some developers look at this mess and think: skip OAuth entirely. Use an app password with IMAP.

As of September 2024, Google disabled less secure app access entirely. App passwords still work if the user has 2-step verification enabled, but they grant full account access. There's no way to scope an app password to read-only. Your agent gets the keys to everything.

And IMAP itself comes with its own problems. Gmail imposes bandwidth limits of 2,500 MB per day for IMAP downloads. Hit that ceiling and your agent goes silent until the next day. The per-user rate limit on the Gmail API is 250 quota units per second — sounds generous until you learn that a single message fetch costs 5 units and a list operation costs 5 units, and you're polling every few seconds.

IMAP also lacks native support for multifactor authentication. That 16-character app password sitting in your agent's .env file is one leaked config away from full inbox exposure.

Why this is especially broken for agents#

All of this pain was designed for a world where a human sits at a browser, clicks "Allow," and moves on. OAuth's consent flow assumes an interactive user. AI agents are headless. They run on servers. They don't have browsers. They can't click consent buttons.

This mismatch is well documented. A recent paper on TechRxiv titled "OAuth Is Not Enough" identifies core limitations: coarse permission scopes, no dynamic policy enforcement, and insufficient controls for autonomous systems that make API calls without direct user involvement.

Your agent doesn't need access to 15 years of email history. It needs to receive messages at an address and maybe send replies. OAuth gives it everything or nothing.

What it looks like when it's not painful#

Here's the LobsterMail equivalent of everything above:

import { LobsterMail } from "@lobstermail/sdk";

const client = new LobsterMail();
const inbox = await client.provision({ name: "my-agent" });

console.log(inbox.address);
// → my-agent@getlobstermail.com

That's it. No Google Cloud project. No consent screen. No token refresh logic. No IMAP bandwidth math. Your agent hatches into its own shell with a working email address. It can receive messages immediately.

No OAuth dance. No credentials to leak. No tokens that expire at 2 AM.

The inbox is isolated — your agent gets only the messages sent to its address, not someone's entire personal email history. If the agent gets compromised, the blast radius is limited to the agent's shell, not your bank statements and medical records.

Tip

On LobsterMail's free tier, your agent can receive emails at its own address. No OAuth tokens, no IMAP credentials, no consent screens. Just a clean inbox on the reef.

Stop fighting Google#

Gmail OAuth was built for web apps where humans click buttons. Agents aren't humans. They run headless, they need narrow permissions, and they definitely don't need access to your entire inbox to do their job.

If you've spent a weekend fighting invalid_grant errors, configuring consent screens for an app that only has one user, or debugging why your refresh token died at 3 AM — you already know. The integration isn't worth the pain.

Give your agent its own email. Let it hatch into the reef with one function call. Keep your Gmail where it belongs — in your browser, behind your own password, out of reach.

Frequently asked questions

Why is Gmail OAuth so complicated for AI agents?

Gmail OAuth was designed for interactive web applications where a human clicks "Allow" in a browser. AI agents run headless on servers and can't complete interactive consent flows. The mismatch forces developers to build workarounds for token exchange, storage, refresh, and revocation handling — all of which add fragility.

What is the Google OAuth consent screen and why does my agent need one?

The OAuth consent screen is a configuration page in Google Cloud Console that defines what users see when authorizing your app. Even if your agent is the only "user," Google requires you to set up an app name, support email, privacy policy URL, and authorized domains before you can create OAuth credentials.

Why do Gmail OAuth refresh tokens expire after 7 days?

If your Google Cloud project's OAuth consent screen is set to "External" with a publishing status of "Testing," Google automatically expires refresh tokens after 7 days. To get longer-lived tokens, you need to push your app to production and pass Google's verification process, which can require a third-party security audit for sensitive scopes.

What is the invalid_grant error in Gmail OAuth?

The invalid_grant error is a catch-all response from Google when a refresh token can't be used. It can mean the user revoked access, the token expired from inactivity (6 months), the user changed their password, you exceeded 100 refresh tokens per client ID, or the app was in Testing mode and the 7-day limit hit. Google doesn't tell you which.

Can I use a Gmail app password instead of OAuth for my agent?

App passwords still work if 2-step verification is enabled, but they grant full account access with no way to restrict permissions to read-only. Google also disabled less secure app access entirely in September 2024. An app password stored in your agent's config is a significant security risk. Read more about the risks of sharing your inbox.

What are the Gmail IMAP rate limits?

Gmail imposes a 2,500 MB daily bandwidth limit for IMAP downloads and 1,250 MB for POP. The Gmail API has a per-user rate limit of 250 quota units per second. A single message fetch or list operation costs 5 quota units. Agents that poll frequently can hit these limits and get temporarily blocked with 429 errors.

Does the Gmail API require a security audit?

If your app requests restricted OAuth scopes — including full mail access (https://mail.google.com/) — Google requires a third-party security assessment before you can publish the app to production. This audit can cost thousands of dollars and take several weeks to complete.

How does LobsterMail avoid the OAuth problem?

LobsterMail gives your agent its own dedicated email address. There's no OAuth flow because there's no existing account to authenticate against. Your agent hatches into a fresh shell with one SDK call. No tokens to refresh, no consent screens, no credentials to store.

Is a dedicated agent email more secure than Gmail OAuth?

Yes. With Gmail OAuth, your agent has access to your entire inbox history — bank statements, password resets, private conversations. With a dedicated inbox, the agent only sees messages sent to its own address. If the agent is compromised, the blast radius is the agent's shell, not your personal life. Learn more about why your agent shouldn't use your Gmail.

Can my agent still interact with Gmail users from a LobsterMail address?

Absolutely. Your agent sends and receives standard email. Anyone with a Gmail, Outlook, or any other email address can message your agent at its LobsterMail address. The protocol is email — it works everywhere.

What happens if my Gmail OAuth token gets revoked?

Your agent silently loses access. Most agents won't surface this as an error until they try to make an API call and get an invalid_grant response. Password changes, manual revocation in Google account settings, or hitting the 100-token limit can all trigger revocation without warning.

How long does it take to set up LobsterMail compared to Gmail OAuth?

Gmail OAuth setup typically takes hours to days — creating a Cloud project, configuring consent screens, implementing token exchange logic, handling refresh and revocation. LobsterMail takes one SDK call. See the full 60-second walkthrough.

Can prompt injection attacks affect my agent through Gmail OAuth?

Yes. When your agent reads email through Gmail OAuth, every message in your inbox becomes an attack surface. An attacker can embed hidden instructions in an email that hijack the agent's behavior. A dedicated agent inbox limits exposure to only messages sent to the agent's address. Read more about prompt injection in email agents.

Is LobsterMail free?

The free tier has no credit card required. Your agent gets its own email address and can receive messages at no cost. Sending unlocks after verification (X post or credit card), free with 10 sends/day. The Builder plan at $9/month adds higher volume and custom domains.


Give your agent its own email. Get started with LobsterMail — it's free.