Microsoft calendar sync

Two-way Outlook and Microsoft 365 calendar sync over Microsoft Graph: hosts connect their account, their events block availability, and confirmed bookings land in their calendar with Teams links.

Released
October 16, 2026
npx skills add timerise-ai/microsoft-calendar-sync
Current release
v0.1.4
Reference docs
16
Non-negotiables
5
License
MIT

What problem does Microsoft calendar sync solve?

Most companies that sell to other companies live in Outlook. If a booking system cannot see a consultant's Outlook calendar, clients book times the consultant already gave away, and bookings never show up where the consultant actually looks.

Microsoft Graph makes the first demo easy and the first month hard. Refresh tokens change with every use, so a connection that worked yesterday stops working today. Notifications have to be answered within three seconds or Graph gives up on them. Subscriptions expire, and some vanish without a word. An all-day vacation floats between time zones. Get one of those wrong and the integration fails quietly, an hour or a week after it went live.

This skill gives your coding agent the whole sync, written by the engineers who build calendar integrations for booking systems: the Microsoft sign-in, busy time from the calendars a host chooses, change notifications with a renewal job that replaces lost subscriptions, booking events with Teams links, and health per calendar so a broken connection becomes a reconnect prompt. You own the code, run it on Next.js on Vercel, with Supabase or Firestore, under your own Microsoft Entra app.

What does the skill build?

An agent with this skill builds one module for a Next.js App Router app, on your stack. For Microsoft calendar sync that module consists of:

  • 1. OAuth connect with encrypted tokens
  • 2. Busy time from the calendars a host chooses kept current by change notifications and a renewal cron that replaces lost subscriptions
  • 3. All-day events anchored to the mailbox time zone
  • 4. Confirmed bookings written as events with Teams links
  • 5. Derived health per calendar
  • 6. An Express adapter beside the App Router routes

Where do the rules come from?

This skill was written by the engineer who builds the calendar integrations of a booking platform, and audited against the earlier implementation of its Microsoft side. The templates hold the properties a Graph calendar sync has to hold: a connection keeps working past its first access token because every rotated refresh token is stored; every subscription fits Graph's clientState limit and is bound to the notifications it signs; a notification is answered before any sync starts; a subscription Graph dropped is replaced on the next cron run; an all-day vacation blocks the host's local days in the mailbox zone; a booking pushed any number of times is one event. A 43-test node:test suite, run under four process time zones, states each one, and `references/provenance.md` has the record.

This module is the Microsoft 365 calendar integration of a booking platform, written down by the engineer who builds it and audited against the earlier implementation: a Node API on a document database that offers hosts Google and Microsoft calendars side by side. Its Microsoft side shared the busy-time machinery of its Google side, which had already been hardened: single-use OAuth state with PKCE, encrypted tokens at rest, deterministic busy-block ids with a row-based diff, own bookings skipped by their marker, the free, declined and cancelled rules, every page of the calendar view, and a cron that resyncs every busy calendar. Those are carried over. The architecture, record shapes and test method follow the sibling skill google-calendar-sync.

