# 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`](../../../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`](../../../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`](../../../ActivityLog-Integrations/Azure/Outlook-FunctionApp-Handoff.md) | Historical Outlook-only handoff. Now superseded by the consolidated Function App. |
| [`../../../ActivityLog-Integrations/Common-Architecture.md`](../../../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`](../../../ActivityLog-Integrations/Teams/SPEC.md) | Teams message types, mapping rules, attribute schema |
| [`../../../ActivityLog-Integrations/Outlook/SPEC.md`](../../../ActivityLog-Integrations/Outlook/SPEC.md) | Email + calendar event mapping |
| [`../../../ActivityLog-Integrations/EntraID/SPEC.md`](../../../ActivityLog-Integrations/EntraID/SPEC.md) | Sign-in + directory-audit mapping |
| [`../../../ActivityLog-Integrations/SharePoint/SPEC.md`](../../../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, with whatever names you prefer (e.g. `teams`, `outlook`, `entra`, `sharepoint`).
2. **One token per system** — five-year retention recommended (compliance-shaped events). See [`20-setup-system-tokens.md`](./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`](../../../ActivityLog-Integrations/Azure/Teams-FunctionApp-Handoff.md) for the historical note.

## Workload coverage

### Teams (Workload = `MicrosoftTeams`)

Routed to the `teams` token. Storage mode: **lean metadata** (the O365 audit feed scrubs body content, so we have nothing to ingest as body).

Event types you'll see:

| Type | Meaning |
|---|---|
| `teams.message.created` | A channel or chat message was posted (metadata only) |
| `teams.meeting.created` / `.joined` / `.left` / `.ended` | Meeting lifecycle |
| `teams.call.started` / `.ended` | Call lifecycle (from CallRecords, joined to audit) |
| `teams.user.added` / `.removed` | Team/channel membership changes |

Full catalogue: [`../../../ActivityLog-Integrations/Teams/SPEC.md`](../../../ActivityLog-Integrations/Teams/SPEC.md).

### Outlook (Workload = `Exchange`)

Routed to the `outlook` token. Storage mode: **lean metadata** (body content scrubbed by audit).

Event types:

| Type | Meaning |
|---|---|
| `outlook.email.sent` / `.received` | Send and receive (with subject line in metadata, never body) |
| `outlook.calendar.event.created` / `.updated` / `.cancelled` | Calendar lifecycle |
| `outlook.meeting.accepted` / `.declined` | Meeting RSVP |

Full catalogue: [`../../../ActivityLog-Integrations/Outlook/SPEC.md`](../../../ActivityLog-Integrations/Outlook/SPEC.md).

### Entra ID (Workload = `AzureActiveDirectory`)

Routed to the `entra` token. Storage mode: **lean metadata**. Two downstream streams:

| Stream | Content |
|---|---|
| `signins` | Interactive user sign-ins, success and failure |
| `directory` | Directory-audit events: user added, group changed, role assigned |

Event types:

| Type | Meaning |
|---|---|
| `entra.signin.success` / `.failure` | Sign-in attempts (IP, app, MFA result in metadata) |
| `entra.user.added` / `.deleted` / `.modified` | User lifecycle |
| `entra.role.assigned` / `.removed` | Role changes |
| `entra.group.member.added` / `.removed` | Group membership |

Full catalogue: [`../../../ActivityLog-Integrations/EntraID/SPEC.md`](../../../ActivityLog-Integrations/EntraID/SPEC.md).

### SharePoint + OneDrive (Workload = `SharePoint` or `OneDrive`)

Routed to the `sharepoint` token. Storage mode: **lean metadata** (file names + paths in metadata, never file content).

Event types (whitelist — others are dropped by default):

| Type | Meaning |
|---|---|
| `sharepoint.file.viewed` / `onedrive.file.viewed` | File opened |
| `sharepoint.file.edited` / `onedrive.file.edited` | File edited |
| `sharepoint.file.uploaded` / `onedrive.file.uploaded` | File added |
| `sharepoint.file.downloaded` / `onedrive.file.downloaded` | File retrieved |
| `sharepoint.file.deleted` / `onedrive.file.deleted` | File removed |
| `sharepoint.file.shared` / `onedrive.file.shared` | File shared |

Weak-signal operations (`FolderCreated`, `FileMoved`, `FileCopied`, `PageViewed`, `SearchQueryPerformed`) are blocked by default to keep volume reasonable.

Full catalogue: [`../../../ActivityLog-Integrations/SharePoint/SPEC.md`](../../../ActivityLog-Integrations/SharePoint/SPEC.md).

## 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 internal tenant; ready to provision for customers |
| Outlook | **Ready to implement** — same Function App; one more content-type subscription + one new token |
| Entra ID | **Ready to implement** — same as Outlook |
| SharePoint | **Ready to implement** — same as Outlook |

The Teams workload is the proven path; the other three plug into the same Function App via the "Adding a new O365 content type" playbook in the deploy spec.

## What's next

| Goal | Doc |
|---|---|
| Stand up the Function App | [`../../../ActivityLog-Integrations/Teams/DEPLOY.md`](../../../ActivityLog-Integrations/Teams/DEPLOY.md) |
| Provision the deploy pipeline | [`../../../ActivityLog-Integrations/Azure/Pipeline-Setup.md`](../../../ActivityLog-Integrations/Azure/Pipeline-Setup.md) |
| Understand event schema and idempotency | [`../../../ActivityLog-Integrations/Common-Architecture.md`](../../../ActivityLog-Integrations/Common-Architecture.md) |
| Mint the four tokens | [`20-setup-system-tokens.md`](./20-setup-system-tokens.md) |
| Query the events back | [`60-query-api.md`](./60-query-api.md) |
