MS Teams (Graph API)¶
This page documents a sink in Robusta classic
For new setups, we recommend HolmesGPT instead.
HolmesGPT triages your alerts instead of just forwarding them. Sinks are deterministic: they send every notification, unchanged, to a fixed destination, leaving you to read and prioritize each one yourself.
HolmesGPT instead uses AI to investigate each alert, surface the likely root cause, and escalate only what needs attention — so you get fewer, more actionable notifications. Set this up with Alerts Triage for alerts, or Triggered Workflows for custom events.
Robusta can post notifications to MS Teams channels with the Microsoft Graph API, as an Adaptive Card. Unlike the webhook based MS Teams sink, a single sink can post to any channel of any team the sending user belongs to, and route each notification to a channel based on labels and annotations.
Note
2-way interactivity (CallbackBlock) isn't implemented yet.
How it authenticates¶
Microsoft Graph only allows posting channel messages with delegated permissions (on behalf of a user). The sink signs in as a dedicated service user with the OAuth2 Resource Owner Password Credentials (ROPC) flow, and messages are posted as that user.
In the Entra ID admin center, create an App registration (single tenant).
Under API permissions, add the Microsoft Graph delegated permission
ChannelMessage.Send, and grant admin consent.Under Certificates & secrets, create a client secret (or enable Allow public client flows under Authentication and leave
client_secretempty).Create a service user, for example
robusta-alerts@yourcompany.onmicrosoft.com. ROPC does not support interactive MFA, so exclude this user from MFA / Conditional Access policies that require it. When creating it, uncheck Require this user to change their password when they first sign in. Otherwise the sign-in fails withAADSTS50055(password expired).Give the service user a Microsoft Teams license. Without one, Graph rejects every message with
403 Failed to get license information for the user.Open the Microsoft 365 admin center.
Go to Users > Active users, and select the service user.
Click Manage product licenses, and assign a license that includes Microsoft Teams.
A new license can take a few minutes to take effect.
Add the service user as a member of every team Robusta should post to.
Get the team and channel ids¶
In Teams, click ... next to the channel, then Get link to channel. The link looks like:
https://teams.microsoft.com/l/channel/19%3Aabc123%40thread.tacv2/Alerts?groupId=<TEAM_ID>&tenantId=<TENANT_ID>
groupIdis the team id.The part after
/channel/is the channel id. It can be used as is (URL-encoded) or decoded (19:abc123@thread.tacv2).
Configuring the sink¶
Add this to your generated_values.yaml
sinksConfig:
- ms_teams_graph_sink:
name: teams_graph_sink
tenant_id: <TENANT_ID>
client_id: <APP_CLIENT_ID>
client_secret: "{{ env.TEAMS_CLIENT_SECRET }}"
username: robusta-alerts@yourcompany.onmicrosoft.com
password: "{{ env.TEAMS_PASSWORD }}"
# where notifications go when no channel routing applies (see Dynamic Channel Routing)
team_id: <TEAM_ID>
channel_id: "19:abc123@thread.tacv2"
runner:
additional_env_vars:
- name: TEAMS_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: teams-graph-credentials
key: client_secret
- name: TEAMS_PASSWORD
valueFrom:
secretKeyRef:
name: teams-graph-credentials
key: password
Then do a Helm Upgrade.
Configuration parameters¶
Field |
Default |
Description |
|---|---|---|
|
(required) |
Entra ID tenant id. |
|
(required) |
App registration (client) id. |
|
(none) |
App registration client secret. Leave empty for a public client app. |
|
(required) |
The service user Robusta signs in as and posts as. |
|
(required) |
Team of the default channel. Also the team of override values that are only a channel id. |
|
(required) |
Default channel, in |
|
(none) |
Template resolved against the finding subject labels/annotations to route to a different channel. |
|
(none) |
Template resolved against the finding namespace's labels/annotations to route to a different channel. |
|
|
When an override is configured but doesn't resolve, send to the default channel ( |
|
|
Include files: text files (e.g. logs) are shown in the card, images are attached to the message. |
|
|
When |
|
|
OAuth scope requested for the token. |
|
|
Change for national clouds, e.g. |
Dynamic Channel Routing¶
The sink routes each notification with two optional override fields, evaluated in order:
namespace_channel_override— resolved against the Namespace object's labels and annotations, looked up by the finding's namespace. Skipped for findings without a namespace (e.g. node alerts).channel_override— resolved against the finding's subject labels and annotations (for Prometheus alerts, the alert labels and annotations). Same behavior as Slack'schannel_override.
If neither produces a channel, send_to_default_if_missing decides whether the notification goes to
the default channel_id or is dropped.
The resolved value can be:
a channel id (
19:abc123@thread.tacv2, or URL-encoded), posted inteam_id, or<team_id>/<channel_id>, to post in another team.
Both override fields use the same template syntax as Slack:
cluster_name— the Robusta cluster name.labels.foo/$labels.foo— value of a label.annotations.bar/$annotations.bar— value of an annotation.${labels.foo-bar}/${annotations.example.com/teams-channel}— bracket form, required when the key contains characters other than letters, digits, or underscores (e.g.-,/,.).Composite patterns are allowed:
"19:$labels.team@thread.tacv2".
Note
Kubernetes label values can't contain : or @, so a channel id can't be stored in a Kubernetes label.
Use an annotation for Kubernetes objects. Prometheus alert labels can hold channel ids.
Example — route by a namespace annotation, then by an alert label, drop if neither is set:
sinksConfig:
- ms_teams_graph_sink:
name: teams_graph_sink
# ... credentials, team_id and channel_id as above
namespace_channel_override: "${annotations.example.com/teams-channel}"
channel_override: "${labels.teams_channel}"
send_to_default_if_missing: false
apiVersion: v1
kind: Namespace
metadata:
name: payments
annotations:
example.com/teams-channel: "19:abc123@thread.tacv2"
Troubleshooting¶
Errors are logged by the Robusta runner.
Error |
Fix |
|---|---|
|
The service user still has to change its first password. Sign in once as the user and set a new password, or reset it with Require this user to change their password when they first sign in unchecked. |
|
Exclude the service user from security defaults, Conditional Access policies and per-user MFA that require MFA. The user's Sign-in logs in Entra ID show which policy required it. |
|
Wrong |
|
Set |
|
Grant admin consent for the |
|
Assign the service user a license that includes Microsoft Teams (step 5 above). |
Any other |
Add the service user as a member of the team. For private or shared channels, also add it to the channel. |
|
Check the team id and the channel id. |