Skip to content

Fix P5: Outlook inbox sync via Microsoft Graph (read-only)#107

Open
bmoss2015 wants to merge 3 commits into
mainfrom
feat/outlook-oauth-and-sync
Open

Fix P5: Outlook inbox sync via Microsoft Graph (read-only)#107
bmoss2015 wants to merge 3 commits into
mainfrom
feat/outlook-oauth-and-sync

Conversation

@bmoss2015

Copy link
Copy Markdown
Owner

Summary

Mirrors the Gmail integration so users can connect a Microsoft 365 / Outlook.com mailbox and have it sync into the inbox alongside Gmail.

New files

  • src/lib/email/microsoft-oauth.ts — OAuth helpers (authorize URL, token exchange, refresh, /me userinfo). tenant=common so both personal and work/school accounts work.
  • src/lib/email/outlook.ts — Graph REST client with token-refresh retry, plus delta-endpoint listing and attachment download.
  • src/app/api/oauth/microsoft/start/route.ts and callback/route.ts — OAuth kickoff and callback mirroring the Google flow. Stores tokens encrypted in channel_accounts (provider='outlook'), then awaits the initial sync so the inbox lands populated.

Changes

  • src/lib/email/sync.ts — adds syncOutlookAccount + a shared upsertConversationGeneric helper. Uses the Graph /me/mailFolders('inbox')/messages/delta endpoint; the @odata.deltaLink is persisted as sync_cursor for incremental pulls. A 410 (expired delta) triggers a clean full-resync next pass. syncAllActiveAccounts now sweeps both providers so the existing cron route picks up Outlook with no changes.
  • EmailAccountsSection.tsx — empty state surfaces both Connect Gmail and Connect Outlook buttons; per-account list adds matching secondary links.

Schema — no migration. channel_accounts.provider already includes 'outlook'.

Scope cuts (intentional)

  • Send through Outlook is not wired. The Mail.Send scope is requested at OAuth time so it's available, but the existing email/send action only knows about Gmail. Adding provider-aware send is a follow-up so this PR stays reviewable.
  • No UI badge for Gmail vs Outlook inside the inbox conversation lists. channel='outlook' is stored on rows so we can color or icon them later.

Required env vars (please add in Vercel)

  • MICROSOFT_OAUTH_CLIENT_ID — staging + prod
  • MICROSOFT_OAUTH_CLIENT_SECRET — staging + prod

Redirect URI in the Azure app registration must be:

  • Staging: https://<staging-preview-or-branch-domain>/api/oauth/microsoft/callback
  • Prod: https://app.nextsurplus.com/api/oauth/microsoft/callback

Verify on preview

  1. Add the env vars in the Vercel preview environment for this branch
  2. Log in: info@mossyland.com / MossEquity-858dc58d33!
  3. Settings > Email Accounts: the empty state shows both Connect Gmail and Connect Outlook
  4. Click Connect Outlook, complete the Microsoft consent screen
  5. Land back on the settings page with success banner and a populated Outlook account block
  6. Open /inbox and confirm recent Outlook messages appear (90-day backfill cap of 25 delta pages)

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

Mirrors the Gmail integration so users can connect a Microsoft 365 /
Outlook.com mailbox and have it sync into the inbox alongside Gmail.

New files
- src/lib/email/microsoft-oauth.ts: OAuth helpers (authorize URL,
  token exchange, refresh, /me userinfo). Tenant=common so personal
  and work/school accounts both work. Reads MICROSOFT_OAUTH_CLIENT_ID
  and MICROSOFT_OAUTH_CLIENT_SECRET.
- src/lib/email/outlook.ts: thin Graph REST client with token-refresh
  retry. Uses the delta endpoint for both backfill and incremental
  pulls and exposes attachment list/content helpers.
- src/app/api/oauth/microsoft/start/route.ts and callback/route.ts:
  signed-in-only OAuth kickoff and callback. Stores tokens encrypted
  in channel_accounts (provider='outlook'), then awaits the initial
  sync so the inbox lands populated.

Changes
- src/lib/email/sync.ts: adds syncOutlookAccount + a generic
  upsertConversationGeneric helper. The Outlook sync uses the Graph
  /me/mailFolders('inbox')/messages/delta endpoint. The deltaLink is
  persisted as sync_cursor; subsequent runs just GET that URL for the
  incremental diff. 410 Gone (expired delta) triggers a clean
  full-resync next pass.
  syncAllActiveAccounts now sweeps both providers, so the existing
  /api/cron/email-sync route picks up Outlook accounts with no
  changes needed.
- EmailAccountsSection: empty state surfaces both Connect Gmail and
  Connect Outlook buttons, and the per-account list has the matching
  secondary links.

Schema
The channel_accounts.provider enum already includes 'outlook' from
the 0100_email_module migration, so no DB changes ship in this PR.

Scope cuts to land this safely
- Sending mail through Outlook is not wired yet. The Mail.Send scope
  is requested at OAuth time so it's available, but the existing
  email/send action only knows about Gmail. Adding Outlook send is a
  follow-up.
- No UI differentiation between Gmail and Outlook inside the inbox
  conversation lists; channel='outlook' is stored on messages and
  conversations and the inbox query already treats both as 'email'.

Required env vars (please add in Vercel)
- MICROSOFT_OAUTH_CLIENT_ID (staging + prod)
- MICROSOFT_OAUTH_CLIENT_SECRET (staging + prod)
Redirect URI in Azure must be {origin}/api/oauth/microsoft/callback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@supabase

supabase Bot commented Jun 15, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project rsdmyydyhqgkkvwlklif because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@vercel

vercel Bot commented Jun 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
moss-equity-portal Ready Ready Preview, Comment Jun 15, 2026 6:46pm

Request Review

Bree already set MICROSOFT_CLIENT_ID and MICROSOFT_CLIENT_SECRET in
the Vercel preview env. My code was reading the longer
MICROSOFT_OAUTH_CLIENT_ID names which would have thrown
'MICROSOFT_OAUTH_CLIENT_ID is not set' on every OAuth attempt.
Renamed to match the actual env values.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant