Skip to content

MS Teams Meeting Scheduler — Setup Guide

Audience: Payroll executives, payroll leads, and platform admins configuring the self-serve "Schedule a meeting" button that clients see on their cycle portal.

Goal: When a client opens their portal (intake, approval, or submitted view) and clicks the Support button, the primary action they see should be a one-click path to book a meeting with their payroll executive — not an email ping. This doc walks through producing the booking URL in Microsoft 365 and pasting it into BreezyCorp.


1. What this unlocks

Once configured per client:

Portal surfaceBefore (no URL set)After (URL set)
Support button (top-right, every view)Popover shows only a mailto: to the PEPopover shows a primary Schedule a meeting button (indigo, opens Teams Bookings in a new tab); the email option drops to a secondary link
"Need help with this cycle?" card (bottom of intake + withdraw-available submitted view)"Ask payroll executive to contact me" notify button"Schedule a meeting" primary CTA

The URL lives on the Client record (clients.teams_scheduler_url). Leave it blank and both surfaces silently fall back to the legacy email-the-PE behaviour — it's purely additive.


2. Pick the right flavour of Microsoft Bookings

Microsoft gives you two ways to share a personal booking page. Either works; choose whichever matches how your tenant is licensed.

Option A — Bookings with me (personal, per-user)

Best for: each payroll executive exposing their own calendar. No admin work needed beyond the seat license.

  1. Sign in to Microsoft 365 as the payroll executive: https://outlook.office.com/bookwithme/me
  2. On first visit, Outlook will prompt you to create your booking page. Accept the defaults — you can refine later.
  3. Add at least one public meeting type (e.g. "Payroll cycle discussion — 30 min"):
    • Duration: 15 / 30 / 45 min (30 is a good default).
    • Location: Microsoft Teams meeting (this is the whole point — the booking auto-generates a Teams link).
    • Availability: tie it to your working hours calendar.
    • Buffer / lead time: 15 min buffer, 4 h minimum lead time recommended.
  4. Click Copy link on the meeting type. The URL looks like:
    https://outlook.office.com/bookwithme/user/<uuid>@<tenant>/meetingtype/<slug>?anonymous&ismsaljsauthenabled
    That's the URL you'll paste into BreezyCorp. Keep both ?anonymous (so clients don't need to sign in) and &ismsaljsauthenabled query params intact.

Option B — Microsoft Bookings (shared mailbox, per-team)

Best for: a shared inbox like payroll-ops@yourcompany.com with multiple payroll executives as staff. Clients pick a time; Bookings assigns whichever exec is free.

  1. Go to https://outlook.office.com/bookings and create a new booking page.
  2. Add services and staff members. Tick Add online meeting to auto-attach a Teams link.
  3. Publish the page, then copy the "Share your booking page" URL from settings:
    https://outlook.office.com/owa/calendar/<slug>@<tenant>/bookings/
  4. (Optional) For service-specific links, use:
    https://outlook.office.com/owa/calendar/<slug>@<tenant>/bookings/s/<serviceId>

Both flavours produce an https:// link you can paste as-is.


3. Paste the URL into BreezyCorp

Who can do this: PLATFORM_ADMIN or PAYROLL_LEAD (anyone with MANAGE_CLIENTS).

  1. Sign in to the dashboard at /login.
  2. Navigate to Clients → {client} → Settings → Profile.
  3. Click Edit (top-right of the Profile card).
  4. Fill in MS Teams meeting scheduler URL with the link from Step 2.
  5. Click Save.

Validation rules enforced client-side and on the API:

  • Must begin with https:// (both the form toast and PUT /admin/clients/:id reject anything else with "teamsSchedulerUrl must start with https://").
  • Empty string is normalized to null on the server (i.e. saving an empty field clears the URL and flips the portal back to the email fallback).
  • No length limit, but keep it under ~2048 chars to avoid edge-case email-client truncation.

Read-mode affordances:

After saving, the Profile card shows a compact strip with the URL plus two icon buttons:

  • Copy — copies the URL to clipboard; the icon flips to an emerald tick for ~1.5 s.
  • Open in new tab — opens the scheduler so you can sanity-check the booking page renders correctly.

4. Verify end-to-end

Run this the first time you set up a new executive or client to catch issues before a real cycle.

  1. Dashboard side: open Clients → {client}, confirm the URL shows in the Profile card, click Open in new tab → the Teams Bookings page must render without requiring a sign-in prompt for the end user.
  2. Portal side: generate a test cycle (or open an in-flight one) and click the client's magic link in Mailpit.
  3. In the portal header, click Support. The popover's primary action should read Schedule a meeting with an indigo background. Hover-cue: "Opens your payroll executive's Teams booking page in a new tab."
  4. Click it — the scheduler should open in a new tab, same page as step 1.
  5. Clear the URL back to empty → the Support popover should flip to showing Email payroll executive as the primary (and only) action.

5. FAQ

Q. One payroll executive handles multiple clients. Do I paste the same URL on every client? Yes. The scheduler URL is scoped to Client (not StaffUser) so it can be overridden per-client when you want to route different clients to different executives. For "one executive, many clients", paste the same URL on each. You can script bulk updates via PUT /admin/clients/:id/config.

Q. The PE changed their booking page. Do I need to update every client? Yes — there's no central rewrite. If this becomes painful, the API shape already supports moving to a StaffUser.teamsSchedulerUrl default with per-client override; the decision to keep it per-client is a product call (confirmed 2026-04-22).

Q. Does this work with Calendly / Cal.com / SavvyCal? Functionally yes — the field accepts any https:// URL. The validation is brand-agnostic. UI labels still say "MS Teams meeting scheduler" and the Support popover says "Teams booking page", so mixed-provider tenants will see misleading copy. Prefer Bookings/Bookwithme for consistency.

Q. A client reported "We're asked to sign in when we click the link." Two common causes:

  • You used the Bookings with me URL but stripped the ?anonymous query param. Re-copy from Outlook.
  • Your Microsoft 365 admin disabled anonymous access tenant-wide. Ask IT to re-enable it for the booking mailbox, or switch to the shared Microsoft Bookings variant which is anonymous by default.

Q. Can the client choose a meeting type, or is it fixed? They see whatever the booking page surfaces. For Bookings-with-me, if you exposed multiple meeting types, they all show up. For shared Bookings, either use the generic page URL or link to a specific service with /s/<serviceId>.

Q. What audit trail is captured when a client books? None on our side — bookings happen inside Microsoft 365 and land on the PE's Outlook calendar. The Spade portal only records the click-through as a browser navigation to an external URL; we don't proxy the request. If you need booking analytics, use Microsoft's Bookings reports.

Q. Does setting / clearing the URL kick off any cycle state change? No. It's pure configuration. In-flight cycles pick up the new URL the next time the portal page loads (the URL is read from the cycle's GET response via cycle.teamsSchedulerUrl, which hydrates from Client.teamsSchedulerUrl server-side).


