Why Message Formatting Matters for Interoperability
A basic bidirectional message bridge that passes plain text between Slack and Teams is technically achievable in a weekend project. The real engineering challenge is rich formatting: bold text, code blocks, bullet lists, file attachments, @mentions, hyperlinks, and interactive elements.
Each enterprise messaging platform has a different JSON schema for representing formatted content. Slack uses Block Kit. Microsoft Teams uses Adaptive Cards. Google Chat uses Cards V2. Zoom Team Chat uses its own markdown-adjacent format.
When a Slack message with a code block, a bullet list, and an @mention to a user arrives at the bridge and needs to be delivered to a Teams channel, the bridge must translate from Block Kit JSON to Adaptive Card JSON while preserving the semantic meaning of each formatting element. This translation is where low-quality integration tools fail — they either drop all formatting (producing unreadable plain text) or fail silently on edge cases.
Slack Block Kit Architecture
Block Kit is Slack's structured message layout system, introduced in 2019. A Block Kit message is a JSON array of "blocks," each of which represents a layout component.
Core block types
Section block — the primary text content block:
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "This is *bold* text with a `code span`"
}
}
Divider block — a horizontal rule:
{ "type": "divider" }
Context block — smaller, secondary text (author attribution, timestamps):
{
"type": "context",
"elements": [
{ "type": "mrkdwn", "text": "Posted by *Jordan Hayes* at 2:30 PM" }
]
}
File block — represents a shared file:
{
"type": "file",
"external_id": "ABCD1234",
"source": "remote"
}
Slack mrkdwn vs standard markdown
Slack uses a proprietary markdown variant called mrkdwn that differs from CommonMark in important ways:
| Format | Slack mrkdwn | CommonMark |
|---|---|---|
| Bold | *bold* | **bold** |
| Italic | _italic_ | *italic* or _italic_ |
| Code span | code | code (same) |
| Strikethrough | ~strikethrough~ | ~~strikethrough~~ |
| @mention | <@U01234567> | N/A |
| Channel link | `<#C01234567 | channel-name>` |
| Hyperlink | `< | link text>` |
The @mention and hyperlink formats are particularly important for cross-platform translation — they contain user IDs and channel IDs that must be resolved to the destination platform's equivalent.
Microsoft Adaptive Cards Architecture
Adaptive Cards is Microsoft's cross-platform card specification, used in Teams, Outlook, and other Microsoft 365 products. Like Block Kit, it represents formatted content as a JSON structure — but with a different schema and different design philosophy.
Core element types
TextBlock — the primary text element:
{
"type": "TextBlock",
"text": "This is **bold** text in Adaptive Cards markdown",
"wrap": true
}
Adaptive Cards TextBlock uses CommonMark-compatible markdown (double asterisks for bold), not Slack's mrkdwn format.
ColumnSet and Column — for multi-column layouts (used for sender attribution):
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"width": "auto",
"items": [
{ "type": "TextBlock", "text": "Jordan Hayes", "weight": "bolder" }
]
},
{
"type": "Column",
"width": "stretch",
"items": [
{ "type": "TextBlock", "text": "via Slack", "color": "accent", "size": "small" }
]
}
]
}
Image — for inline images and thumbnails:
{
"type": "Image",
"url": "https://example.com/image.png",
"size": "medium"
}
The Translation Matrix
Every formatting element that exists in Slack must have a corresponding representation in Adaptive Cards, and vice versa. The translation matrix for a production-grade bridge:
| Slack (mrkdwn) | Adaptive Cards equivalent |
|---|---|
*bold* | **bold** |
_italic_ | _italic_ (same) |
~strike~ | ~~strike~~ |
code | code |
| Fenced code block | Wrap in <pre> via HTML passthrough (limited support) |
<@U01234567> | <at>Display Name</at> with mentions array |
| Bullet list | Separate TextBlock per item, prefixed with "• " |
| Numbered list | Separate TextBlock per item, prefixed with ordinal |
| Blockquote | TextBlock with left-border styling via Container |
The @mention translation problem
@mention translation is the most complex element to handle correctly. In Slack, a mention is encoded as <@U01234567>, where U01234567 is the Slack user ID. To translate this to a Teams @mention:
- Resolve the Slack user ID to an email address via the Slack Users API
- Resolve the email address to a Teams AAD object ID via the Microsoft Graph Users API
- Construct the Teams mention in the Adaptive Card:
{
"body": [
{
"type": "TextBlock",
"text": "Hello <at>Jordan Hayes</at>, the build is complete."
}
],
"msteams": {
"entities": [
{
"type": "mention",
"text": "<at>Jordan Hayes</at>",
"mentioned": {
"id": "29:aad-object-id-here",
"name": "Jordan Hayes"
}
}
]
}
}
The mention entity must appear both in the text content (as <at>Name</at>) and in the msteams.entities array. If either is missing, Teams will not render the mention with a notification — it will appear as plain text.
What Gets Lost in Translation
Some formatting elements have no equivalent on the other platform:
- Slack's Action blocks (interactive buttons) — Teams can render Action Sets, but the interaction model is different. Action blocks in bridged messages are typically rendered as plain text links.
- Teams' Adaptive Card Facts — key/value pairs with no Block Kit equivalent; best translated as a bullet list
- Slack's App surfaces (Home tab, modals) — these are not message content and are not bridged
- Custom emoji — platform-specific emoji (
:slack-emoji:) have no cross-platform equivalent; substitute with nearest Unicode emoji or strip
The Rendering Fidelity Tradeoff
A production messaging bridge must make an explicit architectural decision about rendering fidelity: how much formatting complexity to preserve vs. how much to simplify for reliability.
SyncRivo's translation layer makes the following tradeoffs:
- Plain text, bold, italic, code spans, and hyperlinks: full fidelity
- @mentions: full fidelity (with identity resolution)
- File attachments: full fidelity (with file transfer)
- Complex multi-column layouts: simplified to linear format
- Interactive elements (buttons, selects): stripped to link text
This tradeoff ensures that the bridge never fails on a message it cannot render perfectly — it degrades gracefully to simpler formatting rather than silently dropping the message.
Read the Teams Graph API deep dive → | See Slack ↔ Teams integration →