Design Adaptive Card Incident Alerts With AI Assistance
Hand an LLM your alert payload and a layout spec, and let it draft the Adaptive Card JSON. Here's how I prompt for cards that pass schema validation and render cleanly.
- #microsoft-teams
- #adaptive-cards
- #alerting
- #ai
- #json
I used to hand-write Adaptive Card JSON the way I imagine medieval monks copied manuscripts: slowly, with a lot of squinting, and with a real chance of a typo breaking the whole thing. A misplaced comma in a ColumnSet and the card renders as an error block in front of the whole on-call channel. Adaptive Cards are great for humans and miserable to author by hand.
So I stopped authoring them by hand. An LLM is excellent at turning a layout description plus a sample payload into valid card JSON, and it’s the kind of structured, well-documented format where the model has plenty to draw on. The catch is that “plenty to draw on” includes outdated schema versions and Slack-isms that don’t belong in Teams. You treat the model as a fast junior engineer: it drafts, you validate, and nothing ships to a tenant without a human checking the render.
Give the model the payload and the schema version
The biggest source of bad AI-generated cards is version drift. The model will reach for whatever version it saw most in training, which is often 1.0 or 1.3, and then suggest properties that only exist in 1.5. Pin the version explicitly and give it the real alert payload so it knows what fields exist.
Generate an Adaptive Card (schema version 1.5, for Microsoft Teams).
Input payload below. Show: a colored title using the "severity" field
(critical=Attention, warning=Warning, else Good), a FactSet with
service, environment, and triggered_at, and two actions: "Acknowledge"
(Action.Execute) and "View runbook" (Action.OpenUrl to runbook_url).
Output only valid JSON, no markdown fences, no commentary.
Payload:
{ "severity": "critical", "service": "checkout-api",
"environment": "prod", "triggered_at": "2026-06-16T09:14:00Z",
"runbook_url": "https://runbooks.example.com/checkout-5xx" }
The “output only valid JSON” instruction saves you from the model wrapping everything in explanation. The explicit color mapping saves you from it inventing a Severity property that doesn’t exist.
Here’s the kind of card that prompt produces, which is genuinely close to ship-ready:
{
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.5",
"body": [
{
"type": "TextBlock",
"text": "🔴 checkout-api — critical",
"weight": "Bolder",
"size": "Large",
"color": "Attention"
},
{
"type": "FactSet",
"facts": [
{ "title": "Service", "value": "checkout-api" },
{ "title": "Environment", "value": "prod" },
{ "title": "Triggered", "value": "2026-06-16 09:14 UTC" }
]
}
],
"actions": [
{ "type": "Action.Execute", "verb": "ack", "title": "Acknowledge" },
{ "type": "Action.OpenUrl", "title": "View runbook",
"url": "https://runbooks.example.com/checkout-5xx" }
]
}
Validate before you trust the render
Never assume the JSON is correct because it looks correct. There are two checks worth automating. First, JSON schema validation against the Adaptive Card schema catches structural mistakes. Second, the Teams-specific gotchas: Action.Execute requires a bot to handle it, msteams width: "Full" lives in a specific place, and emoji in TextBlock render fine but emoji in some properties don’t.
import jsonschema, requests
schema = requests.get(
"https://adaptivecards.io/schemas/adaptive-card.json"
).json()
def validate_card(card: dict) -> list[str]:
errors = []
v = jsonschema.Draft7Validator(schema)
for e in sorted(v.iter_errors(card), key=lambda x: x.path):
errors.append(f"{list(e.path)}: {e.message}")
return errors
I run every AI-generated card through this in CI before it lands in the alerting service. The model is fast but not authoritative; the validator is.
Pro Tip: Keep a “golden” rendered screenshot of each card type. When you regenerate a card with AI, compare the render in the Adaptive Cards Designer against the golden image. Layout regressions are invisible in JSON diffs but obvious in the render.
Use templating so AI writes the layout once
You don’t want the model generating a fresh card per alert at runtime — that’s slow, costs tokens, and is non-deterministic. Have the AI design the card template once, with ${...} binding expressions, and bind real data at send time with the Adaptive Cards Templating SDK.
{
"type": "TextBlock",
"text": "${if(severity == 'critical', '🔴', '🟡')} ${service} — ${severity}",
"color": "${if(severity == 'critical', 'Attention', 'Warning')}"
}
This is where AI assistance shines: templating expression syntax is fiddly and easy to get wrong, and the model knows the if(), formatDateTime(), and $data patterns cold. You ask for the binding, you test it with a sample data object, and you ship a static template that costs nothing to render.
Keep secrets and tenant specifics out of the prompt
When you ask the model to design a card, give it the shape of your data, not real production values. Sanitize hostnames, internal URLs, and especially any token-bearing links. A card’s Action.OpenUrl should never contain a signed URL with an embedded secret when you paste it into a model — and ideally those links use short-lived tokens generated at send time anyway, well after the AI is out of the loop.
The webhook or bot that delivers the card is the real security boundary. AI can help you reason about it, but verify the connector or bot endpoint yourself: confirm the incoming webhook URL is stored as a secret, that Action.Execute is backed by an authenticated bot, and that nobody can replay a card action to trigger a privileged operation.
Where AI fits and where it doesn’t
AI gets you from blank file to validated card draft in a minute instead of an afternoon. It will not catch that your “Acknowledge” button needs to actually deduplicate against your alerting backend, or that your severity color scheme conflicts with your accessibility requirements. Those are judgment calls a human makes.
The rule I keep coming back to: the model is a fast junior engineer. It produces a solid first draft, a human reviews before anything deploys to a tenant, you verify the webhook and bot security yourself, and you never hand the model real tenant credentials. Within those rails, AI-assisted card design is one of the highest-leverage uses of an LLM in a Teams DevOps workflow.
For more on the surrounding pieces, see the Microsoft Teams category, grab reusable starting points from the prompt library and prompt packs, and check the monitoring alerts dashboard for how these cards plug into a real alert pipeline. If you author cards inside your editor, Cursor and GitHub Copilot both handle the JSON-with-templating well.
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.