Mentions and Slack tokens

Markdown that already contains Slack's raw mrkdwn tokens converts to native rich text elements: mentions that actually notify people, channel links that navigate, emoji that render. No configuration or lookup is required, because the tokens carry Slack IDs directly. That makes them the natural fit for agents. Give your model the IDs from the incoming Slack event and let it write the tokens inline.

Supported tokens

TokenBecomesNotes
<@U0123456789>User mention (pings)U… or W… user IDs
<#C0123456789>Channel linkC…, D…, or G… channel IDs
<!here> <!channel> <!everyone>Broadcast mention
<!subteam^S0123456789>User group mention
<!date^1719000000^{date_short}>Localized dateOptional ^url and |fallback parts supported
<https://example.com|label>Labeled linkLabels may contain spaces
:rocket: :party-parrot:Emoji elementAny well-formed name, including custom workspace emoji
<!here> Deploy is live :rocket:

<@U0123456789> owns the rollback.
Track it in <#C0123456789>
or the <https://example.com/runbook|runbook page>.

Escalation goes to <!subteam^S0123456789>.
Freeze starts <!date^1784764800^{date_short}|Jul 23, 2026>.
Block Kit JSON

Emoji shortcodes work because Slack accepts arbitrary emoji names and renders a literal :name: fallback for unknown ones. That is also what makes custom workspace emoji work without a lookup table. Times and ratios like 12:30:45 are not misparsed; a shortcode needs word boundaries on both sides.

Mentions notify real people

These tokens are live. A model-generated <!channel> pings everyone in the channel, <!everyone> the whole workspace, and any <@U…> it invents or copies pings that user. slackmark converts them faithfully; it cannot know which pings you intended. Treat mention tokens as a capability you grant rather than a formatting detail:

  • Allowlist what the model may emit. Put only the user/channel IDs it is authorized to mention into its context, and decide which broadcast tokens (if any) are acceptable. Strip or reject disallowed tokens from the model's output before converting. A code span keeps a token visible without pinging (`<!channel>`).
  • Check before posting. Mentions ping when the message is posted, not when it is converted, so a scan of the model output (or the emitted user/broadcast/usergroup elements in blocks) is a cheap final gate.
  • Test in a private channel. Converted output is safe to inspect anywhere, since the playground and previews never notify. Still, post test messages to a private channel with only your bot and you in it.

Escaped tokens still convert

LLMs and upstream sanitizers often HTML-escape angle brackets, producing &lt;@U0123456789&gt;. These convert exactly like the raw form: the markdown parser decodes character references before slackmark sees the text, so both spellings produce a real mention.

To keep a token literal (for example, docs about mentions), wrap it in a code span. Inline code is never scanned for tokens:

Write `<@U0123456789>` to mention a user.

Notification text

Mentions ping from blocks. The text field returned by convert() is the plain-text notification fallback. Send it along so push notifications and screen readers get readable content. It needs no extra mention handling.

Resolving plain @handles (optional)

By default, a bare @sarah or #deploys stays literal text; slackmark never guesses IDs. If your app has a directory, supply resolvers and matching handles become real mentions. Anything a resolver declines (returns undefined) stays text:

import { createConverter } from "slackmark";

const converter = createConverter({
  mentionResolvers: {
    resolveUser: (handle) => userIdByHandle.get(handle), // "sarah" → "U0123456789"
    resolveChannel: (name) => channelIdByName.get(name), // "deploys" → "C0123456789"
  },
});

Handles may contain dots (@john.doe), and sentence punctuation is not swallowed: ping @sarah. resolves sarah, not sarah.. Email addresses like user@example.com are never treated as mentions.

For agents, prefer raw tokens over resolvers: they are deterministic, need no directory, and survive verbatim in the model's output.