Skip to content

Microsoft 365 integration (Outlook + Teams)

Fan Datahub platform signals — task completion, workflow approvals, alert rule fires — into your team through Outlook email and Teams Workflows webhooks, using one customer-owned Azure App Registration in your own tenant.

Each Datahub deployment connects to one Azure App Registration in your Microsoft Entra tenant; Datahub never holds tokens for any other tenant.

Auth modelClient credentials · app-only
DestinationsOutlook · Teams Workflows
App RegistrationSingle-tenant · 1 per deployment
Dispatch logdelivered · failed · blocked

When to use it

Use the Microsoft 365 integration when you want platform signals to land in a shared Teams channel or a specific mailbox. Each destination is called a channel in Datahub and is configured per integration — you can run both at once.

  • Broadcast to a Teams channel


    A #data-quality channel that should get a message every time an Alerts rule fires or a workflow is rejected.

  • Route to a mailbox


    A stewardship inbox, a vendor-facing alias, or an alerts distribution list.

  • Both at once


    Configure Teams and Outlook channels side by side; each channel subscribes to the events it cares about.

  • Prove what was sent


    Every dispatch — delivered, failed, or blocked — lands in the integration's Activity tab for audit.

When this integration is not the right fit

  • You need Datahub to read Teams messages, or post as a specific user → out of scope; the integration is send-only and app-only.
  • You want one shared multi-tenant app across customers → not supported; each deployment binds to one customer-owned App Registration.
  • You need Teams replies, threading, mentions, or reactions surfaced back into Datahub → not captured.

How it works

A platform signal reaches the Microsoft 365 dispatch, which checks the capability is enabled and matches the signal to subscribed channels, then delivers to Outlook (Microsoft Graph) or Teams (a Workflows trigger URL). Every attempt is recorded.

flowchart LR
    T["Task events<br/>completed · overdue"] --> DISP
    W["Workflow events<br/>advanced · completed · rejected"] --> DISP
    DISP(["Microsoft 365 dispatch<br/>capability gate + channel match"]) --> MAIL["Outlook email<br/>Graph /users/{from}/sendMail"]
    DISP --> TEAMS["Teams message<br/>Workflows trigger URL"]
    A["Alert rule fires"] -.->|per-rule action| DISP
    DISP -.->|every attempt| LOG[("Activity log<br/>accepted · delivered · failed · blocked")]
    classDef accent fill:#ff8400,stroke:#e67700,color:#ffffff,font-weight:600;
    class DISP accent

Figure 1 — Task and workflow signals fan out via the dispatcher's capability gate and channel match; alerts arrive on their own path — a per-rule action, a channel subscribed to All alerts, or both, reconciled so a channel is never messaged twice for one fire. Every attempt is written to the Activity log.

The two destinations use different transports:

Outlook email Teams message
Transport Microsoft Graph POST /users/{from}/sendMail POST to a Workflows-app (Power Automate) trigger URL
Auth App Registration Mail.Send (Application) via client credentials The trigger URL is the credential — no Graph permission
What you configure Tenant ID + client ID + client secret The trigger URL pasted into a channel row
Consent Admin consent for Mail.Send None — the standard Teams Workflows template handles delivery

Why teams reach for this

One customer-owned App Registration, app-only tokens that never leave the backend, and an Activity log of every attempt — you get Teams and Outlook delivery without standing up a multi-tenant app or handing Datahub any user's OAuth session.

Concepts

Concept What it is
Integration The configured Microsoft 365 connector: credentials, capability toggles, and channels.
Channel One destination — a Teams Workflows webhook or an Outlook mailbox send — with its own event subscription.
Capability A per-type toggle (Outlook / Teams). Turning one off pauses dispatch for that type without deleting channels.
Signal / event A platform occurrence (task completed, workflow advanced, alert rule fired) that a channel can subscribe to.
Activity log The audit trail — one row per dispatch attempt with status, HTTP response, and error.

The single-tenant trust model

Datahub authenticates to your tenant with the client credentials flow — no user consent, no per-user OAuth, no shared multi-tenant app. It talks only to the one App Registration you create, and Graph access tokens live in an in-memory cache, never on disk.

