Back to blog
Written by Andrei BiroLast updated

Outlook Was Broken. 25 Commits Later, It Worked.

March 2026

We message every new customer personally to check if everything is working — we can see classification stats and ingestion results on our end, so we know when something looks off before they even tell us. On March 18th, one of our early customers replied. She'd just subscribed to BillyBox and was trying to connect her Outlook account. It wasn't working. Her main business invoices went to Hotmail, and without that connection, BillyBox was only pulling in documents from her Gmail — which wasn't where the important stuff was.

Her message was simple: "I'm struggling to integrate my Outlook accounts. I'm also finding it's not really pulling any invoices."

She wasn't just testing — her business invoices depended on this.

What followed was a night of building, debugging, hitting walls, pivoting, and ultimately rewriting the entire approach from scratch. Here's the real story, told through our git log.

Microsoft Killed Password-Based Access

When we launched BillyBox, Outlook connections worked through IMAP — the same protocol that Gmail, Zoho, and every other email provider supports. You enter your email and password, and the app connects to read your messages. Standard stuff.

But Microsoft had quietly disabled basic authentication for all personal accounts (@outlook.com, @hotmail.com, @live.com). No app password, no IMAP login — nothing. The only way in was OAuth2: a "Sign in with Microsoft" flow that goes through Microsoft's own login page.

We used Claude (our AI coding assistant) to research the situation — what exactly Microsoft deprecated, what OAuth scopes were needed for IMAP, how the Azure app registration works for personal accounts. The documentation was scattered and contradictory. That turned out to be foreshadowing.

The Decision

We got her email at 10:50 PM on a Tuesday night. We had two options: reply with "we're working on it" and ship in a few days, or build it right now.

We chose right now. She was a paying customer with a real problem. We refunded her month immediately, then started coding.

Phase 1: The Build (4:41 AM - 7:05 AM)

The first commit landed at 6:41 AM — 948 lines of new code. Full OAuth2 flow, XOAUTH2 IMAP authentication, frontend callback page, encrypted token storage. Claude helped scaffold the Azure app registration, the OAuth2 token exchange, and the SASL XOAUTH2 mechanism that Microsoft requires instead of passwords.

06:41Add Microsoft OAuth2 for Outlook/Hotmail IMAP connection— 948 lines, 17 files
06:59Update docs and types for Outlook provider
07:05Use /consumers/ tenant for Microsoft OAuth— First wall: /common/ requires publisher verification

First production surprise: Microsoft's /common/ OAuth endpoint requires publisher verification (a multi-week review process). We switched to /consumers/ — the personal accounts-only endpoint that doesn't require it.

Phase 2: Deploy & Polish (7:17 AM - 8:18 AM)

Deploy revealed its own set of problems. The Docker Alpine image had a Chromium incompatibility that broke our pre-renderer. The frontend needed SPA routing for the Microsoft callback URL. The provider grid layout broke on mobile.

07:17Remove "Coming soon" badges from Outlook OAuth buttons
07:26Fix prerender: retry on Chromium crash + add Microsoft callback SPA route
07:47Pin Alpine 3.21 to fix Chromium/Puppeteer incompatibility
08:01Fix provider grid to show Outlook: 4 columns on desktop, 2 on mobile
08:14Add AI invoice classifier gate with dual backend
08:18Add admin impersonation and fix Outlook button in settings

Yes, we shipped the AI invoice classifier in the same session. When you're in flow at 8 AM after coding all night, you might as well ship everything.

Phase 3: The IMAP Scope Nightmare (8:35 AM - 9:53 AM)

This is where it got ugly. OAuth2 login worked — Microsoft accepted our credentials and returned a valid token. But when we tried to actually read emails via IMAP, it failed. The error: "Command Error. 12" on SELECT INBOX. Authentication succeeds, but you can't open any mailbox.

We tried every combination Microsoft's docs suggested. None worked. 8 commits in 78 minutes — each one a different theory, each one wrong:

08:35Add detailed error logging for Outlook IMAP test failure

Log token scopes to figure out what's wrong

08:45Fix: use outlook.office365.com audience

Theory: IMAP needs Exchange Online tokens — nope

09:07Fix: use Exchange Online audience, not Graph

