Skip to content

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:

  1. Sync your Exchange account into macOS using System Settings -> Internet Accounts -> Microsoft Exchange
  2. Turn on Mail and Calendar
  3. Continue using Outlook if you want
  4. 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:

  1. Native provider Uses local osascript execution against Apple Mail and Calendar

  2. Local MCP provider Uses a locally configured Apple MCP server if you prefer that backend

  3. 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

  1. Open System Settings
  2. Go to Internet Accounts
  3. Add Microsoft Exchange
  4. Enable:
  5. Mail
  6. Calendar
  7. Open Mail once and allow it to finish syncing
  8. 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:

  • Mail
  • 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:

sb apple health
sb apple health --json

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 when apple.local_index_enabled is currently false, 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_s or --timeout-seconds
  • use --live only 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:

sb apple mail mailboxes --json

Look at:

  • account_name
  • name
  • full_name

Example result shape:

[
  {
    "account_name": "Work Exchange",
    "name": "Inbox",
    "full_name": "Work Exchange/Inbox"
  }
]

Then use those values exactly:

sb apple mail search "review prep" \
  --account "Work Exchange" \
  --mailbox Inbox \
  --unread-only \
  --limit 10

Rules:

  • --account must match Apple Mail's local account name exactly
  • --mailbox must match the local mailbox name exactly
  • stable message refs such as Work Exchange/Inbox/12345 use the same account and mailbox segments

If you are unsure which calendar names are active, inspect:

sb apple calendar upcoming --limit 20 --json

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:

mcp.stdio: process timed out after 45s

the query is usually going through the local MCP wrapper and scanning too much mail live.

Use these rules:

  1. Prefer native or auto instead of forcing mcp
  2. Narrow the search with --account, --mailbox, --date-from, --sender, or --subject
  3. Enable the local index for repeated free-text search
  4. Keep preferred_mail_account_names set so the native provider does not scan every account by default

Recommended first rebuild:

sb apple index rebuild --days 30 --account "Work Exchange" --mailbox Inbox --limit-per-mailbox 200

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 captures table
  • 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_mail
  • apple_calendar_event
  • apple_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:

Check my calendar for my next meeting and find unread emails from the attendees.

SecondBrain should:

  1. identify the next meeting from Apple Calendar
  2. extract attendee emails
  3. search unread recent Apple Mail messages from those attendees
  4. return a structured result

Agent tools

The feature is also exposed through narrow typed tools:

  • apple_health
  • apple_mailboxes
  • apple_mail_get
  • apple_mail_search
  • apple_calendar_events
  • apple_calendar_get
  • apple_calendar_next
  • apple_event_attendees
  • apple_day_summary
  • apple_next_meeting_unread_mail
  • apple_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 Mail
  • Apple Mail appears present but has little or no locally indexed content yet
  • Grant 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_names if 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:

apple:
  enabled: true
  preferred_provider: mcp
  mcp_server_name: apple_local

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: true in 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:

  1. set apple.preferred_provider: native
  2. run sb apple mail mailboxes --json and pick one account/mailbox explicitly
  3. rebuild a bounded local index
  4. retry the search with --account and --mailbox

Files in the repo

  • brain/apple/service.py
  • brain/apple/providers/native.py
  • brain/apple/providers/mcp.py
  • brain/cli/apple_cmd.py
  • brain/kernel/tools/platform_catalog.py