Microsoft 365 integration (Teams + Outlook + Entra ID + SharePoint)
ActivityLog ingests Microsoft 365 activity through a single Azure Function App named ActivityLog-O365, which pulls from the Office 365 Management Activity API and Microsoft Graph CallRecords and routes events to ActivityLog systems by workload.
| Workload | ActivityLog system | Source |
|---|---|---|
| Teams (channel messages, calls, meetings) | teams (or whatever you name it) |
O365 Mgmt API Audit.General + Graph CallRecords |
| Outlook / Exchange (email, calendar) | outlook |
O365 Mgmt API Audit.Exchange |
| Entra ID (sign-ins, directory audit) | entra |
O365 Mgmt API Audit.AzureActiveDirectory |
| SharePoint + OneDrive (file activity) | sharepoint |
O365 Mgmt API Audit.SharePoint |
One Function App, four token routes. This is the architecture today — earlier drafts of the integration specs had separate Function Apps per workload; that's been consolidated.
Where the canonical deployment docs live
This guide is the user-facing overview. The actual deploy steps — Azure resource provisioning, Entra app reg, Key Vault secrets, app settings — are maintained in the ActivityLog-Integrations repo as operator runbooks. Use these as your source of truth:
| Doc | What it covers |
|---|---|
../../../ActivityLog-Integrations/Teams/DEPLOY.md |
The canonical deploy spec. Hosting layout, app permissions, KV secrets, app settings, deploy pipeline, adding new content types. |
../../../ActivityLog-Integrations/Azure/Teams-FunctionApp-Handoff.md |
Historical provisioning record (Teams-only era). Useful when standing up the stack in a new Azure tenant. |
../../../ActivityLog-Integrations/Azure/Outlook-FunctionApp-Handoff.md |
Historical Outlook-only handoff. Now superseded by the consolidated Function App. |
../../../ActivityLog-Integrations/Common-Architecture.md |
Auth contract, storage modes, retention defaults, idempotency schema across all integrations. |
Workload-specific message catalogues:
| Doc | What it covers |
|---|---|
../../../ActivityLog-Integrations/Teams/SPEC.md |
Teams message types, mapping rules, attribute schema |
../../../ActivityLog-Integrations/Outlook/SPEC.md |
Email + calendar event mapping |
../../../ActivityLog-Integrations/EntraID/SPEC.md |
Sign-in + directory-audit mapping |
../../../ActivityLog-Integrations/SharePoint/SPEC.md |
File-activity event mapping |
The summary below describes what you'll see in the ActivityLog portal once the Function App is running — read it first, then go to the deploy doc for the actual stand-up.
High-level architecture
┌──────────────────────────────────────────────┐
│ Microsoft 365 tenant (your org) │
│ ├── Teams │
│ ├── Exchange / Outlook │
│ ├── Entra ID │
│ └── SharePoint / OneDrive │
└────────────────────┬─────────────────────────┘
│ O365 Mgmt Activity API
│ (`Audit.General`, `Audit.Exchange`,
│ `Audit.AzureActiveDirectory`, `Audit.SharePoint`)
▼
┌──────────────────────────────┐
│ ActivityLog-O365 │
│ (Azure Function App, Y1) │
│ - System-assigned MI │
│ - One Entra app reg │
│ - Three Graph permissions │
│ + one O365 Mgmt perm │
└──────┬────────────┬──────────┘
│ │
│ │ Microsoft Graph
│ │ (CallRecords.Read.All)
│ ▼
│ Teams call detail
▼
Route by `Workload` field
├── MicrosoftTeams ──▶ POST /messages (teams token)
├── Exchange ──▶ POST /messages (outlook token)
├── AzureActiveDirectory ──▶ POST /messages (entra token)
└── SharePoint ──▶ POST /messages (sharepoint token)
│
▼
ActivityLog API
One client credential authenticates against two resources: the O365 Mgmt API (for audit content) and Graph (for call records). The Function App routes each event to the right ActivityLog system based on the audit event's Workload field.
What you need to set up
You provision four things in ActivityLog before the Function App can start ingesting:
- Four systems, one per workload. Name each so it ends in
teams,outlook,entraorsharepoint(for exampleacme-teams): the report pages find Microsoft 365 sources by that ending, see90-reports.md. - One token per system — five-year retention recommended (compliance-shaped events). See
20-setup-system-tokens.md. - One Entra app registration in your M365 tenant, with the three Graph permissions + one O365 Mgmt API permission listed below.
- One Function App (
ActivityLog-O365) wired up per the canonical deploy doc.
The Fyin-managed staging tenant has a pre-built version of this Function App that ingests Fyin's own M365 activity. If you're a customer evaluating, the easiest path is to mirror that setup in your own Azure subscription using the deploy doc as the template.
Required Entra app registration permissions
In your M365 tenant, create an Entra app registration with application permissions (not delegated):
| Permission | Resource | Why |
|---|---|---|
ActivityFeed.Read |
Office 365 Management APIs | Subscribe + pull from all four audit content types |
CallRecords.Read.All |
Microsoft Graph | Teams call detail (per-participant session start/end) — not in the audit feed |
User.Read.All |
Microsoft Graph | Resolve user IDs ↔ UPN/displayName for actor mapping |
Tenant admin consent is required for all three. Once consented, the same client credential works against both resources (the access token is requested per-resource at runtime).
Earlier provisioning runs granted
ChannelMessage.Read.All,Chat.Read.All,OnlineMeetingArtifact.Read.All,Reports.Read.All,Files.Read.All. These are redundant under the Mgmt API model and have been removed. If you're following an older handoff doc that lists them, ignore those entries — see../../../ActivityLog-Integrations/Azure/Teams-FunctionApp-Handoff.mdfor the historical note.
Workload coverage
Every message carries the operation's actor; the M365 connector also sets actor.kind (user, system or app) and, where Microsoft marks the actor as a guest or anonymous, actor.is_external. From actor.is_external the API adds actor.origin (external / internal), which the Compliance report uses for guest file access.
Teams (Workload = MicrosoftTeams)
Routed to the teams system. Storage mode: lean metadata (the audit feed carries no message text).
| Type | Meaning |
|---|---|
teams.audit.<operation> |
One per Teams audit operation, lower-cased (for example teams.audit.messagesent, teams.audit.meetingdetail) |
teams.callrecord.<call type> |
A call or meeting from Graph call records (for example teams.callrecord.groupcall), with start and end |
teams.callrecord.participation |
One row per participant of a call, with their own join / leave times |
teams.meeting.attendance |
Per-person meeting attendance with the time actually spent |
Call and attendance durations feed the Timesheet. Full catalogue: ../../../ActivityLog-Integrations/Teams/SPEC.md.
Outlook (Workload = Exchange)
Routed to the outlook system. Storage mode: lean metadata (never message bodies).
| Type | Meaning |
|---|---|
outlook.audit.send |
Mail sent |
outlook.audit.create / .update |
Calendar items created or changed |
outlook.audit.movetodeleteditems / .softdelete / .harddelete |
Mail deleted |
outlook.audit.mailitemsaccessed |
Mail read (left out of work time on the Timesheet) |
outlook.audit.updateinboxrules |
Inbox rule changed |
Full catalogue: ../../../ActivityLog-Integrations/Outlook/SPEC.md.
Entra ID (Workload = AzureActiveDirectory)
Routed to the entra system. Storage mode: lean metadata.
| Type | Meaning |
|---|---|
entraid.signin.success / .failure / .mfa |
Sign-in attempts (IP, app, MFA method or failure reason in metadata) |
entraid.directory.<operation> |
Directory changes, for example user_created, group_member_added, role_assigned, password_reset, ca_policy_updated |
Full catalogue: ../../../ActivityLog-Integrations/EntraID/SPEC.md.
SharePoint + OneDrive (Workload = SharePoint or OneDrive)
Routed to the sharepoint system. Storage mode: lean metadata (file names and paths, never file content). Types are sharepoint.file.<action> or onedrive.file.<action>:
| Action | Microsoft operation | Meaning |
|---|---|---|
viewed |
FileAccessed |
File opened |
edited |
FileModified |
File edited |
uploaded |
FileUploaded |
File added |
downloaded |
FileDownloaded |
File retrieved |
deleted |
FileDeleted |
File removed |
shared |
SharingSet |
Access granted to a person or group |
share_invited |
SharingInvitationCreated |
Sharing invitation sent |
share_accepted |
SharingInvitationAccepted |
Invitation accepted |
link_created |
AnonymousLinkCreated, SecureLinkCreated, CompanyLinkCreated, AddedToSecureLink |
Sharing link created or widened |
link_used |
AnonymousLinkUsed |
Anonymous link opened |
Sharing activity carries a sharing block: link_type (anonymous, specific, company), shared_with, shared_with_type and permission. The API adds sharing.exposure (anonymous, external or internal) and sharing.shared_with_domain (the recipient's domain, including for guest addresses) when it stores the activity; see 50-ingest-native.md. The Compliance report counts external sharing from these.
Weak-signal operations (FolderCreated, FileMoved, FileCopied, PageViewed, SearchQueryPerformed) are dropped to keep volume reasonable.
Full catalogue: ../../../ActivityLog-Integrations/SharePoint/SPEC.md.
Reports
The Microsoft 365 report shows all four workloads; My week and the Timesheet use them per person; Compliance uses SharePoint and OneDrive sharing.
Operational characteristics
| Concern | Behavior |
|---|---|
| Latency | 30–90 minutes — this is the O365 Mgmt API's publication lag, not anything ActivityLog adds. The Mgmt API publishes audit events in batches on a schedule. |
| Idempotency | Each event has an audit Id from Microsoft. The Function App uses it as sourceEventId — retries collapse safely. |
| Backfill on first run | On first start, the Function App pulls the last 7 days from the Mgmt API. Older history requires a separate REST-API backfill (not built in). |
| Watermark | Persisted in Azure Table Storage (activitylogfunctions storage account, o365_watermark partition). If the Function App restarts, it resumes where it left off. |
| Per-workload disable | App-setting toggle per workload. If you don't have a SharePoint use case, leave it off and save the pull. |
Retention recommendation
All four M365 workloads ship with 1825-day (5-year) retention on their tokens by default. This is the typical security-audit window and matches the Common Architecture default for activity-event class sources. Override per token if your compliance posture differs.
Cost (Azure side)
| Resource | Tier | Estimated monthly cost |
|---|---|---|
| Function App (Linux Consumption / Y1) | Y1 | $0 base, ~$0 from executions (well under the 1M/month free grant) |
| Storage account (shared across ActivityLog Function Apps) | Standard LRS | $1–3 |
| Entra app registration | n/a | $0 |
| Key Vault ops | Standard | <$0.01 |
| Total | ~$1–3 per month |
Application Insights is deliberately not provisioned on the Function App — the operational ceiling vs the value at this workload is poor. App-Insights-less debugging is via the deploy doc's logging guidance.
Status today
| Workload | Status |
|---|---|
| Teams | Live on Fyin's tenant |
| Outlook | Live on Fyin's tenant |
| Entra ID | Live on Fyin's tenant |
| SharePoint + OneDrive (incl. sharing links and guest access) | Live on Fyin's tenant |
All four run in the one Function App; a customer tenant is provisioned with the deploy spec as the template.
What's next
| Goal | Doc |
|---|---|
| Stand up the Function App | ../../../ActivityLog-Integrations/Teams/DEPLOY.md |
| Provision the deploy pipeline | ../../../ActivityLog-Integrations/Azure/Pipeline-Setup.md |
| Understand event schema and idempotency | ../../../ActivityLog-Integrations/Common-Architecture.md |
| Mint the four tokens | 20-setup-system-tokens.md |
| Query the events back | 60-query-api.md |