flowchart LR
    DH["Datahub deployment<br/>one App Registration only<br/>token cached in-memory"]
    subgraph TENANT["Your Microsoft Entra tenant · single-tenant"]
        APP["Azure App Registration<br/>client ID · secret · Mail.Send"]
        POL["Application access policy<br/>optional · limits send-as mailboxes"]
        APP --- POL
    end
    DH -->|client_credentials grant| APP
    APP -->|app-only access token| GRAPH["Microsoft Graph<br/>/users/{from}/sendMail"]
    classDef accent fill:#ff8400,stroke:#e67700,color:#ffffff,font-weight:600;
    class APP accent

Figure 2 — The Graph trust path for Outlook. Datahub exchanges the App Registration's client credentials for an app-only token; an optional Exchange application access policy narrows which mailboxes it may send as.

Teams needs no Graph permission

The trust model above is the Graph path used for Outlook. Teams delivery is different — Datahub POSTs to a Workflows-app trigger URL you paste into a channel, so no Microsoft Graph permission or admin consent is involved for Teams.

Setup

1. Create the Azure App Registration

You must do this in your own Microsoft Entra tenant. Datahub never registers an app on your behalf.

  1. Open the Microsoft Entra admin centre → Applications → App registrations → New registration.
  2. Name it something recognisable (for example Datahub notifications).
  3. Supported account types: choose Accounts in this organizational directory only (single tenant) — Datahub only ever talks to your tenant.
  4. Redirect URI: leave blank (Datahub uses client credentials, not user OAuth).
  5. Save.

On the new app's Overview page, copy:

  • Directory (tenant) ID
  • Application (client) ID

2. Grant Microsoft Graph permissions

Still on the new app:

  1. API permissions → Add a permission → Microsoft Graph → Application permissions.
  2. Add the permissions you need:
Capability you want Permission Notes
Outlook email delivery Mail.Send Required for any Outlook channel. By default this lets the app send as any mailbox; you can scope this with an application access policy (recommended — see below).
Teams Workflows webhooks none Teams uses Workflows-app trigger URLs, not Graph. Skip this if you only want Teams.
  1. Click Grant admin consent for {tenant} and confirm. The status column should switch to Granted.

Mail.Send (Application) lets the app send as any mailbox in the tenant. To restrict it, create an application access policy in Exchange Online:

# In Exchange Online PowerShell (Connect-ExchangeOnline)
New-ApplicationAccessPolicy `
  -AppId <client-id> `
  -PolicyScopeGroupId datahub-senders@yourcompany.com `
  -AccessRight RestrictAccess `
  -Description "Datahub may only send mail as members of datahub-senders"

Add the mailboxes Datahub should be able to send from to the datahub-senders mail-enabled security group.

3. Create a client secret

  1. Certificates & secrets → New client secret → Add.
  2. Choose an expiration that fits your rotation policy (12 or 24 months is typical).
  3. Copy the value immediately — Azure shows it only once.

4. Configure the Datahub integration

  1. In Datahub: Admin → Integrations → Microsoft 365. Open the integration.
  2. On the Credentials & capabilities tab paste:
  3. Tenant ID (from step 1)
  4. Application (client) ID (from step 1)
  5. Client secret (from step 3)
  6. Below, toggle the capabilities you have permissions for:
  7. Outlook email — turn on if you granted Mail.Send (Application).
  8. Teams Workflows webhooks — turn on if you want to dispatch to Teams channels.
  9. Click Save, then Test connection at the top of the page. A successful test confirms the credentials work and Datahub can mint Graph tokens.

Pause dispatch without losing config

Flip a capability off at any time to pause dispatch without deleting any channel rows — useful when rotating credentials or temporarily quieting a noisy notification source.

5. Add channels

