← Back to Blog

Day 138: One Mailbox Interface, Two Providers

Pinchy could do email, as long as email meant Gmail. That was fine until it wasn’t, because a lot of the companies I want Pinchy to help run their whole business through Microsoft 365. Adding Microsoft support (PR #328) was the feature on the ticket, but the actual work was undoing an assumption that had soaked into everything: that “email” and “Gmail” were the same word.

The abstraction that should have been there first

The old code let Gmail’s shape leak straight through to the agent. Folders were raw Gmail label strings. Search was raw Gmail query syntax. So an agent, and anyone writing an agent template, had to know Gmail’s specific dialect to ask for something as ordinary as “unread messages in the inbox from last week.” That’s a boundary violation: the provider’s quirks were the agent’s problem.

The fix is a single EmailAdapter contract, five verbs, list, read, search, draft, send, that both providers implement and neither provider defines. Folders became canonical names, INBOX, SENT, DRAFTS, TRASH, SPAM, which was a breaking change away from raw Gmail labels and worth it. Search became a structured request, fields like from, subject, unread, sinceDays, folder, instead of a query string in someone else’s grammar. Now the agent asks for what it wants in Pinchy’s terms, and the adapter translates. The GmailAdapter got refactored onto the contract, and a new GraphAdapter implements the same five verbs against Microsoft Graph.

Microsoft’s refresh tokens don’t sit still

Every provider has one nasty detail, and Microsoft’s is refresh tokens that rotate. Google hands you a refresh token and you keep it. Microsoft invalidates the old refresh token every time you use it and gives you a new one, so the moment you refresh you must persist both the new access token and the new refresh token, or the next refresh fails with a token that’s already dead. Worse, two refreshes racing each other can each burn the other’s token. So refreshes are deduplicated in flight: concurrent refresh requests for the same connection share one in-progress promise instead of stampeding. There’s also a mock Graph server so the whole flow, including the token dance, can be tested end to end without hitting Microsoft.

Day 138

The feature is “Pinchy does Microsoft email now.” The lesson underneath is the one this project keeps circling: Pinchy is the hub a team works through, and a hub earns that name by absorbing each provider’s weirdness so the agents on top of it don’t have to. Gmail’s label strings, Microsoft’s rotating tokens, the different auth dances, all of it should die at the adapter and never reach the quote drafter or the support agent above it. That’s what makes swapping a provider, or running two at once, a boundary the self-hosted hub keeps rather than a leak every agent has to route around. One interface, many providers, is not a convenience. It’s the whole idea.

← Day 137: One Agent Per Job Day 139: A Four-Second Window for a Permanent Problem →

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