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:

  1. Four systems, one per workload. Name each so it ends in teams, outlook, entra or sharepoint (for example acme-teams): the report pages find Microsoft 365 sources by that ending, see 90-reports.md.
  2. One token per system — five-year retention recommended (compliance-shaped events). See 20-setup-system-tokens.md.
  3. One Entra app registration in your M365 tenant, with the three Graph permissions + one O365 Mgmt API permission listed below.
  4. 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.md for 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