Skip to content

Read any mailbox: Microsoft 365, and IMAP with CalDAV, behind one reader - #6

Merged
byw1 merged 3 commits into
mainfrom
claude/google-calendar-gmail-integration-bi7nna
Sep 8, 2026
Merged

Read any mailbox: Microsoft 365, and IMAP with CalDAV, behind one reader#6
byw1 merged 3 commits into
mainfrom
claude/google-calendar-gmail-integration-bi7nna

Conversation

@byw1

@byw1 byw1 commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Mail and calendar were Google-only. Now a person can connect Google, Microsoft 365 / Outlook.com, or any provider that speaks IMAP and CalDAV — and more than one, since a work Outlook and a personal Gmail are both where recruiters write. Slack and Discord sit on the account picker as coming soon.

What changed

  • GoogleAccountLinkedAccount. A provider column (GOOGLE, MICROSOFT, IMAP), provider-neutral features ("mail", "calendar") replacing Google's scope URLs, IMAP and CalDAV fields on the same row, and unique on (userId, provider, email) so several accounts fit. The migration renames rather than recreates, so a Google connection from the previous release survives with its scopes rewritten.
  • One interface, three protocols. src/lib/accounts/types.ts defines MailReader and CalendarReader. google.ts (moved from google-api.ts), microsoft.ts (Graph), imap.ts (imapflow + mailparser) and caldav.ts (tsdav + ical.js) implement them; nothing else in the app knows which answered. There is no write in the interface, so no provider can grow one.
  • Merged reads. src/lib/data/accounts.ts replaces data/google.ts: every thread and event says which account it came from, thread ids are scoped to their account, one account failing is a line in warnings rather than an empty result, and Microsoft's rotated refresh tokens are kept.
  • Microsoft 365 connects through an Entra app registration set under Admin → Configuration → Accounts (new settings group, with the redirect URI and steps shown on the screen). New OAuth start and callback routes, signed state cookie like Google's.
  • IMAP + CalDAV connects from a form with presets for Fastmail, iCloud and Yahoo, using an app password. Both servers are logged in to before anything is saved. Hosts are checked: loopback, link-local (cloud metadata) and multicast targets are refused after resolving the name; a server on a home LAN is allowed on purpose.
  • Tools made provider-neutral: list_linked_accounts, connect_imap_account, test_linked_account, disconnect_account, plus list_correspondence / search_email / get_email_thread / search_calendar with an optional accountId. Admin: admin_get_microsoft_config, admin_set_microsoft_config. get_google_connection and disconnect_google are gone; the briefing and every description say "Settings → Connections".
  • Connections tab gets account tiles per connected account, an account slide-over (test each half, rename, reconnect, disconnect), an add-account picker, and the IMAP form.
  • The schedule merge moved to src/lib/data/schedule.ts so pipeline.ts, which client components import for constants, no longer reaches the Node-only mail libraries; those are server externals in next.config.ts.

Deliberately not

No SMTP. The app never sends on anyone's behalf; read-only is what makes handing over an inbox safe, and sending is a product decision to make on purpose rather than a side effect of "support IMAP".

Verification

npm run typecheck and npm run build pass; node tools/gen-tool-docs.mjs --check is current (106 tools for a member, 138 for an admin). The migration matches prisma migrate diff output for the schema. A tenant-isolation audit of the data layer, providers, tools, actions and OAuth routes found no violations. The ical.js calls in the CalDAV reader were exercised against a sample calendar with a weekly recurrence. Not exercised end to end here: no database or provider credentials in the build container, so the first real connect of each kind on an instance is the test.

Docs

docs/tools/accounts.mdx replaces google.mdx; new docs/self-hosting/microsoft.mdx; the inbox-and-calendar guide, the Google self-hosting page, README and the decision log are updated.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JBeuHate5rfgqAkLcHzJww


Generated by Claude Code

byw1 and others added 3 commits September 3, 2026 04:59
GoogleAccount becomes LinkedAccount, with a provider and room for more
than one per person, because a work Outlook and a personal Gmail are both
where recruiters write. Google, Microsoft Graph, and IMAP plus CalDAV each
implement one MailReader and CalendarReader interface; every screen and
tool merges across accounts and names which one each thread and event came
from. Read-only is the interface, not a convention: there is no send.

Microsoft 365 connects through an Entra app registration an admin sets
under Admin → Configuration → Accounts. Anything else — Fastmail, iCloud,
Yahoo, a self-hosted server — connects from a form with presets and an
app password, both servers logged in to before anything is saved. Slack
and Discord sit on the picker as coming soon.

Tools: list_linked_accounts, connect_imap_account, test_linked_account,
disconnect_account, and the correspondence tools made provider-neutral;
admin_get_microsoft_config, admin_set_microsoft_config. No SMTP: the app
never sends, and that is what makes handing over an inbox safe.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JBeuHate5rfgqAkLcHzJww
…hosts

The IMAP library is Node-only, and pipeline.ts — imported by client
components for its labels — had started reaching it through the accounts
layer. The schedule merge lives in src/lib/data/schedule.ts now, and the
mail libraries are server externals.

A hostname typed into the account form is a request the server makes on
the person's behalf. Loopback, link-local and multicast targets are
refused, after resolving the name; a server on a home LAN still works.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JBeuHate5rfgqAkLcHzJww
…with every mailbox

Main redrew Connections as rows with a library sheet and added the archive
while this branch generalised accounts. The accounts now sit in main's
layout — one row per connected account, the library's Accounts tab offers
Google, Microsoft 365, any IMAP and CalDAV provider, and Slack and Discord
as coming soon — and the archive's filters are ported into the matching
code that replaced data/google.ts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JBeuHate5rfgqAkLcHzJww
@byw1
byw1 merged commit 57dbbe2 into main Sep 8, 2026
1 check passed
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