TL;DR. Yes. SlackBridge bridges threaded replies in both directions: a reply in a Slack thread arrives in Microsoft Teams as a reply on the corresponding Teams root message, and a reply on a Teams root message arrives in Slack inside the corresponding thread. Threading is preserved by storing a Slack-timestamp ↔ Teams-message-ID map at bridge time, with a content-fingerprint fallback (sender name + first 50 characters + a 4-factor inference score) for cases where the original message ID was not captured. Threads work even when the bot was installed after the parent message was posted.
What's the short version?
Threaded replies bridge in both directions. A reply on a Slack message arrives in Microsoft Teams as a reply on the corresponding Teams message; a reply on a Teams message arrives in Slack inside the matching thread. The conversation reads in the same order on both sides.
| Scenario | What SlackBridge does |
|---|---|
| Reply on a bridged Slack message | Looks up the Teams message ID stored when the parent was bridged; delivers the reply with that as the root |
| Reply on a bridged Microsoft Teams message | Detects the parent ID from the Microsoft Graph change-notification payload; looks up the matching Slack thread_ts; delivers the Slack reply with that thread context |
| Reply where the parent was never bridged (e.g., bot installed after the original message) | Logs slack.thread.no_parent_mapping and delivers the reply as a new top-level message |
| Reply where the parent's destination message ID is missing (e.g., Bot Framework returned no ID at original send time) | Falls back to a content search of the last 20 messages in the destination channel, then to a 4-factor inference score; if either matches above the threshold, threading still succeeds |
Both Slack and Microsoft Teams use a flat thread model — there is a root message and a flat list of replies. Replies-to-replies do not exist as a separate visual structure on either platform, so the bridge does not have to do anything special to flatten depth.
If you only remember one thing: threads work, including in cases where the original message ID was lost — the bridge has multiple fallback strategies before giving up.
How does Slack-thread-to-Teams-thread bridging work?
When you reply to a message in a Slack channel, Slack delivers the message event with a thread_ts field that points to the parent message's timestamp. The bridge detects this and resolves the corresponding Microsoft Teams message ID:
1. Slack reply event arrives
2. Detect: thread_ts is set AND not equal to ts → this is a thread reply
3. Resolve parent Teams message ID:
a. Look up the message mapping by parent's Slack timestamp
b. If a Teams message ID is on the mapping → use it
c. Otherwise, search the last 20 Teams messages for one matching
sender name + the first 50 characters of the parent text
d. If still no match, score candidates with a 4-factor inference algorithm
(content hash, temporal proximity, sender match, length similarity)
4. Deliver the reply via Microsoft Bot Framework with the Teams message ID
embedded in the conversation ID:
19:{channelId}@thread.tacv2;messageid={rootMessageId}
The conversation ID format above is a Microsoft Bot Framework requirement — passing the message ID as a separate replyToId field is not enough on its own. The bridge handles this format internally; nothing on your end has to know about it.
When the parent's destination ID is missing
The Microsoft Bot Framework occasionally returns a successful 204 No Content response without a Teams message ID. When that happens, SlackBridge stores the Slack-side metadata anyway (sender name + first 200 characters of body), and on the next thread reply uses one of two fallbacks:
Content search. Fetch the last 20 messages from the Teams channel (GET /teams/{teamId}/channels/{channelId}/messages?$top=20&$orderby=createdDateTime desc) and look for a message whose body contains both the original sender's display name and the first 50 characters of the original text. If found, use that message ID and persist it for future use.
Multi-factor inference. When the simple content search fails — which can happen when the bridged message text was reformatted or split — SlackBridge falls back to a scored match using four signals:
| Signal | Weight | What it measures |
|---|---|---|
| Content hash (BLAKE3) | 0.5 | An exact fingerprint of the original senderName + messageText matching a candidate's body |
| Temporal proximity | 0.2 | Linear decay over a 5-minute window between expected and actual createdDateTime |
| Sender name match | 0.2 | Exact match, body-contains match, or substring match between the original and candidate sender |
| Length similarity | 0.1 | Ratio of expected to actual body length, with a 20% tolerance |
A candidate is accepted only if its weighted score exceeds 0.7. If no candidate clears that bar, the reply is delivered as a top-level message, and slack.thread.content_search_not_found is logged for diagnosis.
The four-factor inference is also covered by SlackBridge's pending utility patent on multi-factor cross-platform thread resolution.
When the parent message was never bridged
If you reply to an old Slack message that predates SlackBridge being installed in your workspace (or whose bridging was paused at the time), the bridge has no mapping to look up. In that case the reply is still delivered to Microsoft Teams, but as a new top-level message rather than as a reply. The log line slack.thread.no_parent_mapping records this so the cause is visible if you investigate later.
To re-thread an old conversation, the parent must be bridged first — most easily by editing the parent on the source side (Slack edits trigger fresh bridging) or by sending a new message that becomes the root of a fresh thread on both sides.
How does Microsoft-Teams-thread-to-Slack-thread bridging work?
When a Microsoft Teams user posts a reply, the Microsoft Graph change-notification subscription delivers a payload containing a resource path that encodes both the parent message ID and the reply message ID:
/teams/{teamId}/channels/{channelId}/messages('PARENT_ID')/replies('REPLY_ID')
SlackBridge's webhook parses this resource path with a regex to extract the parent ID, then:
1. Match resource path: messages('PARENT')/replies('REPLY')
2. Extract replyToId = PARENT
3. Fetch the reply message from Microsoft Graph
4. Look up the message mapping by Teams parent ID → get the Slack thread_ts
5. Deliver the message to Slack via chat.postMessage with thread_ts set
The Slack chat.postMessage API uses the thread_ts parameter to attach a message to an existing thread. When thread_ts is set to the timestamp of the original Slack root, Slack threads the reply correctly.
If the bridge cannot find a Slack mapping for the Teams parent ID — for example, because the parent was a Teams message that was posted before bridging was active, or the mapping was evicted — the reply is delivered as a new top-level Slack message. This is the symmetrical behavior to the Slack-to-Teams case above.
What about reactions on threaded messages?
A reaction on a thread reply bridges by the same rules as a reaction on any other message. Slack reactions in the supported list arrive in Microsoft Teams as a readable bot message naming the reactor and the emoji; Teams reactions in the supported list arrive in Slack as native reactions on the corresponding Slack thread reply. See the SlackBridge emoji article for the full bidirectional map of 15 reaction types and 43 Slack shortcodes.
Reactions are processed as separate events from the message they react to, so a reaction can arrive after a small delay and still attach correctly.
What does Microsoft Teams do that SlackBridge cannot reproduce on the Slack side?
A few Teams threading behaviors do not have direct Slack equivalents:
| Teams behavior | What happens on the Slack side |
|---|---|
| Pinning a Teams message to the channel | Not bridged — Slack pinning is a separate per-channel action |
| "Reply with quote" (block-quoting the parent inline) | The block-quoted text is bridged as plain text in the Slack reply body; the link back to the original is dropped |
@mention of the original poster inside a reply |
Bridged as readable text, not as a clickable Slack mention. See Do @mentions translate? |
| Marking a reply as a "Reply broadcast" (sends to channel) | Not bridged — the reply still arrives in the Slack thread normally; the broadcast flag is dropped |
| Read receipts | Not bridged. SlackBridge does not request the permissions needed to track who has read which message. |
In each case the bridge errs on the side of preserving the message content rather than firing destination-side actions on a Slack user's behalf.
What does Slack do that SlackBridge cannot reproduce on the Teams side?
| Slack behavior | What happens on the Teams side |
|---|---|
Also send to #channel (the "broadcast" checkbox on a Slack reply) |
The reply text is bridged into the Teams thread; SlackBridge does not separately re-post the reply at the channel root in Teams. |
| Slack call links and huddle links inside a thread | Bridged as plain text URLs. Microsoft Teams does not auto-render Slack call links as call cards. |
| Slack workflow buttons inside a thread | The button label is bridged as text; the button itself is not interactive in Teams (Slack interactivity is bound to Slack's interactive payloads, which Teams cannot dispatch back to). |
| Slack canvases linked from a thread | Bridged as a plain link to the canvas URL. The canvas content is not rendered inline. |
How do I verify threads are working in my own bridge?
Quick sanity check, on a mapping that is already active in both directions:
- Post a message in the bridged Slack channel — call it Message A.
- Wait for it to appear in the bridged Teams channel as Message A'.
- Reply to Message A in the Slack thread.
- The reply should appear inside the corresponding thread in Teams (under Message A'), not as a new top-level message.
- Reply to Message A' inside the Teams thread.
- The reply should appear inside the Slack thread.
If a reply ends up as a top-level message instead of inside the thread, the parent's destination message ID was not captured at bridge time. Posting a fresh root message and replying to that should re-thread cleanly. If you keep seeing top-level deliveries, contact SlackBridge support with the timestamps of the messages involved.
Frequently asked questions
If I reply in a Slack thread, does it appear in the Microsoft Teams thread?
Yes. A reply on a bridged Slack message is delivered to Microsoft Teams as a reply on the corresponding Teams root message. The threading is preserved end-to-end, so the conversation reads in the same order on both sides.
If I reply on a Microsoft Teams root message, does it appear in the Slack thread?
Yes. SlackBridge subscribes to thread reply events on the Teams side and bridges each reply to Slack with the matching thread_ts set, so replies land inside the original Slack thread rather than as new top-level messages.
How does SlackBridge know which Microsoft Teams message corresponds to a Slack message?
When SlackBridge bridges a message, it stores a mapping containing the Slack timestamp, the Microsoft Teams message ID, the sender display name, and the first 200 characters of message text. When a thread reply needs the parent's Teams ID, SlackBridge looks it up by the Slack timestamp first, falls back to a content search of recent Teams messages (sender name plus first 50 characters), and finally to a 4-factor inference score (content hash, temporal proximity, sender match, length similarity) before giving up.
What happens if the original parent message wasn't bridged?
If the parent message in Slack predates SlackBridge being installed (or its bridging was paused at the time), the thread reply is still delivered to Microsoft Teams, but as a new top-level message rather than as a reply. SlackBridge logs slack.thread.no_parent_mapping in this case and continues. To re-thread an old conversation, the parent must be bridged first.
Why does Microsoft Teams sometimes show the bot's reply at the top level instead of inside a thread?
Microsoft's Bot Framework requires a specific conversation ID format to deliver a thread reply: 19:{channelId}@thread.tacv2;messageid={rootMessageId}. If SlackBridge cannot resolve the root message ID at delivery time, it falls back to delivering as a new top-level message rather than failing. This is rare and usually resolves on the next bridged thread once the mapping is filled in.
Are threaded reactions also bridged?
Reactions on threaded messages bridge with the same rules as reactions on top-level messages — see the emoji article for the full reaction-bridging map. Adding a reaction to a reply is treated as a separate event from the message it reacts to.
What is the maximum thread depth that SlackBridge bridges?
Both Slack and Microsoft Teams use a flat thread model: there is a root message and a flat list of replies. Neither platform supports replies to replies as a separate visual structure. SlackBridge follows the same model, so any reply on either platform attaches directly to the root.
Still need help?
If a thread is not bridging the way you expect, contact SlackBridge support with:
- The timestamp of the parent message on the source platform (Slack or Teams)
- The timestamp of the reply that did not bridge correctly
- Which platform the reply was sent on, and which platform you expected it to thread on
- Whether the parent was bridged in real time (i.e., posted after the bot was installed) or whether it predates the install
We aim to respond within one business day, and the message-mapping records associated with both timestamps are queryable internally for incident review (see Security → Audit Logging for how SlackBridge's tamper-evident audit trail is structured).
The Microsoft Graph permissions that make threading work are listed under What permissions does SlackBridge ask for, and why? — specifically ChannelMessage.Read.All on the Teams side and channels:history / groups:history on the Slack side.