Local Apple Data Plane for Microsoft 365 / Exchange¶
This integration lets SecondBrain read Microsoft 365 / Exchange email and calendar data locally on macOS without using Microsoft Graph, Entra auth, OAuth device flows, or cloud relays.
The idea is simple:
- Sync your Exchange account into macOS using System Settings -> Internet Accounts -> Microsoft Exchange
- Turn on Mail and Calendar
- Continue using Outlook if you want
- Let SecondBrain read the locally synced Apple Mail / Apple Calendar data surfaces as the agent data plane
This is a local-first feature. It does not require Azure Graph or Exchange Online API access.
What it does¶
SecondBrain adds a local Apple integration layer that can:
- search local Apple Mail messages
- read upcoming Apple Calendar events
- find the next meeting
- extract attendee emails
- correlate unread recent mail from those attendees
- ingest selected local mail/calendar context into the Input OS capture pipeline
- surface health diagnostics when permissions or sync are missing
Why this avoids Azure / Graph¶
This path relies on data already synced to macOS.
SecondBrain reads from:
- Apple Mail
- Apple Calendar
- optional local Apple MCP backends
It does not depend on:
- Microsoft Graph
- Entra app registration
- OAuth device flow
- remote SSE MCP endpoints
- mailbox exports to cloud services
Provider strategy¶
SecondBrain uses a provider abstraction:
-
Native provider Uses local
osascriptexecution against Apple Mail and Calendar -
Local MCP provider Uses a locally configured Apple MCP server if you prefer that backend
-
Fallback mode Returns structured diagnostics instead of crashing when macOS permissions or sync are missing
Which provider should you use?¶
For normal CLI usage and local agent queries, prefer:
native- or
auto
Use mcp only when you specifically want a local Apple MCP backend.
Important: the current apple_local MCP server is not a faster search engine.
It wraps the same native Apple provider through a stdio MCP boundary. That means
preferred_provider: mcp adds subprocess overhead and can still hit the same
Apple Mail scanning limits as the native path.
Config¶
Add Apple integration settings to ~/.config/secondbrain/config.yaml:
apple:
enabled: true
preferred_provider: auto # auto | native | mcp
preferred_mail_account_names:
- "Work Exchange"
preferred_calendar_names:
- "Work"
mcp_server_name: null # set only if using a local Apple MCP server
native_timeout_s: 5
index_rebuild_timeout_s: 60
local_index_enabled: false
body_preview_limit: 280
unread_lookback_days: 14
default_limit: 20
If you want MCP-backed mode, also configure a local server under control_plane.mcp_servers.
Recommended config for direct local usage:
apple:
enabled: true
preferred_provider: native
preferred_mail_account_names:
- "Work Exchange"
local_index_enabled: true
body_preview_limit: 160
Use auto if you want native first with MCP fallback. Avoid mcp as the
default unless you have a specific reason to force the local MCP path.
macOS setup¶
- Open System Settings
- Go to Internet Accounts
- Add Microsoft Exchange
- Enable:
- Calendar
- Open Mail once and allow it to finish syncing
- Open Calendar once and confirm calendars appear
You can continue using Outlook for normal work. This feature is not trying to replace Outlook. It uses macOS-native sync as the local data plane for the agent.
Permissions¶
SecondBrain may need Automation permission for:
- Calendar
If access is denied, macOS may require approval under:
- System Settings -> Privacy & Security -> Automation
Depending on how you launch SecondBrain, grant access to:
- Terminal
- iTerm
- Codex / app host
CLI commands¶
Health:
Mail search:
sb apple mail mailboxes
sb apple mail mailboxes --json
sb apple mail get "Work Exchange/Inbox/12345"
sb apple mail search "product review" --unread-only --limit 10
sb apple mail search "review prep" --account "Work Exchange" --mailbox Inbox --unread-only --limit 10
sb apple mail search "follow up" --flagged-only --json
sb apple mail search "follow up" --sender pm@example.com --json
sb apple mail search "follow up" --live --json
sb apple mail ingest "Work Exchange/Inbox/12345" --project secondbrain
sb apple mail ingest-search "review prep" --limit 3 --defer
sb apple mail ingest-flagged --days 7 --project secondbrain
Important:
sb apple mail search|get|ingest*now default to the local Apple mail index- this avoids the slow Apple Mail live-search path for normal CLI usage
- if the index is empty, the command exits quickly and tells you to run
sb apple index rebuild --days 30 sb apple index rebuild ...works even whenapple.local_index_enabledis currentlyfalse, so you can bootstrap the cache first- rebuild uses a separate longer timeout budget than normal mail CLI calls; tune with
apple.index_rebuild_timeout_sor--timeout-seconds - use
--liveonly when you explicitly want a direct Apple Mail query
Calendar:
sb apple calendar upcoming --limit 10
sb apple calendar get "Work/evt_123"
sb apple calendar attendees "Work/evt_123"
sb apple calendar next
sb apple calendar next --json
sb apple calendar ingest-next --project secondbrain
Local index:
sb apple index status
sb apple index status --json
sb apple index rebuild --days 30 --limit-per-mailbox 200
sb apple index rebuild --days 30 --timeout-seconds 90
sb apple index rebuild --days 14 --account "Work Exchange" --mailbox Inbox --json
Cross-workflow helper:
sb apple workflow next-meeting-unread-mail
sb apple workflow next-meeting-unread-mail --json
sb apple workflow day-summary
sb apple workflow recent-attendee-mail --days 7
sb apple workflow ingest-next-meeting-context --project secondbrain --defer
sb apple workflow day-summary --json now includes both raw events and a
filtered actionable_events / low_salience_events split so chat planning
flows can suppress all-day birthday-style noise by default.
How to choose the correct account and mailbox¶
Apple Mail filters match the local Apple Mail names, not Outlook server IDs.
To discover the exact account and mailbox names available on this Mac:
Look at:
account_namenamefull_name
Example result shape:
Then use those values exactly:
sb apple mail search "review prep" \
--account "Work Exchange" \
--mailbox Inbox \
--unread-only \
--limit 10
Rules:
--accountmust match Apple Mail's local account name exactly--mailboxmust match the local mailbox name exactly- stable message refs such as
Work Exchange/Inbox/12345use the same account and mailbox segments
If you are unsure which calendar names are active, inspect:
and look at each event's calendar_name.
Is indexing required?¶
No. The Apple local index is not required for correctness.
It is recommended when you want:
- repeated free-text mail search
- broader queries across large mailboxes
- lower latency for agent prompts that ask mail questions often
The current service uses the local index only as a performance layer. The source of truth remains Apple Mail / Apple Calendar.
How to avoid timeouts¶
If you see:
the query is usually going through the local MCP wrapper and scanning too much mail live.
Use these rules:
- Prefer
nativeorautoinstead of forcingmcp - Narrow the search with
--account,--mailbox,--date-from,--sender, or--subject - Enable the local index for repeated free-text search
- Keep
preferred_mail_account_namesset so the native provider does not scan every account by default
Recommended first rebuild:
Then search with explicit scope:
sb apple mail search "review prep" \
--account "Work Exchange" \
--mailbox Inbox \
--unread-only \
--limit 10
Operational guidance:
- exact lookup or small mailbox: native provider is usually enough
- broad free-text search: local index is the right path
- direct CLI search: native is usually better than MCP
- MCP is useful for integration boundaries, not for speed
Input OS ingestion¶
The Apple integration is now connected to the same Input OS path used by sb note,
sb meet, sb ingest --normalize, and sb watch.
The Apple ingest commands above:
- create a
RawCapture - persist it into the
capturestable - optionally leave it queued with
--defer - otherwise run the normal classify -> extract -> vault-write -> index path
This means Apple-sourced captures get the same:
- status tracking
- retries
- dedupe
- vault note generation
- local indexing
- source trace back to the Apple stable reference
The source types are:
apple_mailapple_calendar_eventapple_meeting_context
sb apple mail ingest-flagged is the intended low-friction triage path when you
want to mark mail in Apple Mail and then explicitly pull those flagged items
into the Input OS without bulk-ingesting the whole mailbox.
Scheduled automations¶
You can also register Apple-backed jobs with the integration/automation layer:
sb integrations add-apple-flagged-mail apple_flagged \
--account "Work Exchange" \
--mailbox Inbox \
--days 7 \
--max-results 10 \
--project secondbrain
sb integrations add-apple-next-meeting apple_next_meeting \
--project secondbrain \
--tag meeting
sb integrations run-due --type apple_flagged_mail --json
sb integrations run-due --type apple_next_meeting_context --json
These integrations use the same Input OS capture pipeline as the manual Apple ingest commands, but make them schedulable through the existing automation runner.
add-apple-flagged-mail defaults to --only-new, which remembers previously
ingested Apple Mail stable_refs and skips them on later successful runs. Use
--all when you intentionally want a replay of all currently flagged messages
in the selected window.
Core workflow that should work¶
This is the target workflow:
SecondBrain should:
- identify the next meeting from Apple Calendar
- extract attendee emails
- search unread recent Apple Mail messages from those attendees
- return a structured result
Agent tools¶
The feature is also exposed through narrow typed tools:
apple_healthapple_mailboxesapple_mail_getapple_mail_searchapple_calendar_eventsapple_calendar_getapple_calendar_nextapple_event_attendeesapple_day_summaryapple_next_meeting_unread_mailapple_recent_attendee_mail
These are read-only and observable. They do not expose giant unstructured interfaces.
apple_day_summary is intended for daily planning, so its payload now marks
all-day birthdays, anniversaries, and holidays as low-salience instead of
treating them as primary action items.
Diagnostics and health checks¶
sb apple health checks:
- whether you are running on macOS
- whether Mail.app is present
- whether Calendar.app is present
- whether automation access appears available
- whether Mail accounts exist
- whether Calendar data exists
- whether Mail appears to have locally indexed content
Typical remediation messages include:
No accounts were found in Apple MailApple Mail appears present but has little or no locally indexed content yetGrant Automation permission for Terminal/iTerm/Codex to control Mail and Calendar
Current limitations¶
- read/search only
- no sending mail
- no editing events
- no Contacts / Reminders / Notes workflows yet
- native Mail search is correctness-first, not optimized for very large local stores
- when
local_index_enabled: true, SecondBrain maintains a local SQLite FTS cache of messages it has already seen - index rebuild is bounded and recent-message oriented; it does not attempt an unbounded full-mailbox crawl
Troubleshooting¶
sb apple health says not macOS¶
This feature is only supported on macOS.
Mail is installed but no messages are found¶
- Open Apple Mail directly
- Confirm the Exchange account is present
- Let the mailbox finish downloading/indexing
- Retry
sb apple health
Calendar exists but next meeting is empty¶
- Open Apple Calendar once
- Confirm the Exchange calendar is visible
- Add
preferred_calendar_namesif you have many calendars
Native provider fails with permission issues¶
Approve Automation access in macOS Privacy settings and retry.
You want a local MCP backend instead of direct AppleScript¶
Set:
and configure a local Apple MCP server under control_plane.mcp_servers.
Be aware that the current apple_local server wraps the same native provider.
Choose this for integration reasons, not because it will be faster than
preferred_provider: native.
The local index seems empty¶
Check:
apple.local_index_enabled: truein config- run
sb apple index status - run
sb apple index rebuild --days 30
The local index is optional and local-only. It is a performance layer, not the source of truth.
Mail search is timing out¶
Try these in order:
- set
apple.preferred_provider: native - run
sb apple mail mailboxes --jsonand pick one account/mailbox explicitly - rebuild a bounded local index
- retry the search with
--accountand--mailbox
Files in the repo¶
brain/apple/service.pybrain/apple/providers/native.pybrain/apple/providers/mcp.pybrain/cli/apple_cmd.pybrain/kernel/tools/platform_catalog.py