Outlook Calendar Source
What it ingests
Scheduled calendar events from the mailboxes of your organization's members — every meeting that was held, not only the ones that were recorded.
Each event becomes one Event in the ontology (or a Meeting when it is an online meeting). Attendees resolve to Person entities by email, so they canonicalize against people already ingested from Slack, GitHub, or Teams rather than minting duplicates. Recurring series land as one series entity with instances PART_OF it.
This is the first producer of the Event entity type. Meet and Teams only see meetings that were recorded; calendar sees the recurring 1:1s and standups nobody records — which is where reporting lines and team boundaries live.
Cancelled events are skipped. Attendee-response changes (accepted / declined) do not mint a second entity.
Mail, 1:1 chats, and meeting transcripts are out of scope. Transcripts stay on the Microsoft Teams source.
Credential
References the same microsoft_graph credential as the Microsoft Teams and SharePoint / OneDrive sources — a JSON object describing an Entra ID app registration:
{
"tenant_id": "72f988bf-86f1-41af-91ab-2d7cd011db47",
"client_id": "a1b2c3d4-e5f6-4789-8abc-def012345678",
"client_secret": "abc8Q~…"
}Already running Teams or SharePoint? Reuse that credential.
Point this source at the same vault row. A tenant that has connected either does not need a second app registration — only one additional consented permission on the one it already has (Calendars.Read). That is why the credential type was named for Graph rather than for Teams.
Config fields
| Key | Required | Notes |
|---|---|---|
cloud | no | global (default) for commercial Microsoft 365 — including GCC, which is served by the commercial endpoints — usgov for GCC-High (L4), or usgovdod for DoD (L5). |
lookback_days | no | How many days of past events to re-fetch each poll. Default 14, maximum 90. |
lookahead_days | no | How many days of upcoming events to fetch. Default 14, maximum 90. |
poll_overlap_minutes | no | Extra minutes added to the lookback. Default 30. |
member_allowlist | no | Comma-separated mailbox UPNs. When set, fan-out is restricted to these members (staged rollout / testing). |
workspace_domains | no | Comma-separated email domains. When set, only members in these domains are polled. |
group_ids | no | Comma-separated Microsoft 365 group IDs. Fan-out becomes the intersection of org members and those groups' members. Requires GroupMember.Read.All. |
Why the whole window is re-fetched
calendarView filters by event start time, not last-modified. An edit to next week's standup would be missed by a cursor that only advanced from the last poll. Each poll therefore re-reads [now − lookback, now + lookahead]. Unchanged events are no-ops: the versioned event id (iCalUId + lastModifiedDateTime) hits the existing sensor_events unique constraint. An edit produces a new sensor event that updates the same graph entity.
Provider-side setup
1. Use (or create) the app registration
If you already configured the Teams or SharePoint source, reuse that app registration and skip to step 2. Otherwise follow the Teams guide's app-registration steps — the registration itself is identical; only the permissions differ.
2. Add application permissions and grant admin consent
In Entra ID → App registrations → your app → API permissions, add these Application (not Delegated) permissions and click Grant admin consent:
| Permission | Why |
|---|---|
Calendars.Read | Read each member's calendar via GET /users/{id}/calendar/calendarView |
User.Read.All | Test connection (lists one user to probe) and address mailboxes by UPN. Teams already requires this. |
GroupMember.Read.All | Only if group_ids is set |
Missing Calendars.Read fails Test connection with a named permission error, not a generic 500.
3. GCC-High / DoD tenants
L4 and L5 are different hosts. Set cloud to usgov for GCC-High and usgovdod for DoD:
cloud | Graph host | Token scope |
|---|---|---|
usgov | graph.microsoft.us | https://graph.microsoft.us/.default |
usgovdod | dod-graph.microsoft.us | https://dod-graph.microsoft.us/.default |
Both authenticate against login.microsoftonline.us. Plain GCC (not GCC-High) is served by the commercial endpoints — use global there.
calendarView is ✅ in GCC-High (L4) and DoD (L5) separately — List calendarView. Unlike Teams transcript discovery, Outlook calendar has no national-cloud override page that contradicts that table. This source therefore runs in both sovereign clouds. It still does not ingest meeting transcripts; those stay on the Microsoft Teams source, where meeting_transcripts is rejected in usgov / usgovdod because Microsoft does not implement getAllTranscripts there.
Wire-up
POST /api/sensors
Every org member, default two-week window:
{
"sensor_type": "outlookcalendar",
"name": "Contoso Calendar",
"config": {},
"credentials": { "graph_credential": "<microsoft_graph credential id>" }
}Staged rollout to two mailboxes, GCC-High:
{
"sensor_type": "outlookcalendar",
"name": "Agency Calendar (pilot)",
"config": {
"cloud": "usgov",
"member_allowlist": "ada@agency.gov, bob@agency.gov",
"lookback_days": "7",
"lookahead_days": "7"
},
"credentials": { "graph_credential": "<microsoft_graph credential id>" }
}Verify
Run Test connection. It probes calendarView on one mailbox (the first allowlisted address, or one user listed from the directory), so a missing Calendars.Read consent fails here rather than silently ingesting nothing.
A mailbox that 404s (no Exchange license, a guest without a mailbox) is skipped and named on the source's status line; the other members keep ingesting. Sources only begin polling once you confirm their bindings.
After the first poll, check that:
sensor_eventsexist for meetings across more than one member- the same meeting attended by two members produced one Event, not two
- attendees resolved to existing Person entities (same email as Slack / GitHub / Teams) rather than duplicates
- editing a meeting's time and re-polling updated that entity instead of creating a second one
- a recurring series has instance entities
PART_OFone series entity
Notes and limits
Coverage is the Corveil org, not the whole Microsoft 365 tenant. Fan-out walks this organization's members (filtered by allowlist, domain, or group). A Microsoft 365 user who is not a Corveil member is not polled.
Fan-out is capped at 200 mailboxes per poll. The cap exists so one large tenant cannot stall every other org's tick. Hitting it is not silent: the poll warns and the source status names how many eligible mailboxes were skipped. Narrow with member_allowlist, workspace_domains, or group_ids.
The same meeting is identified by iCalUId, not Graph's id. Graph's event id is mailbox-local — Alice and Bob have different ids for the same staff meeting. iCalUId is the same on every calendar and is the stable entity key.
Cancelled events are not removed from the ontology. They are skipped on ingest; entities already created from them remain.
Keep the window modest. This source is deliberately low-volume: one entity per event and per series. A 90-day lookback on a large tenant is legal but expensive — start with the defaults and widen only if you need the history.