Skip to content

HWqs/beta

Repository files navigation

outlook-apple-sync

One-way sync of Outlook meeting invitations into Apple Calendar, plus a small web dashboard so you can see everything at a glance. Reads your Outlook calendar via the Microsoft Graph API and mirrors events into an iCloud calendar over CalDAV. Reschedules, location changes, cancellations, and attendee updates flow through; file attachments are cached locally and served from the dashboard.

Designed to run on a NAS (or any always-on Linux box): one long-running process serves the dashboard, cron kicks off the sync every few minutes.

What it syncs

  • Events in the window [now - SYNC_WINDOW_PAST_DAYS, now + SYNC_WINDOW_FUTURE_DAYS]
  • Recurring events are expanded per occurrence (via Graph calendarView) so a single rescheduled occurrence updates independently of the series
  • By default, only events where you are an invitee (not the organizer) — set SYNC_INVITES_ONLY=false to mirror everything
  • Cancellations and events that fall out of the window are removed from the mirror calendar

It does not modify your Outlook calendar, and it only writes to the single iCloud calendar you name (ICLOUD_CALENDAR_NAME). Keep that calendar dedicated to the sync — anything you put in it by hand may be deleted.

Prerequisites

1. Register a Microsoft Entra application

  1. Go to https://entra.microsoft.com/App registrationsNew registration.
  2. Name it anything. Supported account types: "Accounts in any organizational directory and personal Microsoft accounts" (unless you only need work).
  3. After creation, open AuthenticationAdd a platformMobile and desktop applications → add redirect URI http://localhost and enable "Allow public client flows".
  4. API permissionsMicrosoft GraphDelegated → add:
    • Calendars.Read
    • Mail.Read (optional; only needed if you later extend this to scan mail)
    • User.Read
    • offline_access
  5. Copy the Application (client) ID into MS_CLIENT_ID.

2. Generate an iCloud app-specific password

  1. https://appleid.apple.com/Sign-In and SecurityApp-Specific Passwords.
  2. Create one for "Outlook Sync" and put it in ICLOUD_APP_PASSWORD.
  3. In the Apple Calendar app, create a new calendar (e.g. Outlook) under your iCloud account and set ICLOUD_CALENDAR_NAME to match.

Install

Docker (recommended for NAS)

git clone <this repo> /volume1/apps/outlook-apple-sync
cd /volume1/apps/outlook-apple-sync
cp .env.example .env   # fill in credentials
docker compose build

# First-time login (interactive device-code flow). The `sync` service is a
# CLI helper under the "cli" compose profile.
docker compose --profile cli run --rm sync login

# Verify creds
docker compose --profile cli run --rm sync check

# Start the dashboard (binds 0.0.0.0:8765 in the container)
docker compose up -d web

Dashboard will be at http://<nas>:8765. Then install the cron line from deploy/nas-cron.example so the sync runs every 10 minutes.

Plain Python (systemd)

python3 -m venv /opt/outlook-apple-sync/.venv
/opt/outlook-apple-sync/.venv/bin/pip install -e /opt/outlook-apple-sync
/opt/outlook-apple-sync/.venv/bin/outlook-apple-sync login
sudo cp deploy/systemd/* /etc/systemd/system/
sudo systemctl enable --now outlook-apple-sync.timer

CLI

outlook-apple-sync login    # one-time device-code login
outlook-apple-sync check    # prove Outlook + iCloud creds work
outlook-apple-sync sync     # run one sync pass
outlook-apple-sync status   # show recent runs
outlook-apple-sync web      # serve the dashboard (default 127.0.0.1:8765)

Dashboard

outlook-apple-sync web --host 0.0.0.0 --port 8765 starts a read-only dashboard backed by the same SQLite database the sync writes. Features:

  • Up next card with live countdown and Join button
  • Today and Next 7 days lists
  • Event detail pages with organizer, location, attendees, notes, and attachments
  • File attachments are served from the local cache; reference attachments (OneDrive/SharePoint links) redirect out; nested items are listed but open in Outlook
  • Sync now button triggers a background sync; the page auto-refreshes every minute

Auth: set WEB_USERNAME and WEB_PASSWORD in .env to enable HTTP basic auth. Leave them unset if you're binding to 127.0.0.1 only or sitting behind a reverse proxy / Tailscale / VPN.

How it works

  1. GET /me/calendarView returns every event instance in the configured window, with iCalUId included.
  2. Each event is rendered to iCalendar text and hashed on semantic fields (subject, start/end, location, attendees, join URL, cancellation flag).
  3. SQLite (state/sync.sqlite) maps Outlook event IDs to the CalDAV href and the last content hash.
  4. For each Outlook event: insert if new, overwrite in iCloud if the hash changed, otherwise just touch last_seen_run.
  5. Any row whose last_seen_run wasn't updated this pass is considered removed and deleted from iCloud — but only if the Graph fetch completed successfully, so a transient network error can't wipe your calendar.
  6. File attachments are downloaded lazily per event (only when hasAttachments=true) and stored under state/attachments/<hash>/. SQLite is kept in WAL mode so the dashboard can read while the sync writes.

Limits and non-goals

  • One-way only: changes you make in Apple Calendar are not pushed back to Outlook and may be overwritten.
  • No attendee RSVP: the sync doesn't respond to invitations on your behalf.
  • Window-bound: events outside [past, future] are not mirrored. Keep the window large enough to cover your planning horizon.
  • Throttling: if Graph returns 429, the run aborts and cron retries on the next tick; no backoff inside a single run.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors