A scheduling app has exactly one job: offer times that are genuinely free and book them without collisions. On Outlook that job runs on Microsoft Graph, which ships two tools most integrators overlook, a free/busy endpoint that reveals availability without exposing private event detail, and a meeting-time finder that does the intersection math for you. This piece is about building the scheduling layer itself: the availability patterns that work, the enterprise realities of Microsoft 365, the time-zone traps, and the edge cases that surface once real users are on the system.
Introduction
A scheduling app has one job: offer times that are actually free, and book them without collisions. On Outlook, that job runs on Microsoft Graph, and Graph happens to ship two purpose-built tools that most integrators either miss or misuse: a free/busy endpoint that gives you availability without reading private event detail, and a meeting-time finder that does the intersection math for you. This piece is about building the scheduling layer specifically, the patterns that work, the enterprise realities of Microsoft 365, and the edge cases that will eventually find you. The Graph calendar layer beneath it is documented in full at https://www.unipile.com/guide-to-using-microsoft-outlook-calendar-api/.
What a scheduling app really needs from a calendar
Strip a scheduler down and it needs four things from the calendar: a truthful read of when people are busy, a way to propose times that respect working hours and time zones, a write that commits a chosen slot atomically, and a live signal when any of that changes underneath it. Notice what is not on the list: it does not need to read the contents of people’s meetings. A good Outlook scheduler reads availability, not private detail, which is both a privacy win and a permissions win, because you can often request narrower scopes.
Free/busy without opening the calendar
The workhorse for availability is getSchedule, a Graph action that takes a set of mailboxes and a time window and returns each one’s busy, tentative, out-of-office, and free blocks, along with working-hours information, without exposing subjects or attendees. For a scheduling product this is exactly the right primitive: you learn that a person is busy from 2 to 3, not what they are doing, which is all a booking flow should ever need.
getSchedule shines for the “when is this group free” question because you can pass several mailboxes at once and intersect the results yourself. It also lets you choose the availability-view granularity, so you can request coarse or fine-grained blocks depending on how tightly you slot meetings. Build your availability engine on getSchedule rather than reading full events, and you get a smaller permission footprint and a cleaner privacy story for free.
Letting Graph do the math with findMeetingTimes
When you want the platform to propose slots rather than compute them yourself, Graph offers findMeetingTimes. You hand it attendees (required and optional), a duration, one or more time windows, and constraints like minimum attendee availability, and it returns ranked candidate slots with a confidence score for each. It factors in working hours and existing commitments so you do not have to reimplement the intersection logic.
The trade-off is control. findMeetingTimes is fast to adopt and great for “suggest three times that work for everyone”, but its ranking and constraints are Microsoft’s, not yours. Many mature schedulers use a hybrid: getSchedule when they want full control over how slots are generated and presented, findMeetingTimes when they want a quick, sensible suggestion without owning the algorithm. Knowing both exist, and when each fits, is most of the battle.
Working hours, time zones, and mailbox settings
Availability is meaningless without the context of when someone actually works and in which zone. Graph exposes mailbox settings, including the user’s time zone, working hours, and working days, and a serious scheduler reads them rather than assuming a nine-to-five in the server’s zone. Two people can both be “free at 8am” and only one of them wants a meeting then.
The recurring hazard is time-zone handling. Outlook events carry a dateTime plus a named time zone, and you should set the Prefer header to control the zone times come back in, then render each candidate slot in the viewer’s own local time. A meeting that a scheduler in New York offers to a colleague in Berlin has to read as the right wall-clock time on both ends, across daylight-saving changes, which only works if you carry named zones end to end rather than bare offsets.
The enterprise realities of Microsoft 365
Scheduling apps disproportionately land in Microsoft 365 organizations, which brings enterprise behavior you cannot ignore. Access to other people’s free/busy is governed by tenant and mailbox permissions, so what a user can see about a colleague depends on how their organization is configured, and your app inherits those limits. Many Graph calendar permissions require administrator consent, so onboarding a company often means an IT admin approves your app for the whole tenant before any employee can connect.
There is also room resource booking: conference rooms and equipment are represented as their own mailboxes, so a scheduler that books rooms is just scheduling against another calendar, using the same availability and event primitives. Designing for M365 from the start (admin consent, tenant-scoped visibility, room mailboxes) saves a painful retrofit when your first enterprise customer arrives.
Edge cases that will eventually find you
The demo works; production is where the long tail lives. A representative sample:
- Tentative and out-of-office blocks are not the same as busy, and your slotting logic has to decide how each counts.
- Double bookings from races: availability is a snapshot, so re-check right before writing and treat a conflicting write as a normal outcome to handle, not a crash.
- All-day and multi-day events interact oddly with slot generation if you assume every event has a neat start and end time.
- Recurring meetings with exceptions mean “this Thursday moved” must not be read as “the whole series moved”.
- Cancellations arrive as state changes to reconcile, and freeing the slot promptly is what keeps your availability honest.
- Delegate and shared mailboxes add another actor who can change a calendar you are also writing to.
None of these is exotic. Every one of them will show up once real users and real enterprises are on the system, and a scheduler earns trust precisely by handling them quietly.
Booking rules and buffers your product should own
Raw availability is not the same as bookable time. Most scheduling products layer their own rules on top of what the calendar reports: minimum notice before a meeting can be booked, buffer time between back-to-back appointments, a daily cap on how many bookings a person will accept, and blackout periods that have nothing to do with existing events. Graph will happily tell you a slot is technically free at 7pm, and your product still has to decide whether it should be offered.
Keep these rules in your own domain rather than trying to encode them in the calendar. The calendar is the source of truth for what already exists; your product is the source of truth for what you are willing to book. Blur that line and you either offer times you should not or block times you should not, both of which erode trust.
Reschedules and cancellations in a scheduler
The unhappy path is where schedulers earn their keep. A reschedule has to feel atomic to both parties: the old slot frees, the new one commits, and no one ever sees both live at once. A cancellation has to release the slot promptly so it becomes bookable again, and it has to reconcile cleanly with the change notification that Graph will send you about the same event, so you do not double-process it. Because Outlook represents these as state changes rather than clean deletions, your reconciliation logic has to be idempotent, treating the same cancellation seen twice as one event, not two.
Keeping availability fresh
A scheduler is only as trustworthy as its freshness. If your view of a calendar lags reality, you will offer slots that are already taken. Pair Graph subscriptions (webhooks for change notifications) with delta queries so you learn about changes in near real-time instead of polling, and remember that subscriptions expire within a few days and need a renewal job. The goal is that a slot you display was free as of moments ago, not minutes ago.
Build the scheduling layer, integrate the calendar layer
The scheduling logic, how you generate slots, rank them, present them, and enforce your own booking rules, is often where your product actually competes, and worth building yourself. The calendar layer beneath it, OAuth against Entra ID, admin consent, subscription renewal, delta sync, time-zone plumbing, room mailboxes, is the same for every Outlook scheduler and is exactly the part that quietly breaks. For a practical reference to that layer, a dedicated Outlook Calendar API guide is worth keeping on hand. And if your scheduler needs to work across Google Calendar too, all of the Graph-specific work above has an entirely separate twin on Google’s side, which is why cross-provider schedulers so often normalize both calendars behind one interface and spend their real effort on the scheduling experience instead.