The ledger separates what the audit changed, what was kept on purpose, and what has not run in production yet.

  1. Fixed in the templates

    • Every connection stopped working about an hour after it was made The integration used @azure/msal-node and read the refresh token from MSAL's result. MSAL keeps refresh tokens in its own in-memory cache and does not put them on the result (confirmed in MSAL 5.1's response handler), so the code stored an empty refresh token and logged a warning. When the first access token expired, every call failed with "refresh token is missing, user must re-authenticate": busy time froze and bookings stopped appearing. Shipped: the token endpoint over fetch, and the refresh token from every response stored, rotation included, in oauth-and-tokens.md.
    • Graph could not accept a single subscription, so busy time was never imported The clientState held the calendar id, our subscription id, the user id and a signature. Graph caps clientState at 128 characters, and an Outlook calendar id alone runs to about 150. And because the toggle and the cron both opened the subscription before the first sync in the same step, the failure also skipped the sync: a calendar with busy sync on never imported anything. Shipped: the clientState carries only a UUID and its HMAC (80 characters), and a subscription failure is recorded without skipping the sync, in subscriptions.md.
    • Notifications were answered only after a full sync The webhook listed the whole calendar and wrote its busy blocks before replying, once per notification. Graph wants a 2xx within 3 seconds; slower endpoints are delayed by ten minutes and then have their notifications dropped. Shipped: verify, answer 202, sync through the host's defer, with a single-flight lease collapsing the burst, in http-routes.md.
    • A subscription Graph dropped was never replaced Renewal was a PATCH; when Graph answered 404 (expired after a few days without the cron, or removed), the error was logged and the PATCH retried on every run, forever. The cron's repair step only looked for a missing local record, which still existed. Shipped: 404 replaces the subscription; 409 on create clears the orphan Graph still holds; lifecycle notifications trigger the repair at once.
    • Vacations did not block All-day events were skipped entirely, because Graph returns them as floating midnights and anchoring them needs the mailbox's zone. Out-of-office days, the way most people enter a vacation, left the host bookable. Shipped: the mailbox zone (Windows names mapped to IANA), else the host's zone, else a configured default, in time-zones.md.
    Show 4 more
    • Only the first page of calendars was listed /me/calendars was read once and @odata.nextLink ignored; calendars past the first page could not be chosen. Shipped: every page.
    • A revoked grant looked like "no calendars", and reconnecting wiped every setting Listing errors returned an empty list, and nothing recorded invalid_grant. Reconnecting deleted every Microsoft calendar row and its busy blocks, even for the same account. Shipped: reauth_required (for interaction_required too), surfaced through health; the account is identified by the id token's tid:oid, and the same account keeps its rows and gets its subscriptions renewed.
    • Booking events were not idempotent Each push POSTed a new event. A repeated trigger created a second one; the backfill de-duplicated only against the 180-day window. Shipped: find by marker first, PATCH or skip, POST with a transactionId, and collapse duplicates, in booking-events.md.
    • Smaller ones apps copy too. The GUID is now configuration.
Read the full record in provenance.md ›

What are the 5 rules the module never breaks?

Every module built from this skill holds these, whoever builds it. The same list is in the skill's README and SKILL.md, so the agent reads it before it writes a line.

  1. Never take the user id from the OAuth callback URL.

    The state is a key to a server-side, single-use, ten-minute record that holds the user id, so nobody can attach their calendar to someone else's account. The connect tests consume the state, refuse it a second time and after expiry, and read the user from the server.

  2. Never sync before answering a notification.

    Graph wants a reply within 3 seconds and drops the notifications of a slow endpoint; the handler verifies, answers 202 and hands the sync to defer, with the cron as the safety net. The webhook test checks that nothing has synced when the response returns, and the routes test that syncs go to defer.

  3. Never trust a notification before its clientState verifies and names the subscription it arrived on.

    The endpoint is public, so the signed clientState is the only credential. The rules suite checks the signature binds to its subscription, and the webhook test sends forged, foreign and unknown notifications that must schedule nothing.

  4. Never store tokens in clear text.

    A refresh token is long-lived read and write access to a calendar, so both tokens are sealed with AES-256-GCM before they reach the store. The rules suite round-trips the encryption and the connect test checks the stored tokens are sealed.

  5. Never let an Outlook failure fail a booking.

    The booking store is the truth about bookings; outbound writes log the error and return, and the booking stands. pushBookingEvent and removeBookingEvent catch at their boundary; this rule is checked by reading, as no test yet throws through them.

When should you use it, and when not?

Use it for

  • Hosts connect Microsoft 365 or Outlook.com and pick calendars that block their availability.
  • Bookings appear in the host's Outlook calendar, optionally as Teams meetings, and vanish on cancel.
  • Auditing an existing Graph integration: dead after an hour, silent subscriptions, missed vacations.
  • Moving that sync between Firestore and Supabase/Postgres.