Not available for personal-only apps either

09:27Skip folder select after OAuth login

Login works, SELECT fails. "Command Error. 12"

09:53Split auth scopes from token exchange scopes

Last attempt: different scopes for consent vs. token

Microsoft's documentation says IMAP works with OAuth2 for personal accounts. It lists three different scope formats depending on which page you read. Claude helped us research each approach — Exchange Online audiences, Graph API scopes, resource-specific tokens — and we tried every combination. None of them worked reliably.

The core problem: Microsoft's IMAP server for personal accounts silently rejects SELECT commands even with a valid OAuth2 token. You can authenticate, but you can't read mail. Their docs don't mention this limitation anywhere.

Phase 4: The Pivot (10:16 AM)

After 8 failed attempts to make IMAP work, we made the call: abandon IMAP entirely. Instead, use Microsoft's Graph REST API — a completely different approach where you fetch emails via HTTP requests instead of the IMAP protocol.

10:16Switch Outlook from IMAP XOAUTH2 to Microsoft Graph REST API

982 new lines. Full Graph API mail fetching — message listing, attachment downloading, message threading. All via HTTP.

This wasn't a small change. We wrote a brand new 913-line service (outlook_graph_service.py) that mirrors our Gmail API service but uses Microsoft Graph endpoints. Different auth scopes (Mail.Read instead of IMAP.AccessAsUser.All), different token handling, different message format parsing. Claude helped architect the service, translate our IMAP patterns to Graph API equivalents, and handle Microsoft's pagination model.

It worked on the first try. No scope issues, no mysterious error codes. The Graph API does what Microsoft's IMAP server refuses to do.

Phase 5: Making It Solid (10:46 AM - 3:44 PM)

The pivot worked, but production traffic revealed new edge cases:

10:46Fix SQLAlchemy session error during Outlook Graph fetch
10:47Fix mid-fetch db.commit() in IMAP service token rotation
11:01Fix transaction poisoning: use separate session for token rotation

Token refresh during email fetch corrupted the DB session

11:05Widen seen_emails.email_uid from 64 to 255 chars

Graph message IDs are longer than IMAP UIDs

13:13Fix Outlook showing as "Other IMAP" in settings
14:03Fix TS error: add 'outlook' to EmailAccount provider type
15:44Replace "Connected" badge with checkmark icon

The nastiest bug: when Microsoft's token expired mid-fetch, the refresh call committed to the database inside the fetch transaction, poisoning the SQLAlchemy session. Took 3 commits to properly isolate the token rotation into its own session.

The Final Count

Total commits:25
Lines of code:~2,000+
Time:~11 hours
Failed IMAP scope attempts:8
Complete rewrites:1 (IMAP → Graph API)
AI-assisted commits:25/25

The Role AI Played

Every one of those 25 commits was co-authored with Claude. Not as a code generator that spits out boilerplate — as a research partner and debugger.

When we needed to understand what Microsoft actually deprecated, Claude researched it. When we hit the IMAP scope wall, Claude helped us try each documented approach systematically. When we decided to pivot to Graph API, Claude helped architect a 913-line service in one sitting — translating our IMAP patterns to REST equivalents, handling Microsoft's pagination model, and mapping their message format to our schema.

The 8-commit scope nightmare is a good example of what AI-assisted development actually looks like. It's not "AI writes perfect code." It's "AI helps you explore a problem space faster." We tried 8 approaches in 78 minutes that would have taken days of manual documentation reading. When none of them worked, we had enough evidence to make the pivot decision confidently.

The Refund That Kept a Customer

Before we started building, we refunded her first month. She was paying for something that didn't work for her yet.

The next morning at 11:20 AM, we emailed her: "Outlook is now supported." She didn't cancel. She's still subscribed today.

The refund cost €19.99. The 25 commits, the 8 failed IMAP attempts, the full architecture pivot at 10 AM — that's just what it takes when a customer has a problem and you give a damn.

Connect Your Outlook Account

BillyBox now supports Outlook, Hotmail, and Live accounts through the Microsoft Graph API. No password sharing, no app passwords, no IMAP configuration.

This is how we build BillyBox — fast, close to users, and willing to throw away code when it doesn't work.