The problem
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.
The module
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
Provenance
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'sclientStatelimit 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-testnode:testsuite, 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.
Fixed in the templates
- Every connection stopped working about an hour after it was made The integration used
@azure/msal-nodeand 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 overfetch, 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
clientStateheld the calendar id, our subscription id, the user id and a signature. Graph capsclientStateat 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 moreShow fewer
- Only the first page of calendars was listed
/me/calendarswas read once and@odata.nextLinkignored; 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(forinteraction_requiredtoo), surfaced through health; the account is identified by the id token'stid: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.
- Every connection stopped working about an hour after it was made The integration used
Non-negotiables
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.
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.
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 todefer.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.
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.
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.
pushBookingEventandremoveBookingEventcatch at their boundary; this rule is checked by reading, as no test yet throws through them.
Fit
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 eventsInstead
google-calendar-sync - Generating bookable slots and subtracting busy timeInstead
time-slot-generator - Public booking pages per host, Calendly-styleInstead
calendar-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
Build it yourself
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-syncClaude 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 directoryreferences/data-model.mdRecords, types, the store contract, the rename tablereferences/oauth-and-tokens.mdEntra ID registration, scopes, the OAuth round trip, token rotation, encryptionreferences/graph-api.mdThe fetch-based client and the Graph facts that shape itreferences/time-zones.mdMailbox zones, Windows names, all-day anchoringreferences/busy-sync.mdWhich events block, the sync run, feeding availabilityreferences/booking-events.mdWriting and deleting booking events, invitations, Teamsreferences/subscriptions.mdOpening, renewing, replacing subscriptions; the cronreferences/calendars-and-settings.mdThe calendar list and the two togglesreferences/http-routes.mdRoute handlers, the webhook, Next.js and Express wiringreferences/firestore-backend.mdCollections, index, rules, storereferences/supabase-backend.mdSchema, RLS, optimistic concurrency, storereferences/operations.mdHealth, logs, configuration, runbookreferences/testing.mdRunning the suite, what it pins, mutation check, doublesreferences/test-suite.mdThe rules and client testsreferences/test-flows.mdThe flow testsreferences/provenance.mdThe audit ledger: fixed, changed by design, kept deliberately, designed hereevals/prompts.mdwith what an operator types, and one file per agent eval run.github/workflows/agent-eval.ymlRuns the agent evals on every published releaseCHANGELOG.mdOne section per release, newest firstCLAUDE.mdEditing conventions for an agent working on this repositoryLICENSEMIT
Recent releases
- v0.1.4October 16, 2026
From scoring the prompt-1 agent eval runs against 0.1.3. Wording only; no template changed.
- v0.1.3October 16, 2026
From scoring the prompt-1 agent eval runs against 0.1.2. Wording only; no template changed.
- v0.1.2October 16, 2026
From scoring the prompt-1 agent eval runs against 0.1.1. Wording only; no template changed.
After installing
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.
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.
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.
FirestoreOur Outlook calendar integration stops working about an hour after a host connects, and their vacations never block anything. Find out why and fix it.
Tested
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.
- Built, checks pass
Gemini CLI0.64.0
gemini-3.8-flash
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
What we saw
Rubric 8/8, scored from the final summary, which this time shows the file list and the
.env.examplebody: the eleven variables empty with the Supabase pair, un-ignored. Templates listed as shipped with the 0.1.1 removal rule;setup.tslazy with the tenant line kept;authenticateonsupabase.auth.getUser(token); 43 tests run unchanged in four zones; and the four handover steps with the redirect URI, permissions,commonfor personal accounts, the GUID and key generated once, public HTTPS and the 12-hour cron. - Built, checks pass
Codex CLIcodex-cli 0.163.0
gpt-6.1-sol
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
What we saw
Rubric 8/8, scored from the transcript. Every template and both route files extracted verbatim and never edited;
setup.tschanges the store, the host seams,defer: afterand two comments, the tenant line as shipped;@supabase/supabase-jsinstalled; the shipped suite run unchanged, 43 in four zones; and the handover: Entra app,.env.examplewithcommonfor 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. - Built, checks pass
Claude Code2.1.301
claude-opus-5-5
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
What we saw
Rubric 8/8, scored from the final summary. Templates copied unchanged with
setup.tsthe only adapted file; 43 tests in four zones throughscripts/test-calendar.sh; Supabase access tokens verified server-side;.env.exampleempty and tracked; the skill's defaults kept andcommonhanded over; and the four steps with their detail, the variables named as those in.env.example. Not rerun.
Build it with Timerise
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:
Step 1
Brief
Tell us what the module must connect to. Takes minutes, in a chat.
Step 2
Prototype in 48 hours
A clickable prototype of your system and a quote, at no cost.
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-syncBuild 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
Generated from the skill's own files at commit 750677c. Every rule above links to where the repository says it. All skills