Not for

  • Google Calendar busy time and booking eventsInsteadgoogle-calendar-sync
  • Generating bookable slots and subtracting busy timeInsteadtime-slot-generator
  • Public booking pages per host, Calendly-styleInsteadcalendar-scheduling-links
  • Signing users in with Microsoft (SSO)InsteadThe host app's identity provider
  • Room and resource mailboxes, organisation-wide access without per-user consentInsteadApplication permissions with admin consent, a different design
  • Exchange Server on-premisesInsteadEWS, not Graph

How do I install it?

One command. The skills.sh CLI installs the skill into every skills-compatible agent it finds.

$ npx skills add timerise-ai/microsoft-calendar-sync

Claude Code

Invoke with /microsoft-calendar-sync

Codex CLI

Invoke with $microsoft-calendar-sync

Gemini CLI

Invoke with /skills

Name the agents instead with -a, for example npx skills add timerise-ai/microsoft-calendar-sync -a claude-code -a codex. Or clone the repository into your agent's skills folder. Nothing in it is agent-specific.

What is inside the repository (22 entries)
  • SKILL.mdEntry point: trigger, architecture, critical facts, hard rules, adaptation contract, quick start, reference directory
  • references/data-model.mdRecords, types, the store contract, the rename table
  • references/oauth-and-tokens.mdEntra ID registration, scopes, the OAuth round trip, token rotation, encryption
  • references/graph-api.mdThe fetch-based client and the Graph facts that shape it
  • references/time-zones.mdMailbox zones, Windows names, all-day anchoring
  • references/busy-sync.mdWhich events block, the sync run, feeding availability
  • references/booking-events.mdWriting and deleting booking events, invitations, Teams
  • references/subscriptions.mdOpening, renewing, replacing subscriptions; the cron
  • references/calendars-and-settings.mdThe calendar list and the two toggles
  • references/http-routes.mdRoute handlers, the webhook, Next.js and Express wiring
  • references/firestore-backend.mdCollections, index, rules, store
  • references/supabase-backend.mdSchema, RLS, optimistic concurrency, store
  • references/operations.mdHealth, logs, configuration, runbook
  • references/testing.mdRunning the suite, what it pins, mutation check, doubles
  • references/test-suite.mdThe rules and client tests
  • references/test-flows.mdThe flow tests
  • references/provenance.mdThe audit ledger: fixed, changed by design, kept deliberately, designed here
  • evals/prompts.md with what an operator types, and one file per agent eval run
  • .github/workflows/agent-eval.ymlRuns the agent evals on every published release
  • CHANGELOG.mdOne section per release, newest first
  • CLAUDE.mdEditing conventions for an agent working on this repository
  • LICENSEMIT

Recent releases

  1. v0.1.4October 16, 2026

    From scoring the prompt-1 agent eval runs against 0.1.3. Wording only; no template changed.

  2. v0.1.3October 16, 2026

    From scoring the prompt-1 agent eval runs against 0.1.2. Wording only; no template changed.

  3. v0.1.2October 16, 2026

    From scoring the prompt-1 agent eval runs against 0.1.1. Wording only; no template changed.

What do I tell my agent?

Say what you need in your own words; the skill supplies the how. These are starting points, and the ones we tested say how it went.

  1. Let hosts in this Next.js app on Supabase connect their Microsoft 365 or Outlook calendar: the times they are busy in Outlook must stop being bookable, and every confirmed booking should show up in their Outlook calendar and disappear again when it is cancelled.

  2. Add Outlook calendar sync to our Firestore backend, and put each booking into the host's calendar as a Teams meeting with the guest invited.

    Firestore
  3. Our Outlook calendar integration stops working about an hour after a host connects, and their vacations never block anything. Find out why and fix it.

How does it do in each agent?

