Public preview Grafana Cloud
Last reviewed: November 9, 2026

Microsoft Teams incident bridge

Note

Grafana Workflows is currently in public preview. Grafana Labs offers limited support, and breaking changes might occur prior to the feature being made generally available.

Open a Microsoft Teams bridge for every critical incident and announce it in your Teams incident channel with a join button.

This Grafana Workflows blueprint is in the Incidents category of the Blueprints library.

Before you begin

To use this blueprint, you need:

Steps

Steps run in order from first to last. A step inside a switch branch runs only when Workflows chooses that branch.

StepIDTypeDescription
Create the bridge meetingcreate-meetingmsteams.meeting.createCreates a Teams meeting for the incident bridge. Set organizerEmail to the Microsoft Entra user who owns the meeting, for example a shared incident-bridge account. Change the duration in endDateTime if your incidents usually run longer.
Announce the bridge in Teamsannounce-bridgemsteams.message.postPosts an Adaptive Card with the join link. Pick the Teams team and channel where responders gather.
Add the bridge link to the incidentrecord-bridgeincident.add-noteRecords the join link on the incident timeline. No changes needed.

For the inputs and outputs of each step type, refer to Step and trigger types reference.

Before you turn on the workflow, customize any steps that need input from you, such as a channel ID or a schedule name. Each step’s description says what to set.

Workflow definition

Save this definition as a YAML file to create the workflow with gcx, the Workflows API, or Terraform, or give it to a coding agent as an example. Replace FOLDER_UID with the UID of the folder for the workflow. Blueprints start disabled, so finish customizing the steps before you set enabled to true. For details, refer to Manage workflows as code.

YAML
apiVersion: workflows.ext.grafana.app/v1alpha1
kind: Definition
metadata:
  name: teams-incident-bridge
  annotations:
    grafana.app/folder: <FOLDER_UID>
spec:
  name: Microsoft Teams incident bridge
  enabled: false
  runOnceFor: 'teams-bridge:${inputs.data.incidentID}'
  startWhen:
    matchingRules:
      - eventNameRegex: 'grafana_irm_app\.incident\.updated'
        filter: "resources.incident.event.status == 'active' && resources.incident.event.severity.matches('(?i)^critical$')"
    schedules: []
  steps:
    - id: create-meeting
      type: msteams.meeting.create
      name: Create the bridge meeting
      description: >-
        Creates a Teams meeting for the incident bridge. Set organizerEmail to the Microsoft Entra
        user who owns the meeting, for example a shared incident-bridge account. Change the
        duration in endDateTime if your incidents usually run longer.
      inputs:
        organizerEmail: ''
        subject: 'Incident bridge: ${resources.incident.event.title}'
        startDateTime: '${now()}'
        endDateTime: "${now() + duration('2h')}"
    - id: announce-bridge
      type: msteams.message.post
      name: Announce the bridge in Teams
      description: Posts an Adaptive Card with the join link. Pick the Teams team and channel where responders gather.
      inputs:
        teamID: ''
        channelID: ''
        messageText: 'Critical incident: ${resources.incident.event.title}'
        adaptiveCard: |
          {
            "type": "AdaptiveCard",
            "version": "1.4",
            "body": [
              {
                "type": "TextBlock",
                "text": "Critical incident declared",
                "size": "Large",
                "weight": "Bolder",
                "color": "Attention"
              },
              {
                "type": "TextBlock",
                "text": ${json_stringify(resources.incident.event.title)},
                "wrap": true
              },
              {
                "type": "FactSet",
                "facts": [
                  {
                    "title": "Incident",
                    "value": "${inputs.data.incidentID}"
                  },
                  {
                    "title": "Severity",
                    "value": ${json_stringify(resources.incident.event.severity)}
                  },
                  {
                    "title": "Status",
                    "value": "${resources.incident.event.status}"
                  }
                ]
              }
            ],
            "actions": [
              {
                "type": "Action.OpenUrl",
                "title": "Join the bridge",
                "url": "${steps['create-meeting'].outputs.joinURL}"
              },
              {
                "type": "Action.OpenUrl",
                "title": "Open incident",
                "url": "${resources.incident.event.overviewURL}"
              }
            ]
          }
    - id: record-bridge
      type: incident.add-note
      name: Add the bridge link to the incident
      description: Records the join link on the incident timeline. No changes needed.
      inputs:
        incidentID: '${inputs.data.incidentID}'
        message: "Teams bridge is open: ${steps['create-meeting'].outputs.joinURL}"

How it works

This section explains when the workflow runs and how its steps work together.

When it runs

This blueprint starts on the grafana_irm_app.incident.updated event.

Its trigger filter starts a run only when this CEL expression is true:

text
resources.incident.event.status == 'active' && resources.incident.event.severity.matches('(?i)^critical$')

It runs at most once for each run-once key. This blueprint’s key is teams-bridge:${inputs.data.incidentID}, so a later event with the same key doesn’t start another run. For details, refer to Deduplicate workflow runs.

How the steps fit together

These notes cover how the steps connect and which Workflows behaviors the blueprint relies on.

  • Runs once per incident when an active incident is Critical, using the same trigger filter plus runOnceFor pattern as Major incident kickoff.
  • msteams.meeting.create books a meeting on the organizer’s calendar from now() to now() + 2h, which shows CEL time arithmetic; timestamps render as RFC 3339 UTC strings.
  • msteams.message.post sends an Adaptive Card.
  • The card is a JSON string.
  • Free text such as the incident title and the admin-defined severity label is inserted with json_stringify(), which quotes and escapes it, so quotes or newlines cannot break the JSON.
  • Values the system generates (the incident ID, the status, and URLs) cannot contain quotes, so they go inside a quoted string directly, for example “${inputs.data.incidentID}”.
  • The bridge link is then recorded on the incident timeline.
  • Customize the organizer email (a Microsoft Entra user the Teams app can create meetings for), the Teams team and channel, the meeting length, and the card text.

Next steps