← Back to Blog

Day 62: Gmail Without the Dev Console

The email integration merged this morning. It's the first integration in Pinchy whose setup wizard walks the admin through registering an OAuth application — because getting Gmail to talk to anything requires a trip through the Google Cloud Console, and that trip has been the reason the Gmail integration kept getting postponed.

A Wizard Instead of a README

The standard Gmail setup instructions are a wall of screenshots. Create a Google Cloud project. Enable the Gmail API. Configure the OAuth consent screen. Add test users or publish the app. Create OAuth 2.0 Client ID credentials. Copy the redirect URI. Copy the Client ID. Copy the Client Secret. Now go to your app and paste them in.

Every step has a footgun. Wrong redirect URI and the callback fails with an opaque Google error page. External user type without the app published, and only the five whitelisted test users can sign in. OAuth client left in "Testing" mode, and refresh tokens expire every seven days without warning. Scope not added on the consent screen, and the refresh token never arrives at all.

The wizard flips this. On the first Google connection in a fresh install, the integration dialog opens in OAuth-setup mode. Pinchy shows the exact redirect URI to paste into the Google Cloud Console — with a copy button — and collects Client ID and Client Secret in the same form. The wizard then moves to Connect Google Account, which kicks off the OAuth flow from Pinchy's end. Once the callback lands, the connection becomes active.

On the second Google connection, the credentials are already stored at the workspace level. The wizard skips the setup step entirely and goes straight to Connect Google Account. Add a third mailbox, add a fourth — same thing. The pain happens once per workspace, not once per mailbox.

Pending Is a Status

The interesting half of OAuth is everything that can go wrong between starting the flow and the callback coming back. The tab gets closed. The redirect URI is subtly wrong and Google returns an error. The consent screen asks for a scope that wasn't approved and the user cancels. The network drops between consent and redirect.

Previously, these failure modes produced an invisible half-state: a row that was almost a connection, with no way for the admin to see it and no way to retry. Today's change treats pending as a real status in the database. When OAuth begins, a status: "pending" row is inserted with an oauth_pending_id cookie. The callback finds the row by cookie and updates it to active — so there are never duplicate rows. If the callback never comes, the row stays pending, and the integrations list shows it with an amber Setup in progress badge.

The dropdown on a pending row has two options: Continue setup, which reopens the wizard at the connect step, and Remove, which deletes the row. Pending connections are blocked at the credentials endpoint with a 403, filtered out of the OpenClaw config, and excluded from the agent email-permission selector. They exist in the UI exactly so the admin can either finish them or delete them, and nowhere else.

That whole shape — pending as a first-class status with an explicit recovery path — is going to come back for every future OAuth integration. Better to get it right the first time.

Elsewhere: Two Branches in Review

The smarter-docs branch landed the full Odoo doc coverage today. All 16 agent templates now source their domain knowledge from the pinchy-docs plugin instead of carrying it hardcoded in their system prompts. Code-review improvements applied. Out for a second pass.

The web-search branch — Brave Search plus a pinchy_web_fetch URL reader with SSRF protection — picked up a round of hardening: stricter domain validation, an audit log entry when the plugin config changes, and a PATCH input guard on the per-agent settings endpoint. Unit tests added for openclaw-config.ts's pinchy-web plugin generation path, since that's the surface where a bad domain filter would silently leak.

Neither is merged yet. Both are close.

Day 62

Gmail is the first integration where Pinchy's setup story had to include an external setup story. The right answer wasn't to document it better. The right answer was to take as much of the external setup into the product as possible: the copy-pasteable redirect URI, the state recovery when the tab closes, the workspace-level credentials that let every additional mailbox skip the console trip entirely. The integration isn't done when the tokens land. It's done when an admin who has never touched OAuth before can finish it on the first try.

← Day 61: Docs That Know Who's Asking

Pinchy is open source and ready to deploy. Clone the repo, run docker compose up, and your first agent is live in minutes.