Switch to the Channels tab and click Add channel. Pick the destination type:

  1. In Teams, go to the target channel. Click the ··· (More options) icon next to the channel name.
  2. Select Workflows from the context menu.
  3. Search for and select the "Send webhook alerts to a channel" template.
  4. In the wizard, select the target team and channel, then click Save.
  5. After the workflow is saved, click Copy next to the generated trigger URL (it looks like https://{env}.{region}.environment.api.powerplatform.com:443/powerautomate/automations/direct/workflows/…).
  6. In Datahub: pick Teams Workflows webhook, paste the trigger URL, give the channel a recognisable name, and pick which events it should subscribe to.

The trigger URL is the credential

Treat it like a password. Anyone holding it can POST payloads that fire your workflow. Regenerate it in Power Automate if you suspect leakage, then update the URL in Datahub.

  1. Pick Outlook.
  2. From mailbox = the user principal name (UPN, e.g. alerts@contoso.com) or object ID of the mailbox Datahub should send as. The App Registration's policy must permit sending as this mailbox.
  3. Recipients = one or more email addresses (press Enter or comma to add).
  4. Pick which events the channel should subscribe to.

Leaving Recipients empty is allowed — and is the normal setup for delivering to a Teams channel

A channel with no default recipients cannot send on its own: every alert or flow action that uses it must supply its own address. That is exactly what you want when one Outlook channel serves several destinations — including a Teams channel's own email address (see Deliver a file to a Teams channel).

Datahub marks such a channel No default recipients in the channel list, and Send test message asks you for a one-off address rather than failing.

You can subscribe to All task & workflow events or to a specific subset — typical choices:

Event What fires it
Task completed A user marks a Datahub task as done.
Task overdue A task crossed its due date without being completed.
Workflow step advanced An approval workflow moved to its next step (intermediate sign-off).
Workflow completed An approval workflow reached its terminal "approved" state.
Workflow rejected An approval workflow was rejected at any step.

The Alerts group is listed separately, and covers every alert Datahub raises — whichever part of the platform raises it:

Event What fires it
All alerts (any source) Every row below, including alert sources added in future releases.
Alert rule triggered An alert rule in Alerts matched its threshold.
Pipeline run failed A scheduled pipeline run finished in error.
Pipeline run stuck A run has been going longer than its stuck threshold.
Pipeline run missed A scheduled run never started inside its grace window.

All alerts — send me every alert, without wiring every rule

Tick All alerts when one channel should hear about everything that needs attention, without adding an action to each alert rule you write and without configuring a separate pipeline-alerting target. It is the setup for a single ops or on-call channel, and it keeps covering new alert sources as they are added.

The alert group sits outside All task & workflow events on purpose: alerts are the highest-volume signal Datahub produces, and a channel set to "all events" before this option existed should not start receiving them without an admin asking. The two groups are independent — ticking one leaves the other's choice alone, so "all routine traffic and all alerts" is two ticks, not a compromise.

You will not get two messages. If an alert rule also names this exact channel in its own Microsoft 365 action — or if it is the pipeline sweep's configured target — that direct route delivers once, in the message format configured there. The subscription only fills the gap.

Subscribed alerts use Datahub's built-in alert message (rule name, current value, threshold). Per-rule message templates, recipient overrides, and .xlsx attachments belong to a rule's own action — if you need those, wire the rule per-rule as below.

The Governance gaps group carries the gaps Datahub finds in how your data is governed, for example a domain with no steward. Each one also reaches the people who can fix it, in their Datahub notifications; a channel is for the team that wants to watch them too.

Event What fires it
A governance gap opens A gap is found. The message says how many and how bad, who in Datahub was told, and links to the page that fixes it.
A governance gap is fixed or eased A gap is gone (fixed), or still there but under the level that sends a notice (eased; it stays listed on the Administration page).
Daily governance gap digest (08:00, Amsterdam time) Every morning at 08:00 (Amsterdam time) while a gap is open: one table of what is new, fixed, eased and still open since the last digest, each row linking to its page. Nothing on a day with no gap open and nothing changed. If none of the subscribed channels could take a digest, the next one lists those changes again. If some took it, a channel that could not does not get that day's changes again; its failed send is on the dispatch log.

Neither All task & workflow events nor All alerts includes these: tick the ones you want.

Gaps arrive check by check

Datahub switches its gap checks on for channels one at a time. Until a check is on, its gaps stay on the Administration page and these events stay silent, so a new subscription can be quiet for a while. That is not a broken channel: Send test message on the channel row checks the channel itself.

6. (Optional) Wire alert rules

To send a specific alert rule to an M365 channel:

  1. Open the alert rule in Alerts.
  2. Under THEN, add an action of type Microsoft 365.
  3. Pick the integration, then the channel within it. Save.

The alert action runs only when this rule fires, so noise stays controllable. Use a per-rule action when this rule needs its own message, recipients, or attachment; use the channel's All alerts subscription when one channel should simply hear everything. A channel covered both ways still receives one message per fire: the rule's action.

Day-2: testing and audit

  • The Channels tab has a Send test message button on every channel row. It dispatches a test payload or test email straight from your browser through the same code path live notifications use, so a successful test means real signals will land too. On an Outlook channel with no default recipients, Datahub asks you where to send this one test; the address is used once and is not saved on the channel.
  • The Activity tab is the audit trail — last 100 dispatch attempts with delivered / accepted / failed / blocked status, HTTP response code, and any error message. Failures stay in the log so you can investigate Microsoft Graph rate limits, permission gaps, or invalid webhooks without Datahub silently swallowing them.

Accepted is not the same as Delivered — and it means something different per channel

Both transports answer 202 Accepted, and neither confirms delivery. Datahub shows Accepted for both, with queued — not confirmed under it, and the row's tooltip says which of these two you are looking at:

  • Outlook. Microsoft Graph queued your mail. Exchange can still reject it — a wrong address, a transport rule, a channel that does not accept external mail — and that rejection comes back as a bounce in the From mailbox, never as a status in this table. If a recipient says nothing arrived, open the From mailbox and look for it.
  • Teams Workflows webhook. Power Automate accepted the request. Whether the flow then posted the card happens inside your own tenant, and a flow that fails there is not reported back to Datahub. If a card is missing, open the flow's run history in Power Automate.
flowchart LR
    EV["Matched signal<br/>capability on · channel subscribed"] --> TYPE{Teams or Outlook?}
    TYPE -->|Teams| GUARD{Webhook host public?}
    GUARD -->|private / loopback| BLK["blocked"]
    GUARD -->|public| POST["POST Workflows trigger URL"]
    TYPE -->|Outlook| SEND["Graph /sendMail as From mailbox"]
    POST --> OK["accepted by Microsoft"]
    SEND --> OK
    POST -.->|HTTP error| FAIL["failed"]
    SEND -.->|HTTP error| FAIL
    OK --> LOG
    FAIL --> LOG
    BLK --> LOG
    LOG[("Activity tab<br/>last 100 attempts")]
    classDef accent fill:#ff8400,stroke:#e67700,color:#ffffff,font-weight:600;
    class LOG accent

Figure 3 — Every matched signal resolves to one of three logged outcomes. Teams webhooks pass an SSRF guard first: a private, loopback, or link-local host is recorded blocked; a Graph or webhook HTTP error is failed. Success is where the log stops: Microsoft has taken the message, which the Activity tab shows as Accepted when the response was a 202. What happens after that reaches you as a bounce, not as a row here.

Deliver a file to a Teams channel

An alert can attach its full result set as an .xlsx and land it in a Teams channel. It does this by email, not by the Teams webhook — a Workflows trigger URL takes a JSON card and has no file field, so an attachment can never travel that way. Every Teams channel has its own email address, and mail sent to it appears in the channel.

This needs no new Microsoft Graph permission. The Mail.Send you granted in step 2 is enough.

Get the channel's email address

  1. In Teams, hover the target channel and click the ··· (More options) icon.
  2. Choose Get email address.
  3. Click Copy.

While that dialog is open, check Advanced settings. It controls who may send to this address — the default on many tenants is anyone, but a tenant hardened to only members of this team will silently reject mail from your Datahub sending mailbox. If you tighten it, add the From mailbox you configured in step 5 to the team.

Point an alert at it

  1. Add an Outlook channel to your Microsoft 365 integration (step 5). Leaving Recipients empty is fine — the alert supplies the address.
  2. Open the alert rule in Alerts and add a Microsoft 365 action on that Outlook channel.
  3. Paste the Teams channel email address into the action's Recipients field.
  4. Turn on Attach the results as an Excel file and pick how many rows the file should hold.
  5. Use Send test to see the real message with the real file before you activate the rule.

The message preview names the file it will send, with its size and row count, so you can see that an attachment is coming without sending one.

What to expect

Where the file lands As a post in the channel conversation, with the file attached to it. Teams files channel email under an Email Messages folder in the channel's SharePoint library, so it is not stored the way a file you upload to the Files tab is.
How long it takes Not instant, and not something Datahub can see. Graph answers accepted the moment it queues the mail; the message then goes through an Exchange transport hop before Teams shows it. A test that has not appeared a few seconds after Datahub says it sent is normal, not a failure — give it a minute before you go looking.
How many rows Up to 10,000, set per action (100 / 1,000 / 10,000 / all). This is separate from the row cap on a {{result_table}} in the message body, which stays at 100 — a card cannot render more, a spreadsheet can.
If the file is cut short It says so on its last row ("showing N of M rows"), and the message body repeats it in one sentence — so someone who acts on the message without opening the file still knows.
How big it can be A few megabytes. Datahub checks the encoded size before sending and fails the dispatch with a legible error rather than posting a request Graph will reject.
Channels that cannot carry it A Teams Workflows webhook and a Google Chat webhook refuse the attachment outright and say why — both post a card payload with no file field. A Gmail channel carries one, like Outlook. The toggle is disabled on the channels that cannot, with the reason on it, and Datahub never delivers the message with the file quietly missing.
What the audit shows The Activity tab records the filename, byte size and row count of what left. A file is not visible in the message the way an inline table is, so this row is the only place an auditor can see the size of what was sent.

Decide who may receive a spreadsheet

The recipient field accepts any address, so by default a 10,000-row export can be addressed anywhere. Datahub caps the row count and logs the envelope either way.

To restrict where files may go, an administrator sets Admin → Defaults → Alert attachments to Only the domains listed below and lists the permitted recipient domains. An alert that would send a file elsewhere is then refused — when it is saved, and again if it somehow reaches dispatch — with the reason and the permitted domains named.

If you deliver to a Teams channel, add its domain too. A channel address is external: it ends in .teams.ms, not your own domain, so an allowlist that contains only your organisation will refuse exactly the delivery route this page recommends. Matching is exact, so list the address's own domain (for example emea.teams.ms).

The restriction covers the attached file only. The result table inside a message is capped at 100 rows and is not affected.

Limitations

Limitation Why What it means for you
Teams uses Workflows-app trigger URLs, not Graph ChannelMessage.Send. Lightweight onboarding — no admin consent for Teamwork.Migrate.All or RSC. Each Teams destination is one trigger URL; replies, threading, mentions, and reactions are not surfaced back to Datahub.
One App Registration per Datahub deployment. Single-tenant trust boundary. Two environments (e.g. UAT + PROD) means two App Registrations, each with its own Mail.Send policy.
Outlook needs a real, licensed mailbox to send as. Mail.Send (Application) sends as a mailbox. Not a contact, distribution group, or external address. Shared mailboxes work if the App Registration has send-as on them (via the application access policy from step 2).
Teams trigger URLs are bearer secrets. Anyone holding the URL can fire the workflow until it is regenerated. Don't paste it into chat or tickets; regenerate in Power Automate on suspected leakage. Datahub stores webhook URLs in the integration config JSON, and admins with Integrations: Manage can read them — by design, so they can rotate.
No outbound to private-network webhook hosts. SSRF guard. URLs whose host resolves to a private, loopback, or link-local address are recorded blocked (this includes reverse proxies pointed at internal hosts). Use official Power Automate trigger URLs — *.environment.api.powerplatform.com or *.logic.azure.com.
An attached file arrives as mail in the channel, not as an upload to the Files tab. Arriving as mail is exactly what makes it need no extra Graph permission. The file is on the post and downloadable from there. Uploading it into the channel's document library as a first-class file would need Files.ReadWrite.All / Sites.Selected — ask us if you need that specifically.
A Teams Workflows webhook channel cannot carry an attachment. Its trigger URL takes a JSON card; there is no file field. Address an Outlook channel to the Teams channel's own email address. On Google Workspace the equivalent split applies: a Gmail channel carries a file, a Google Chat webhook does not.

Payload contract

Datahub POSTs a v1.2 Adaptive Card JSON (title + body, full-width) to the Teams trigger URL. The "Send webhook alerts to a channel" Workflows template renders it directly — no custom flow expressions or extra flow configuration needed.

An alert message that uses the {{result_table}} placeholder is posted as a v1.5 card instead, because that is the lowest schema version defining the Table element. The same Workflows template renders it; no reconfiguration is needed. The card is kept under the Teams payload size limit — when a table is too large to fit, rows are removed and the card says so under the table. Outlook mail for such a message is sent as HTML rather than plain text; every other alert mail is unchanged.

Audit & compliance

Question a CISO might ask Where to look
"What did Datahub send, and did it arrive?" Integration → Activity tab: last 100 attempts with delivered / failed / blocked, HTTP status, and error message.
"Does Datahub hold any user's credentials?" No — the integration is exclusively app-only. It stores only the App Registration's tenant_id / client_id / client_secret; no user OAuth tokens, refresh tokens, or Entra user identities.
"Where are the secrets stored?" client_id and client_secret as Key Vault references (values never leave the backend); tenant_id in config JSON as a non-secret. Graph access tokens live in an in-memory cache only.
"Can Datahub send as any mailbox?" Only if Mail.Send is left unscoped. Restrict it with an Exchange application access policy (step 2).
"Can an internal host be reached via a pasted webhook?" No — the SSRF guard rejects private / loopback / link-local hosts and records the attempt as blocked.
"Who can read the stored Teams webhook URLs?" Admins with Integrations: Manage — by design, so they can rotate a leaked URL.

Troubleshooting

Symptom Likely cause Fix
Test connection fails with AADSTS7000215 Wrong client secret Generate a new secret in Azure → paste in Datahub → save
Test connection fails with AADSTS50034 Wrong tenant ID Copy the Directory (tenant) ID from the App Registration overview (not from your user account)
Outlook test sends, but production messages fail with Access Denied App access policy excludes the From mailbox Add the mailbox to the policy's PolicyScopeGroupId group, or remove the policy if you intentionally want unrestricted sending
Teams test fails with 404 or 400 Bad Request Workflow trigger URL is stale (flow was deleted or regenerated) or copied incorrectly Re-copy the trigger URL from Power Automate → your flow → trigger details, and update the channel in Datahub
Teams Activity tab shows delivered for the test but no message appears in Teams The Workflows template was created using a custom action instead of the standard "Send webhook alerts to a channel" template, or the flow is turned off In Power Automate, confirm the flow is on and uses the "Send webhook alerts to a channel" template (trigger: "When a Teams webhook request is received" + action: "Post card in a chat or channel"). Datahub sends a fully-formed Adaptive Card — the template renders it without any custom configuration.
Activity tab shows blocked rows Webhook hostname resolves to a private IP Use an official Power Automate trigger URL (*.environment.api.powerplatform.com or *.logic.azure.com) — never a reverse proxy
Notifications stopped arriving but Test passes A capability flag (Outlook / Teams) was switched off Re-enable on the Credentials & capabilities tab
Alert rule fires but no M365 message The alert has no Microsoft 365 action, or the action's channel is disabled Open the rule → THEN → confirm a microsoft_365 action exists with a valid integration + channel

What Datahub stores

Item Storage Notes
tenant_id Integration config JSON (not a secret) Visible to admins via the Integrations API
client_id, client_secret Key Vault Backend stores Key Vault references in the database; secret values never leave the backend
Channels Integration config JSON Includes Teams webhook URLs and Outlook recipients/from-mailbox
Dispatch log integrations.m365_dispatch_log table One row per attempt — delivered/failed/blocked, HTTP status, error message
Microsoft Graph access tokens In-memory cache only Acquired via client_credentials, never persisted to disk

Datahub never stores user-level OAuth tokens, refresh tokens, or any user identity from your Entra tenant — the integration is exclusively app-only.

See also

  • Microsoft 365 Copilot


    The other side of the Microsoft story — bring Datahub context into Copilot.

  • Alerts


    Wire a Microsoft 365 action under THEN to route a specific rule to a channel.

  • Tasks


    Task completed / overdue signals are among the events an M365 channel can subscribe to.

  • Workflows


    Approval step-advanced, completed, and rejected events fan out to Outlook and Teams.