On May 4 at 9am we opened a fresh tab, created a dedicated Gmail address for the agent, and spent the next four hours wiring OAuth so a Claude Code session could read and send mail. Email is unglamorous infrastructure, but for two weeks we had kept hitting the same wall. Every time we tried to get an agent to do something real, the first blocker was that the agent had no identity of its own.
Google Drive would not share a doc with it. Calendar had nowhere to send an invite. The dispatch loop we wanted to sketch assumed an inbox that could receive work. None of that works while the agent is impersonating us.
We had been treating the agent stack like a tool we controlled, not a participant in systems built around identity. That was wrong. Fixing it took four hours of credential plumbing and zero AI reasoning.
What running under a personal account cost us
The original setup ran Claude Code sessions under a personal Google context, when they ran under any context at all. OAuth tokens lived in a temp directory. When a session ended, the tokens expired or got swept, and the next session re-authorized from scratch. For one-shot tasks that was annoying. For anything that needed to persist state across sessions it was a hard stop.
There was a subtler cost too. When an agent shares a Google doc, the doc shows a person’s name as the sharer. When it creates a calendar event, that person’s name is on it. If the agent sends a message from a personal account, the message looks like it came from a person. That matters when the agent is wrong, which it will be. You want an audit trail where “the agent did that” is technically true.
A real identity fixes both. The credentials persist, and the actions are attributable to the right actor.
Provisioning the account
The Google account itself took five minutes. Everything after that was slower.
We created a GCP project under a personal account as the billing parent, with the agent’s ops inbox as the service identity. We enabled the Gmail API, the Calendar API, and Drive. We walked the OAuth consent screen setup, which for a personal-use app makes you explicitly add yourself as a test user. We downloaded the credentials.json and stored it alongside the refresh token from the initial authorization flow, in a secrets file the rest of the stack already knew how to read.
A secrets helper already existed for this pattern. One command returns the token value, and any session that needs Google access loads the file and has the credentials in scope.
For claude.ai MCP, we wired the Gmail and Calendar servers through the standard OAuth flow. That required a separate client ID scoped to the MCP redirect URI. So there are two client IDs in the same GCP project: one for programmatic access from scripts, one for the MCP servers. MCP’s redirect handling is browser-interactive and the script-side flow is non-interactive, so it is the same scopes with a different flow type and a different client ID.
The credential surface after setup: one GCP project, two client IDs, one refresh token per API surface, all in a single secrets directory. Nothing in a temp directory, and nothing that expires on session end.
The inbox as a transport layer
Once the account existed and the credentials persisted, we stopped thinking about email as a notification channel for the agent and started thinking about it as a transport layer for work. A notification channel delivers information to a human. A transport layer moves work between actors that can be machines.
Gmail labels map well onto work states. An inbound message tagged pending is a task waiting to be picked up. The agent polls the label, processes the item, and tags it done. That is a kanban board, and it runs on infrastructure every business already trusts. There is no new queue service and no webhook to maintain, only labels and a polling interval.
We sketched the dispatch loop the same afternoon. Search for threads with label agent-queue, read the top message, parse a structured action from the subject line or body, execute it, apply a result label. The agent needs read and label permissions for the loop itself, and send permissions for the reply path.
# Gmail labels as a work queue. No new infra: just labels + a poll.while True: for thread in gmail.search("label:agent-queue"): msg = thread.top_message() action = parse_action(msg.subject, msg.body) result = execute(action) thread.relabel(remove="agent-queue", add=f"done-{result.status}") if action.wants_reply: thread.reply(result.summary) # needs send scope sleep(POLL_SECONDS)We did not finish the loop that day. We got it far enough to confirm the polling worked, then stopped, because the goal was to see whether the identity layer held weight before building anything on top of it.
Why the agent looked flaky
There is a version of this story where we skip the infrastructure work, keep running the agent as a person, and blame the agent. It feels flaky. Session context does not persist. Shared docs do not work. We would conclude agent tooling is not ready yet, and that conclusion would be technically accurate and wrong about the cause.
The friction sat in the gap between Claude Code as a smart shell command and Claude Code as a participant in the systems a business already runs. Google Workspace, JIRA and GitHub are all built around identity. They assume every actor has an account, a set of permissions, and a credential that persists. They have no concept of a stateless anonymous assistant that borrows a human’s session.
Closing that gap takes the same plumbing a new employee goes through on their first day: create the account, grant the permissions, hand over a badge. It surprises people because the demo videos for AI tools skip it. The demo starts with a working credential. Our actual work started at 9am on May 4 with a fresh GCP project and an empty secrets file.
The part we have not done
One Gmail account currently holds three functions: inbound work items from the dispatch loop, outbound sharing when the agent sends a doc link, and automated alerts from crons and scripts. We watch that mix with mild concern.
The clean version separates them into an inbox for work routing, a send-only identity for sharing, and a no-reply address for automated alerts. That makes three identities with explicit roles, and no ambiguity about what a message in the inbox actually is.
We have not built that yet. The single account works well enough for the volume we run. Once the dispatch loop sees real inbound traffic from more than one source, the separation becomes necessary. For now it is a diagram on a whiteboard.
Membership was the bottleneck
We had been thinking about agent capability as a function of model quality: bigger model, more capable agent. That framing is not wrong, but it misses the binding constraint. For most of the practical work we need done, current-generation reasoning is more than sufficient. The work was held up because the agent could not act in the systems we already use, since those systems did not recognize it.
Giving it a Gmail account is a credential decision, exactly as unglamorous as setting up a new contractor’s system access. It is also what makes the rest possible. The model reasons about the work, and the identity lets it touch the systems where the work lives.
So before you judge an agent by its reasoning, check whether it has its own account, its own permissions, and credentials that persist between sessions. An agent running under a person’s account makes every action look like that person’s, and its sessions lose their tokens when they end. Give it a separate identity so its work is attributable, and plan to split inbound work, outbound sharing and automated alerts once more than one source feeds the inbox.