At-Mention On-Call Engineers in Teams Adaptive Cards
A card nobody is pinged about gets ignored. Learn how to render real @-mentions inside Adaptive Cards so the right on-call engineer actually gets notified.
- #microsoft-teams
- #adaptive-cards
- #mentions
- #on-call
- #alerting
For months our incident bot posted beautiful Adaptive Cards into the on-call channel — severity, runbook link, the works — and they got ignored at night. The reason was embarrassingly simple: nobody was actually pinged. A card sitting in a channel with no mention does not light up anyone’s phone. The fix was to render a real @-mention inside the card so the person who needs to wake up actually gets a notification, not just a pretty rectangle they will see in the morning.
Mentions in Adaptive Cards are slightly fiddly because they live in two places at once: a placeholder in the card body, and an entity in the message’s mentions array. Get one without the other and the mention renders as plain text or fails to notify. This guide walks through doing it correctly. I drafted the entity-mapping code with an AI assistant — it is decent at the mechanical mapping — but the model happily hardcoded a user’s AAD object ID in an example, which is exactly the kind of thing you scrub before sharing. Treat AI as a fast junior engineer, review what it generates, and never feed it real tenant directory data.
The two halves of a mention
To mention a user in an Adaptive Card sent through a bot, you need:
- A placeholder in the card text using the
<at>...</at>syntax. - A matching entity in the
msteamsmention list, mapping that placeholder to a real user.
Here is the card text:
{
"type": "TextBlock",
"text": "🚨 SEV1 paged to <at>oncall</at>",
"weight": "Bolder",
"wrap": true
}
The <at>oncall</at> is just a label — oncall is an arbitrary key, not the user’s name. The actual identity comes from the mention entity you attach to the message activity:
"entities": [
{
"type": "mention",
"text": "<at>oncall</at>",
"mentioned": {
"id": "29:1AbCdEf...",
"name": "Priya Nair"
}
}
]
The text in the entity must match the placeholder in the card exactly, including the <at></at> wrapper. The id is the user’s Teams conversation account ID (the 29: form for channel context). When both halves line up, Teams renders “Priya Nair” as a clickable mention and fires the notification.
Resolving who is on call to a Teams ID
The hard part is not the card — it is mapping “whoever is on call right now” to a Teams user ID. Your paging system (PagerDuty, Opsgenie, a rotation table) knows the human; you need their Teams identity. Look it up through Graph by email:
async function teamsIdForEmail(email, graph) {
const user = await graph
.api(`/users/${email}`)
.select("id,displayName")
.get();
return { aadId: user.id, name: user.displayName };
}
The AAD object ID from Graph is what you carry into the bot context. When sending through the Bot Framework, you build the mention entity from the conversation member, whose id is the 29:-prefixed account ID — not the raw AAD ID. Conflating the two is the most common reason a mention “looks right” but never notifies. The Graph AAD ID identifies the human in the directory; the Bot Framework account ID identifies them in this specific conversation.
Pro Tip: You can mention an entire channel or team, not just a person, with mentioned.id set to the channel/team conversation ID and type of "mention". For a SEV1 you may want to ping the whole on-call channel and the specific primary. Two entities, two placeholders, one card — and yes, both placeholders must appear in the card text.
Building the mention list programmatically
In a real incident card you may mention the primary, the secondary, and the incident commander. Build the entities array from a list so you are not hand-writing JSON per page:
function buildMentions(people) {
// people: [{ key: "oncall", id: "29:...", name: "Priya Nair" }, ...]
return people.map((p) => ({
type: "mention",
text: `<at>${p.key}</at>`,
mentioned: { id: p.id, name: p.name },
}));
}
Then assemble the card text with the matching <at>${p.key}</at> placeholders. The invariant to assert before sending: every entity’s text appears in the card, and every <at> placeholder in the card has an entity. Mismatches are silent — Teams just renders the orphan as literal text.
Do not over-page
A mention is a blunt instrument. If your card mentions five people on every alert, people mute the channel and you are back where you started — a channel nobody watches. Reserve mentions for cards that genuinely require human action now, and let informational cards stay quiet. I route severity-based mention logic through the same classification my incident-response dashboard uses, so only the right severity actually pings a human. The “should this page someone” decision is one I let AI suggest from the alert payload but always keep a human-reviewable rule for.
Where AI fits
The mechanical placeholder-to-entity mapping is repetitive, so I let Claude or Gemma generate the builder function and then I review the ID handling — specifically that we use the conversation account ID for the mention, not the raw AAD ID. The structured starters in my prompt packs include a mention-builder skeleton, and I iterate edge cases in the prompt workspace. What I never do is paste a real directory dump into a prompt to “help” the model resolve users — that is tenant data, and it stays out of the model.
Conclusion
A Teams card only wakes someone up if it mentions them, and a mention only works when the <at> placeholder in the body matches an entity in the mention list using the conversation account ID. Resolve on-call humans through Graph, build the entities programmatically, assert the placeholders and entities line up, and reserve pings for cards that truly need action. Let AI handle the mapping boilerplate while you own the identity correctness and keep tenant data out of prompts. More on-call patterns are in the Microsoft Teams category.
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.