Route Alerts to Microsoft Teams With Adaptive Cards That People Actually Read
Plain-text Teams alerts get ignored. Here's how to route Prometheus and Azure Monitor alerts into rich adaptive cards with severity, context, and one-click actions.
- #microsoft-teams
- #adaptive-cards
- #alerting
- #chatops
- #prometheus
- #observability
I’ve run on-call rotations where the Teams channel was a wall of grey text — [FIRING] HighErrorRate instance=10.2.0.14 repeated forty times an hour. Nobody read it. The signal drowned in its own noise, and people muted the channel, which is the worst possible outcome for an alerting channel.
The fix isn’t fewer alerts. It’s better-formatted ones. After 25 years of this, I’m convinced that the single highest-leverage change you can make to a noisy alert channel is moving from raw text to adaptive cards. Here’s how I do it.
Why adaptive cards beat plain text
An adaptive card is a JSON-described UI block that Teams renders natively. Instead of a string, you get a structured layout: a colored header, key-value facts, and action buttons. The human eye parses a card in about a second — severity color, service name, and “what do I click” all land at once.
Three things change when you switch:
- Severity becomes visual. A red bar means drop what you’re doing; a yellow one means finish your coffee.
- Context is inline. Runbook link, dashboard link, and the actual metric value live in the card, not three tabs away.
- Actions are one click. Acknowledge, open the runbook, or silence — without leaving Teams.
The card schema, minimally
Adaptive cards are versioned JSON. Here’s a lean alert card that covers 90% of what you need:
{
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.5",
"body": [
{
"type": "Container",
"style": "attention",
"items": [
{
"type": "TextBlock",
"text": "🔥 FIRING: High Error Rate",
"weight": "Bolder",
"size": "Large"
}
]
},
{
"type": "FactSet",
"facts": [
{ "title": "Service", "value": "checkout-api" },
{ "title": "Severity", "value": "critical" },
{ "title": "Value", "value": "8.4% (threshold 2%)" },
{ "title": "Started", "value": "02:14 UTC" }
]
}
],
"actions": [
{
"type": "Action.OpenUrl",
"title": "Open Runbook",
"url": "https://runbooks.internal/checkout-high-error-rate"
},
{
"type": "Action.OpenUrl",
"title": "View Dashboard",
"url": "https://grafana.internal/d/checkout"
}
]
}
The style: "attention" container is what gives you the red header. Use "good" for resolved, "warning" for warnings, "emphasis" for info. That one property does most of the visual work.
Wiring Alertmanager to the card
Prometheus Alertmanager speaks webhooks, not adaptive cards, so you need a small translation layer. I run a tiny receiver — a 60-line service — that accepts the Alertmanager webhook payload and emits a card to a Teams Workflow webhook.
The translation logic is mostly a severity → containerStyle map and a template fill. Pseudocode:
const styleFor = { critical: "attention", warning: "warning", info: "emphasis" };
function toCard(alert) {
const labels = alert.labels;
return {
type: "AdaptiveCard",
version: "1.5",
body: [
headerContainer(styleFor[labels.severity] || "default", labels.alertname),
factSet(labels, alert.annotations)
],
actions: linksFrom(alert.annotations)
};
}
Map your runbook and dashboard URLs from Alertmanager annotations (annotations.runbook_url, annotations.dashboard_url) so each alert carries its own context. That convention — runbook URL as a required annotation — pays off forever.
Azure Monitor is even easier: its action groups can call a Logic App or Power Automate flow that posts a card directly, no custom receiver needed.
Grouping so you send cards, not card-storms
The biggest mistake teams make is one card per firing alert. During a real incident that’s 200 cards. Use Alertmanager’s group_by to collapse related alerts, and render a summary card with a count and an expandable list rather than a flood:
route:
group_by: ['alertname', 'service']
group_wait: 30s
group_interval: 5m
In the card, show “12 instances of HighErrorRate on checkout-api” as a single line. The grouping happens upstream in Alertmanager; the card just reflects it.
Let AI draft the card layouts
Hand-writing adaptive card JSON is fiddly — the schema is picky about element order and version support. I lean on AI for the first draft: describe the alert fields you have and the actions you want, and ask for valid Adaptive Card 1.5 JSON. It gets you 90% there, and you fix the last 10% against the real renderer.
A prompt I keep around: “Generate an Adaptive Card 1.5 JSON for a Prometheus alert with these labels and annotations, red header for critical, a FactSet of the key labels, and OpenUrl actions for runbook and dashboard.” There’s a growing set of Microsoft Teams prompts for exactly this kind of scaffolding, and the broader prompt library has more ChatOps patterns.
Test cards before you ship them
Don’t iterate by spamming your channel. Microsoft’s Adaptive Cards Designer (adaptivecards.io/designer) renders cards live and shows schema errors. Paste your JSON, confirm it renders, then wire it up. I’ve saved hours by validating layout there instead of redeploying a receiver to see a typo.
What good looks like
After the switch, my alert channels went from muted-by-everyone to genuinely useful. The pattern that works:
- Color-coded severity so triage starts before anyone reads a word.
- Inline context — runbook and dashboard links on every card.
- Grouped summaries instead of per-instance floods.
- One-click actions so acknowledging doesn’t mean alt-tabbing.
Start with the critical alerts only — the ones that page someone. Get those into clean cards, prove the value, and expand from there. A channel people actually read is worth far more than complete coverage nobody looks at.
Adaptive card layouts and generated JSON are assistive. Validate against the official renderer and your Teams client version before relying on them in production.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.