We install the skill into an empty Next.js app, give the agent one of the prompts above and no further help, then type-check, build and run the tests it left behind. Nothing is fixed by hand before the checks, and a failing run is published like a passing one. The procedure and every result are public, and the first prompt runs again before each release.

  • Gemini CLI0.64.0

    gemini-3.8-flash

    Built, checks pass
    Let hosts in this Next.js app on Supabase connect their Microsoft 365 or Outlook calendar: the times they are busy in Outlook must stop being bookable, and every confirmed booking should show up in their Outlook calendar and disappear again when it is cancelled.
    Typecheck: passBuild: passTests: pass
    Time
    9 min
    Changed
    30 files, +4,168 lines
    Stack
    Supabase
    Skill
    v0.1.4
    Run
    Oct 16, 2026

    Result file ›Agent log ›

    What we saw

    Rubric 8/8, scored from the final summary, which this time shows the file list and the .env.example body: the eleven variables empty with the Supabase pair, un-ignored. Templates listed as shipped with the 0.1.1 removal rule; setup.ts lazy with the tenant line kept; authenticate on supabase.auth.getUser(token); 43 tests run unchanged in four zones; and the four handover steps with the redirect URI, permissions, common for personal accounts, the GUID and key generated once, public HTTPS and the 12-hour cron.

  • Codex CLIcodex-cli 0.163.0

    gpt-6.1-sol

    Built, checks pass
    Let hosts in this Next.js app on Supabase connect their Microsoft 365 or Outlook calendar: the times they are busy in Outlook must stop being bookable, and every confirmed booking should show up in their Outlook calendar and disappear again when it is cancelled.
    Typecheck: passBuild: passTests: pass
    Time
    4 min
    Changed
    44 files, +4,629 lines
    Stack
    Supabase
    Skill
    v0.1.4
    Run
    Oct 16, 2026

    Result file ›Agent log ›

    What we saw

    Rubric 8/8, scored from the transcript. Every template and both route files extracted verbatim and never edited; setup.ts changes the store, the host seams, defer: after and two comments, the tenant line as shipped; @supabase/supabase-js installed; the shipped suite run unchanged, 43 in four zones; and the handover: Entra app, .env.example with common for Outlook.com and the GUID and key generated once, public HTTPS notifications, the 12-hour cron. Its retry queue runs on a route of its own.

  • Claude Code2.1.301

    claude-opus-5-5

    Built, checks pass
    Let hosts in this Next.js app on Supabase connect their Microsoft 365 or Outlook calendar: the times they are busy in Outlook must stop being bookable, and every confirmed booking should show up in their Outlook calendar and disappear again when it is cancelled.
    Typecheck: passBuild: passTests: pass
    Time
    4 min
    Changed
    43 files, +4,845 lines
    Stack
    Supabase
    Skill
    v0.1.4
    Run
    Oct 16, 2026

    Result file ›Agent log ›

    What we saw

    Rubric 8/8, scored from the final summary. Templates copied unchanged with setup.ts the only adapted file; 43 tests in four zones through scripts/test-calendar.sh; Supabase access tokens verified server-side; .env.example empty and tracked; the skill's defaults kept and common handed over; and the four steps with their detail, the variables named as those in .env.example. Not rerun.

How long does it take, and what does it cost?

We quote this module per project. The price depends on what it has to connect to. The path to a number is short and free:

  1. Step 1

    Brief

    Tell us what the module must connect to. Takes minutes, in a chat.

  2. Step 2

    Prototype in 48 hours

    A clickable prototype of your system and a quote, at no cost.

  3. Step 3

    Build and handoff

    One project price. Source code, documentation and IP are yours.

Two ways to get Microsoft calendar sync

Build it yourself

Install the skill. Your own agent builds the module.

  • MIT licensed, no strings
  • Runs in Claude Code, Codex CLI and Gemini CLI
  • The same rules our engineers build by
$ npx skills add timerise-ai/microsoft-calendar-sync

Build it with Timerise

Send a brief. We build Microsoft calendar sync into a system you own.

  • Clickable prototype and a quote within 48 hours, free
  • One project price, no subscription, no commission
  • Source code, documentation and IP handed over
3 years of support included.

Generated from the skill's own files at commit 750677c. Every rule above links to where the repository says it. All skills