Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
11 KiB
PRD — Teams Online Meetings → Interactions, MSP scheduling, recording/transcript capture
- Slug:
teams-online-meetings-interactions - Date:
2026-06-01 - Status: Draft (design converged; see
../../../../.ai/teams-meetings-interactions-consolidated-plan.mdfor the long-form design)
Summary
Make Microsoft Teams online meetings first-class interaction records in Alga PSA, let MSP users schedule meetings themselves (not just as a side effect of a client appointment request), and capture the recording and transcript after a meeting ends — surfaced on the contact/client interactions timeline (and appointment views). Meetings are created as calendar-backed events so their recordings/transcripts are retrievable from Microsoft Graph. Storage: transcript saved as a durable internal document; recording streamed through an internal authenticated proxy (raw Graph content URLs are never exposed), with an optional per-tenant blob-download.
Problem
Today a Teams meeting only exists as a side effect of an end-client appointment request: the client
requests, an MSP user approves, and (if enabled) Graph creates a standalone online meeting whose join
link is stashed on appointment_requests. There is no record on the interactions timeline, MSP
users cannot originate a meeting themselves, and nothing captures the recording or transcript.
Additionally, the current standalone-onlineMeeting creation path cannot reliably surface
recordings/transcripts (Graph requires calendar-backed meetings for those APIs).
Goals
- Online meetings appear as "Online Meeting" interactions on the contact/client timeline with the join link, regardless of edition (CE shows join link only).
- MSP users can schedule a Teams meeting from the interactions feed and from the schedule-entry popup.
- After a meeting ends, recording(s) and transcript(s) are captured and surfaced on the interaction and appointment detail views, via a manual "Refresh recordings" action (Phase 1) and Graph change notifications (Phase 2).
- Appointment-approval meetings and MSP-initiated meetings share one data model and one interaction type.
Non-goals
- Importing externally-organized Teams meetings (per-user OAuth browse/import) — deferred.
- Per-user meeting organizer (everything uses one tenant service account in v1).
- Co-organizer / presenter role assignment for the creating MSP user.
- Providers other than Teams (data model is provider-agnostic but only
teamsis implemented). - "Recording available" notification emails.
- Operational gold-plating (metrics, dashboards, retry frameworks) beyond the explicit capability diagnostics and the bounded recording-fetch retry cap below.
Users and Primary Flows
- MSP user (internal):
- Approves a client appointment request with "Generate Teams meeting" → meeting created, interaction +
online_meetingsrow created, capture registered. - Creates a meeting ad-hoc from the interactions feed (Quick Add → type "Online Meeting" → "Create Teams meeting") or from the schedule-entry popup.
- Views the interaction: Join, list of recording/transcript artifacts (newest first), status, and a "Refresh recordings" button.
- Approves a client appointment request with "Generate Teams meeting" → meeting created, interaction +
- Client user (client portal): sees Join, and recording/transcript links only when the tenant has enabled client-portal visibility (default MSP-only).
- Tenant admin: configures the meeting organizer service account and the
download_recordings/expose_recordings_in_portaltoggles in the Teams integration settings page.
UX / UI Notes
- New system interaction type "Online Meeting", icon
video(provider-agnostic; distinct from the manual "Meeting" type). InteractionDetails: "Online Meeting" section — Join + status ("Recording pending" / "No recording") + "Refresh recordings" + an artifacts list (each transcript → "View transcript" opening the internal document; each recording → "Download recording" hitting the internal proxy).QuickAddInteraction: when type = Online Meeting, a "Create Teams meeting" toggle (gated on capability) with start/end.EntryPopup: "Generate Teams meeting" option when creating a schedule entry.- Client-portal appointment views: recording/transcript links next to "Join Teams Meeting", gated on the tenant visibility setting.
- All new interactive elements need stable kebab-case
ids (UI reflection); all new copy viat('...')i18n keys.
Requirements
Functional Requirements
- Create meetings as calendar-backed events (
POST /users/{organizerUpn}/events,isOnlineMeeting: true,onlineMeetingProvider: 'teamsForBusiness'); resolve the onlineMeeting id from the eventjoinUrl. - Persist a
online_meetingsrow + an "Online Meeting" interaction for both creation paths (appointment approval and MSP-initiated), linked byappointment_request_idorschedule_entry_id. - Capture recordings and transcripts as
online_meeting_artifactsrows (collections, not single values). Transcript content stored as an internal document (client+contact association,is_client_visible=falseunless opted in). Recording surfaced via internal proxy; optional blob download. - Manual "Refresh recordings" action (Phase 1) and Graph change-notification subscriptions (Phase 2) both drive the same idempotent fetch handler.
- Keep appointment reschedule/cancel in sync: update/delete the calendar event via
provider_event_idfor new rows; preserve legacyonline_meeting_idhandling for pre-existing standalone meetings; set statuscancelledon decline. - Tenant settings (Teams integration settings page): organizer service account
(
default_meeting_organizer_upn+default_meeting_organizer_object_id),download_recordings,expose_recordings_in_portal. - Capability diagnostics surface missing consent / Exchange-side mailbox scoping instead of failing silently.
Non-functional Requirements
- Multi-tenant / Citus: all new tables distributed on
tenant, PK includestenant, application-level integrity (no cross-shard FKs).online_meeting_artifactscolocated withonline_meetings. - Transaction discipline: all Graph create/update/delete calls run outside DB transactions; if Graph create succeeds but the DB transaction fails, the orphaned calendar event is deleted (compensation).
- Edition:
online_meetings,online_meeting_artifacts, and the interaction type are edition-agnostic; recording/transcript capture, subscriptions, and renewal jobs are EE-gated. - Bounded retry: recording fetch capped via
recording_fetch_attempts, terminalno_recording. - Security: raw Graph content URLs never sent to any client; proxy enforces auth + tenant + (for portal) the visibility setting.
Data / API / Integrations
- New core migration —
online_meetings(PK(tenant, meeting_id);provider,provider_meeting_id,provider_event_id,organizer_upn,organizer_user_id,subject,join_url,start_time,end_time,status∈ {scheduled, ended, recording_pending, recording_ready, no_recording, cancelled, failed},recording_fetch_attempts,last_fetch_at,appointment_request_id,interaction_id,schedule_entry_id,created_by, timestamps) andonline_meeting_artifacts(PK(tenant, artifact_id);meeting_id,artifact_type∈ {recording, transcript},provider_artifact_id,content_url,document_id,file_id,created_date_time, timestamps; unique(tenant, meeting_id, artifact_type, provider_artifact_id)). - New core migration — insert "Online Meeting" into
system_interaction_types(iconvideo). - EE migration — add
default_meeting_organizer_object_id,download_recordings,expose_recordings_in_portal(+ Phase 2 subscription id/expiry) toteams_integrations. - Types —
IOnlineMeeting+IOnlineMeetingArtifact(withartifacts[]); optionalonline_meetingonIInteraction. - Graph (EE) — change
createTeamsMeeting/update/deleteto the events endpoints; newfetchMeetingArtifacts(recordings + transcripts collections, transcript content); reusefetchMicrosoftGraphAppToken/resolveTeamsMeetingExecutionConfig. URL-encode id path segments. - Permissions (document, admin-consent) —
Calendars.ReadWrite(application) scoped via an Exchange Application Access Policy / RBAC to the organizer mailbox, plus protected/meteredOnlineMeetingRecording.Read.All,OnlineMeetingTranscript.Read.Allon top of existingOnlineMeetings.ReadWrite.All+ Teams Application Access Policy.
Security / Permissions
scheduleTeamsMeetingrequires an explicithasPermissioncheck (confirm resource/action in repo catalog) in addition towithAuth.- Recording proxy route: authenticated, tenant-scoped, portal-visibility-gated.
- Transcript documents default
is_client_visible=false. Calendars.ReadWritemust be mailbox-scoped on the Exchange side (Teams Application Access Policy does not scope calendar/mailbox access).
Observability
Out of scope beyond capability diagnostics (consent/scoping warnings) — no metrics/dashboards.
Rollout / Migration
- New tables/type ship in CE & EE; capture features are EE-only and no-op in CE.
- No backfill of appointments approved before launch (they keep
online_meeting_*columns + email/portal links; no timeline interaction). - Phase 1 (manual refresh) ships first; Phase 2 (subscriptions + renewal job) follows.
- New Graph permissions + Exchange scoping documented in Teams setup docs; not auto-granted.
Open Questions
- Exact
hasPermissionresource/action forscheduleTeamsMeeting(confirm against repo catalog). - Whether MSP-initiated meetings should default to including the contact as an attendee or leave it optional in the UI.
Acceptance Criteria (Definition of Done)
- Approving an appointment with a Teams meeting creates an "Online Meeting" interaction +
online_meetingsrow (calendar-backed), with no external attendees, and the join link still appears in email/portal. - An MSP user can create a meeting from the interactions feed and the schedule popup; both produce the
same interaction + row; a schedule entry (when created) has
work_item_id = interaction_id. - After recording a meeting, "Refresh recordings" populates transcript document(s) + recording artifact(s); the interaction lists all artifacts; recording plays via the proxy; no raw Graph URL is exposed.
- Reschedule updates, and cancel/decline cancels, the calendar event via
provider_event_id(legacy rows still handled); all Graph calls run outside DB transactions; orphaned events are cleaned up on DB failure. - Client portal shows recording/transcript only when
expose_recordings_in_portalis on. - CE build shows the interaction with join link only and no recording UI errors.
- Migrations apply/rollback cleanly on plain Postgres and Citus.