6. Troubleshooting

SymptomCauseFix
Portal still shows Ask payroll executive to contact me instead of Schedule a meetingURL is blank on the client, or set on the wrong clientConfirm on /dashboard/clients/{id} — the Profile card is the source of truth
Support popover shows "Schedule a meeting" but clicking it does nothingPopup blocker — target="_blank" plus rel="noopener noreferrer" is set, but some browsers still require a user gestureCheck the browser console; most resolve after the first manual approval
Save button rejects with "teamsSchedulerUrl must start with https://"The URL you pasted is missing the scheme or uses http://Re-copy from Outlook — never hand-type
URL saves but is not visible next page loadBrowser cached the old GET /admin/clients/:id responseHard reload (⌘⇧R) — the ETag check should refresh; if not, restart the dev API to rule out stale Prisma client
Client approver says the booking page shows "This page cannot be displayed"The PE's Bookings subscription lapsed, or the booking page was deleted in OutlookRe-create the booking page, grab the fresh URL, update the client record

  • Schema: packages/db/prisma/schema.prismaClient.teamsSchedulerUrl (column teams_scheduler_url).
  • Migration: packages/db/prisma/migrations/20260422120000_add_teams_scheduler_url_to_client/.
  • API:
    • PUT /admin/clients/:id — accepts teamsSchedulerUrl in body (also via /admin/clients/:id/config).
    • GET /admin/clients/:id — returns the URL in ClientDetailResponse.
    • GET /portal/cycles/:token — surfaces the URL to the portal via CycleSummary.teamsSchedulerUrl.
  • Dashboard UI: apps/web/src/app/dashboard/clients/[id]/page.tsx (Profile card, TeamsSchedulerReadout component).
  • Portal UI:
    • apps/web/src/features/intake/portal-support-button.tsx (persistent header Support button).
    • apps/web/src/features/intake/pe-contact-card.tsx (bottom-of-intake card).
  • Contracts: packages/contracts/src/schemas/client.ts and packages/contracts/src/schemas/cycle.ts.

See the manual testing guide (docs/MANUAL_TESTING_GUIDE.md, section 2b.1) for a full walkthrough in context.

Internal use only — BreezyCorp