Outlook Teams and Calendar
approvedby Nabheet Madan
Sync Microsoft 365 into your vault: Outlook mail (one note per email + thread index), calendar events, and Teams messages (chats + channels). Incremental sync, PKCE auth, read-only scopes. - This plugin has not been manually reviewed by Obsidian staff.
Outlook Teams and Calendar
Sync your Microsoft 365 into your Obsidian vault β mail, calendar, and Teams messages β from a single Azure app. Incremental sync, PKCE auth (no client secret stored), read-only scopes, configurable folder mappings.
π Setting up? See SETUP.md for the full step-by-step guide: Azure app registration, a per-feature permissions matrix, admin consent, and troubleshooting.
Desktop-only (needs Node
http/cryptofor the OAuth loopback). Not for Obsidian mobile.
What it does
Three features, each independently toggleable, all under one target folder
(default 10-Mailbox):
π§ Mail
- Reads mail from one or more Outlook folders (Inbox by default).
- Writes one note per email to
<target>/<subfolder>/<date> <subject> <id>.mdwith YAML frontmatter (from/to/cc, dates, conversation id, web link, flags). - Maintains
_Thread Index.mdper subfolder, grouping messages by conversation. - Uses Graph delta queries so each sync pulls only changes, not the whole mailbox. Optional attachment download per folder.
π
Calendar (10-Mailbox/Calendar/)
- One note per event plus a sidebar pane of upcoming meetings.
- Rolling days-ahead window (default 14) β upcoming only, no past archiving.
- Optionally links meetings to related emails by shared attendees + subject.
π¬ Teams (10-Mailbox/Teams/)
- One transcript note per conversation (chats and/or channels) plus a
regenerated
_Conversation Index.md. - New messages are appended incrementally, bounded by a last-N-days window.
One-time setup: register an Azure AD app
π New here? See SETUP.md for the full step-by-step guide with a per-feature permissions matrix (Mail / Calendar / Teams), admin-consent notes, and an error-code troubleshooting table. The summary below is the quick version.
You need a free Azure AD app registration (public client). No secret is created or stored β auth uses Authorization Code + PKCE.
- Go to Azure Portal β App registrations β New registration.
- Name:
Obsidian Outlook Teams and Calendar(anything). - Supported account types: Accounts in this organizational directory only (single-tenant) is fine for a work/school account. Multi-tenant also works.
- Redirect URI: platform Mobile and desktop applications, value:
http://localhost(Azure allowshttp://localhoston any port for this platform β the plugin picks a free port at login time.) - Click Register.
- On the Overview page, copy the Application (client) ID and the Directory (tenant) ID.
- API permissions β Add a permission β Microsoft Graph β
Delegated permissions β add:
Mail.ReadUser.Readoffline_accessCalendars.Read(for calendar sync)- For Teams chats:
Chat.Read - For Teams channels:
Team.ReadBasic.All,Channel.ReadBasic.All,ChannelMessage.Read.All(these three typically need admin consent) The plugin only requests the Teams scopes when you enable those features, so add them only if you'll use Teams sync.
- If your tenant requires it, click Grant admin consent (or ask your admin).
Work/school tenants often gate consent β without it, login returns
AADSTS65001/AADSTS90094. - Authentication β under Advanced settings, ensure Allow public client flows = Yes.
Then in the plugin settings:
- Application (client) ID β the value from step 6.
- Tenant β your tenant GUID (from step 6), or
organizationsfor any work/school account, orcommonfor any account type.
Install into your vault
From this repo:
npm install
npm run build # produces main.js
Copy main.js, manifest.json, and styles.css into your vault:
<your-vault>/.obsidian/plugins/outlook-teams-calendar/
Or use the helper (set your vault path):
VAULT="/path/to/your/vault" npm run install:vault # see scripts/install.mjs
Then in Obsidian: Settings β Community plugins β Installed plugins β enable Outlook Teams and Calendar. Reload plugins if it doesn't appear.
Configure & sync
Open Settings β Outlook Teams and Calendar:
-
Enter the client ID and tenant, click Connect β a browser opens for Microsoft sign-in and consent.
-
Set Target folder (default
10-Mailbox). -
Under Folders to sync, each row maps an Outlook folder to a vault subfolder:
Outlook path Vault subfolder inboxInboxarchiveArchiveProjects/ClientXClients/ClientX- Single-segment well-known names (
inbox,archive,sentitems,deleteditems,junkemail,drafts, β¦) resolve directly. - Anything else is matched by display name,
/-separated for nesting.
- Single-segment well-known names (
-
Set Auto-sync interval (minutes;
0disables). Toggle Sync on startup.
Sync manually anytime via the mail ribbon icon or the "Outlook Teams and Calendar: Sync now" command.
Note layout
10-Mailbox/
Inbox/
_Thread Index.md
2026-07-04 Quarterly numbers a1b2c3d4e5.md
2026-07-03 Re Quarterly numbers f6g7h8i9j0.md
Archive/
_Thread Index.md
...
Each email note:
---
source: outlook
message_id: AAMk...
conversation_id: AAQk...
subject: Quarterly numbers
from: alice@contoso.com
from_name: Alice Smith
to:
- nabheet@infinitelocus.com
received: 2026-07-04T09:12:00Z
folder: Inbox
has_attachments: false
web_link: https://outlook.office365.com/...
---
# Quarterly numbers
**From:** Alice Smith <alice@contoso.com>
**To:** Nabheet Madan <nabheet@infinitelocus.com>
**Date:** 2026-07-04T09:12:00Z
**[Open in Outlook](https://outlook.office365.com/...)**
---
<email body as Markdown>
How sync works (delta)
- First sync of a folder does a full enumeration and stores an
@odata.deltaLink. - Subsequent syncs call that delta link β Graph returns only messages added,
changed, or removed since. Delta links are persisted per resolved folder id
in the plugin's
data.json. - Updates overwrite the existing note (same message id β same file name).
- Removals move the note to Obsidian trash and drop it from the thread index.
- Reset sync state (settings β Maintenance) clears delta tokens + thread index so the next sync re-enumerates. Existing notes are left in place.
Calendar
Enable Settings β Calendar β Sync calendar.
- Layout: one note per event under
10-Mailbox/Calendar/, plus an Upcoming meetings sidebar pane (ribbon icon / command to open). - Window: a rolling days-ahead window (default 14). Upcoming events only β past events are not archived, and events that fall out of the window are not deleted from the vault.
- Email cross-linking: with Link related emails on, each meeting note links to emails that share attendees and a similar subject, and vice-versa.
- Calendar uses Graph
calendarView(expands recurring events into instances); it re-reads the window each sync rather than a delta cursor.
Teams messages
Enable Settings β Teams β Sync Teams, then pick chats and/or channels. After toggling, click Reconnect so the new Graph scopes are consented (channel scopes usually need an admin).
- Layout: one Markdown note per conversation (a running transcript), under
10-Mailbox/Teams/by default, plus a regenerated_Conversation Index.md. Chats are titled by topic or participants; channels asTeam / Channel. - Incremental: new messages are appended to the transcript β bodies live
in the note, so
data.jsonstays small (only a cursor + last-written timestamp per conversation are persisted).- Channels use Graph message delta (a server change-tracking cursor). An expired delta token (404/410) transparently re-enumerates, same as mail.
- Chats cannot use delta β Microsoft Graph does not support change
tracking on chat messages (
/chats/{id}/messages/deltareturns "Change tracking is not supported against microsoft.graph.chatMessage"). Instead, chats are listed newest-first and paged only back to the sync window; the per-conversation last-written timestamp handles dedup. Practical effect: keep the window reasonable (see below) so chat listing stays cheap.
- Window: Sync messages from the last N days (default 30) bounds how far
back the first sync reaches and how far chat listing pages each run;
0= all history (heavy for chats β every sync walks the full chat). - Channels & replies: channel posts sync via delta; replies are fetched for posts seen in that sync. A new reply to an older post (older than the last synced message) won't be picked up until that post next changes β a known limitation of channel reply delta. Chats have no such caveat.
- Message edits are not re-written (dedup is by "newer than last written"); deleted and system (join/leave) messages are skipped.
Observability
- Status bar:
π¬ Outlook Β· <last sync time>/syncingβ¦/error. - Notices on completion (counts) and on errors.
- Debug logging toggle β verbose
[obs-mail]console output (open devtools withCtrl/Cmd+Shift+I).
Security notes
- Auth is Authorization Code + PKCE with a loopback redirect. No client secret is used or stored.
- The refresh token is stored in the plugin's
data.jsoninside your vault, in plain text (same as most Obsidian plugins). Anyone with read access to your vault files can use it. Keep your vault private; don't syncdata.jsonto untrusted locations. Use Disconnect to revoke locally, and revoke the app under My Account β Apps & services / Azure to invalidate server-side. - All scopes are read-only and requested only for the features you enable:
User.Read+offline_access(always),Mail.Read(mail),Calendars.Read(calendar),Chat.Read(Teams chats), andTeam.ReadBasic.All+Channel.ReadBasic.All+ChannelMessage.Read.All(Teams channels). The plugin never writes to or deletes anything in Microsoft 365.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
AADSTS65001 / consent required | Grant admin consent for the app permissions (step 8). |
AADSTS7000218 / public client | Set Allow public client flows = Yes (Authentication blade). |
| Login browser opens but nothing happens | Loopback port blocked by firewall; retry, or check devtools console. |
Outlook folder not found | Use the exact display name; /-separate nested folders; well-known names are lowercase (sentitems, not Sent Items). |
| Nothing new syncs | State is delta-based; use Reset sync state to force full re-enumeration. |
| Conditional Access blocks device | This plugin uses the interactive auth-code flow, not device code β sign in through the opened browser as normal. |
Development
npm install
npm run dev # esbuild watch, rebuilds main.js on change
npm run typecheck # tsc --noEmit
npm run build # typecheck + minified production bundle
Source layout:
src/main.tsβ plugin lifecycle, commands, ribbon, status bar, timer.src/auth.tsβ PKCE loopback OAuth flow + token refresh.src/graph.tsβ Graph client: token lifecycle, folder resolution, delta.src/notes.tsβ HTMLβMarkdown, note + thread-index writing.src/sync.tsβ per-folder delta orchestration + thread bookkeeping.src/settings.tsβ settings UI.src/types.tsβ shared types + defaults.
License
Released under the MIT License Β© 2026 Nabheet Madan.
This project is an independent, community-built plugin. It is not affiliated with, endorsed by, or sponsored by Microsoft. "Microsoft 365", "Outlook", and "Microsoft Teams" are trademarks of Microsoft Corporation.
For plugin developers
Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.