$npx -y skills add ccheney/robust-skills --skill teams-message-formattingProactively apply when generating Microsoft Teams message text, Bot Framework activities, Teams SDK bot responses, Microsoft Graph chatMessage bodies, Teams Adaptive Card TextBlock or FactSet text, Workflows webhook messages, legacy connector MessageCard text, mentions, emoji, co
| 1 | # Teams Message Formatting |
| 2 | |
| 3 | Microsoft Teams has several incompatible text systems. Pick the surface first, then write the text for that surface — most rendering bugs come from using one surface's syntax on another. |
| 4 | |
| 5 | ## CRITICAL: Four Markup Systems |
| 6 | |
| 7 | | System | Used In | Bold | Link | Mention | |
| 8 | |--------|---------|------|------|---------| |
| 9 | | Bot text `markdown` | Bot Framework/Teams SDK activity `text` (default) | `**bold**` | `[text](url)` | `<at>Name</at>` plus activity `entities` | |
| 10 | | Bot text `xml` | Bot activity `textFormat: "xml"` and hero/thumbnail card `text` | `<strong>bold</strong>` | `<a href="url">text</a>` | `<at>Name</at>` plus activity `entities` | |
| 11 | | Adaptive Card Markdown | `TextBlock.text`, `Fact.title`, `Fact.value` | `**bold**` | `[text](url)` | `<at>Name</at>` plus root `msteams.entities` | |
| 12 | | Graph `chatMessage` HTML | Microsoft Graph `chatMessage.body.contentType: "html"` | `<strong>bold</strong>` | `<a href="url">text</a>` | `<at id="0">Name</at>` plus `mentions` array | |
| 13 | |
| 14 | Do not mix them. Slack mrkdwn (`*bold*`, `<url|text>`) is wrong everywhere in Teams. Full standard Markdown is also wrong in Adaptive Cards: headings, tables, images, preformatted text, and blockquotes are not supported in `TextBlock`. |
| 15 | |
| 16 | ## Quick Decision Tree |
| 17 | |
| 18 | ``` |
| 19 | What are you formatting? |
| 20 | |-- Bot reply or proactive bot message |
| 21 | | |-- Simple text -> textFormat "markdown" (or SDK default) |
| 22 | | |-- User/tag mention -> <at>...</at> plus activity entities |
| 23 | | `-- Rich UI, inputs, buttons -> Adaptive Card ($teams-adaptive-cards) |
| 24 | |-- Adaptive Card text |
| 25 | | |-- TextBlock / FactSet -> limited Adaptive Card Markdown only |
| 26 | | |-- Heading -> TextBlock size/weight/style, never `#` |
| 27 | | |-- Table -> Table element, never Markdown table |
| 28 | | `-- Code -> CodeBlock element, never backticks |
| 29 | |-- Microsoft Graph chatMessage (delegated send) |
| 30 | | |-- Plain text -> body contentType "text" |
| 31 | | |-- Formatting, mentions, emoji, code -> body contentType "html" |
| 32 | | `-- Adaptive Card -> <attachment id> placeholder + attachments |
| 33 | |-- Service-to-channel webhook |
| 34 | | |-- Anything new -> Workflows webhook + Adaptive Card wrapper |
| 35 | | `-- Legacy webhook.office.com URL -> retired May 2026; migrate to Workflows |
| 36 | `-- Hero/thumbnail card -> XML/HTML subset in `text` only; no Markdown, |
| 37 | no formatting in title/subtitle |
| 38 | ``` |
| 39 | |
| 40 | ## Bot Activity Text |
| 41 | |
| 42 | Set `textFormat` when constructing raw Bot Framework activities (`markdown` is the default): |
| 43 | |
| 44 | ```json |
| 45 | { |
| 46 | "type": "message", |
| 47 | "textFormat": "markdown", |
| 48 | "text": "**Build failed** in [CI](https://example.com/run/123)." |
| 49 | } |
| 50 | ``` |
| 51 | |
| 52 | Bot text Markdown support varies by client, so stay inside the safe subset: |
| 53 | |
| 54 | - Safe on desktop, iOS, and Android: bold, italic, links, preformatted text, blockquotes. |
| 55 | - Desktop only: ordered and unordered lists. |
| 56 | - Not on Android: strikethrough. |
| 57 | - Never supported in text-only bot messages: headings, horizontal rules, tables, image links. Use an Adaptive Card for those. |
| 58 | |
| 59 | Bot messages have an approximate 100 KB limit (text, mentions, reactions); stay under 80 KB or the send fails with `413 MessageSizeTooBig`. |
| 60 | |
| 61 | For mentions, include both the visible `<at>...</at>` text and a matching `entities` item: |
| 62 | |
| 63 | ```json |
| 64 | { |
| 65 | "type": "message", |
| 66 | "text": "Hey <at>Ada Lovelace</at>, the deployment is ready.", |
| 67 | "entities": [ |
| 68 | { |
| 69 | "type": "mention", |
| 70 | "text": "<at>Ada Lovelace</at>", |
| 71 | "mentioned": { |
| 72 | "id": "29:teams-user-id", |
| 73 | "name": "Ada Lovelace" |
| 74 | } |
| 75 | } |
| 76 | ] |
| 77 | } |
| 78 | ``` |
| 79 | |
| 80 | The `entities[].text` value must exactly match a substring of `text` (including any `@` prefix); otherwise Teams ignores the mention. The `mentioned.id` accepts a Teams user ID (`29:...`), a Microsoft Entra Object ID, or a UPN. See [references/MENTIONS.md](references/MENTIONS.md) for tag, team, and channel mentions. |
| 81 | |
| 82 | ## Adaptive Card Text |
| 83 | |
| 84 | Use limited M |