Skip to content
CloudOps
Newsletter
All guides
AI for Microsoft Teams By James Joyner IV · · 10 min read

Build AI Digests for Noisy Teams Alert Channels

When your Teams alerting channel scrolls faster than anyone can read, an LLM-summarized digest card restores signal. Here's how to build one with Graph and a bot.

  • #microsoft-teams
  • #alerting
  • #graph-api
  • #ai
  • #observability

There’s a specific failure mode every busy on-call team hits: the alerts channel becomes unreadable. Not because any single alert is bad, but because forty of them scroll past in an hour and the human brain can’t parse a firehose. People mute the channel, and a muted alerting channel is worse than no channel at all. I’ve watched a genuine SEV1 sit unacknowledged for twenty minutes because it was buried under flapping warnings.

The fix I’ve landed on is an AI digest. Instead of (or alongside) the raw stream, a bot posts a periodic summary card: what’s firing, what’s new since last time, what’s resolved, and what looks correlated. An LLM is genuinely good at “read these 60 messages and tell me what matters,” which is exactly the read the on-call engineer needs. It’s a fast junior engineer doing triage — and a human still owns the decision about what to act on.

Pull the channel messages with Graph

To summarize a channel, you read its recent messages via Microsoft Graph. The channelMessage resource gives you message text, sender, and timestamp.

GET https://graph.microsoft.com/v1.0/teams/{teamId}/channels/{channelId}/messages?$top=50
Authorization: Bearer {token}

The application permission is ChannelMessage.Read.All, which requires admin consent and reads channel messages across the tenant — scope it to a dedicated service principal used only by this digest bot. You’ll want to filter to the digest window (say, the last hour) client-side, since Graph’s filtering on messages is limited.

from datetime import datetime, timedelta, timezone

cutoff = datetime.now(timezone.utc) - timedelta(hours=1)
recent = [
    m for m in messages
    if datetime.fromisoformat(m["createdDateTime"].replace("Z", "+00:00")) > cutoff
]

Pro Tip: Strip the HTML out of message.body.content before summarizing — Teams stores rich content as HTML, and the tags inflate your token count and confuse the model. A quick BeautifulSoup(... ).get_text() pass is enough.

Prompt for a structured digest, not prose

A summary that reads like a paragraph is almost useless on-call. You want structure the eye can scan. I prompt for explicit buckets and ask the model to identify correlation without inventing root cause.

You are summarizing the last hour of a DevOps alerting channel for the
on-call engineer. From the messages below, produce:

- NEW & FIRING: distinct active alerts (dedupe flapping into one line
  with a count, e.g. "HighLatency checkout — flapped 12x").
- RESOLVED: alerts that fired and recovered this window.
- POSSIBLY RELATED: groups of alerts that share a service, host, or time
  cluster. Say "possibly related", do not assert a root cause.
- NEEDS A HUMAN: anything that looks like it warrants action now.

Use only the messages provided. Do not invent alerts or metrics.

Messages:
{cleaned_messages}

The dedupe instruction is the highest-value part. Flapping alerts are the main source of channel noise, and collapsing “HighLatency fired 12 times” into one line with a count is most of the readability win right there.

Post the digest as a clearly-labeled card

The digest goes back to the channel (or better, a separate alerts-digest channel) as an Adaptive Card. Label it as AI-generated so nobody mistakes the model’s “possibly related” grouping for a confirmed diagnosis.

{
  "type": "AdaptiveCard",
  "version": "1.5",
  "body": [
    { "type": "TextBlock", "text": "📊 Alert digest — last hour (AI-summarized)",
      "weight": "Bolder", "size": "Large" },
    { "type": "TextBlock", "text": "Grouping is a hint, not a diagnosis. Verify before acting.",
      "isSubtle": true, "wrap": true },
    { "type": "TextBlock", "text": "**🔥 New & firing**", "wrap": true },
    { "type": "TextBlock", "text": "${firing}", "wrap": true },
    { "type": "TextBlock", "text": "**✅ Resolved**", "wrap": true },
    { "type": "TextBlock", "text": "${resolved}", "wrap": true }
  ]
}

I deliberately keep the raw stream available too. The digest is a navigation aid, not a replacement — when an engineer needs to dig in, they scroll the real alerts.

Run it on a schedule, proactively

The bot posts the digest on a timer (hourly is a good default; tighter during an active incident) using proactive messaging. You store the channel’s conversation reference once, then post to it from a scheduled job without a user triggering anything. Watch your proactive messaging rate limits — batch the digest into a single post rather than one message per alert.

The Graph credentials, the bot’s app secret, and the conversation reference all live in your service. None of it goes to the model. The model receives cleaned message text and returns a summary; it never authenticates to Teams, never holds a token, and never sees a tenant secret. That boundary is what keeps a summarization feature from becoming a credential-leak feature.

Where the model helps and where it can mislead

An AI digest restores signal to a channel that’s drowned in its own noise, and it does it in a way no static rule could — the model genuinely reads context and groups things sensibly. But it can also group two unrelated alerts because they happened to fire at the same minute, or downplay something that turns out to matter. That’s why every card says “verify before acting” and why the raw stream stays available.

The principle is the same as everywhere else in AI ops tooling: the model is a fast junior engineer doing triage, a human reviews and decides before acting on a tenant, you verify the bot and Graph security yourself, and you never hand the model real tenant credentials. For related work, see the Microsoft Teams category and the monitoring alerts dashboard. The prompt library has summarization prompts to adapt, and Claude handles the larger message batches comfortably.

Free download · 368-page PDF

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.