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 surface | Before (no URL set) | After (URL set) |
|---|---|---|
| Support button (top-right, every view) | Popover shows only a mailto: to the PE | Popover 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.
- Sign in to Microsoft 365 as the payroll executive:
https://outlook.office.com/bookwithme/me - On first visit, Outlook will prompt you to create your booking page. Accept the defaults — you can refine later.
- 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.
- Click Copy link on the meeting type. The URL looks like:That's the URL you'll paste into BreezyCorp. Keep both
https://outlook.office.com/bookwithme/user/<uuid>@<tenant>/meetingtype/<slug>?anonymous&ismsaljsauthenabled?anonymous(so clients don't need to sign in) and&ismsaljsauthenabledquery 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.
- Go to
https://outlook.office.com/bookingsand create a new booking page. - Add services and staff members. Tick Add online meeting to auto-attach a Teams link.
- Publish the page, then copy the "Share your booking page" URL from settings:
https://outlook.office.com/owa/calendar/<slug>@<tenant>/bookings/ - (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).
- Sign in to the dashboard at
/login. - Navigate to Clients → {client} → Settings → Profile.
- Click Edit (top-right of the Profile card).
- Fill in MS Teams meeting scheduler URL with the link from Step 2.
- Click Save.
Validation rules enforced client-side and on the API:
- Must begin with
https://(both the form toast andPUT /admin/clients/:idreject anything else with"teamsSchedulerUrl must start with https://"). - Empty string is normalized to
nullon 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.
- 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.
- Portal side: generate a test cycle (or open an in-flight one) and click the client's magic link in Mailpit.
- 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."
- Click it — the scheduler should open in a new tab, same page as step 1.
- 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
?anonymousquery 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
| Symptom | Cause | Fix |
|---|---|---|
| Portal still shows Ask payroll executive to contact me instead of Schedule a meeting | URL is blank on the client, or set on the wrong client | Confirm on /dashboard/clients/{id} — the Profile card is the source of truth |
| Support popover shows "Schedule a meeting" but clicking it does nothing | Popup blocker — target="_blank" plus rel="noopener noreferrer" is set, but some browsers still require a user gesture | Check 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 load | Browser cached the old GET /admin/clients/:id response | Hard 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 Outlook | Re-create the booking page, grab the fresh URL, update the client record |
7. Related files & endpoints
- Schema:
packages/db/prisma/schema.prisma→Client.teamsSchedulerUrl(columnteams_scheduler_url). - Migration:
packages/db/prisma/migrations/20260422120000_add_teams_scheduler_url_to_client/. - API:
PUT /admin/clients/:id— acceptsteamsSchedulerUrlin body (also via/admin/clients/:id/config).GET /admin/clients/:id— returns the URL inClientDetailResponse.GET /portal/cycles/:token— surfaces the URL to the portal viaCycleSummary.teamsSchedulerUrl.
- Dashboard UI:
apps/web/src/app/dashboard/clients/[id]/page.tsx(Profile card,TeamsSchedulerReadoutcomponent). - 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.tsandpackages/contracts/src/schemas/cycle.ts.
See the manual testing guide (docs/MANUAL_TESTING_GUIDE.md, section 2b.1) for a full walkthrough in context.