Slack APIでメンションするには、相手の名前ではなくユーザーIDを山かっこで囲んだ<@U0123ABCD>の形をメッセージ本文に入れます。この記事では、個人・ユーザーグループ・@hereなどの書き分け、メールアドレスやハンドル名からIDを引くPythonコード、chat.postMessageとBlock Kitでメンション付き通知を送る手順、通知が届かないときの原因と全体通知の暴発を防ぐエスケープまでを、Slack公式ドキュメントの記述に沿って整理する記事です。後半では、ワークフロービルダーのメンションで済ませるか、APIで組むかの判断基準も示します。
まとめ:Slack APIのメンションはIDの記法と通知の届く条件で決まる
実装で押さえるのは2点だけです。1つ目は記法で、個人は<@ユーザーID>、ユーザーグループは<!subteam^グループID>、チャンネル全体は<!here>などの特殊メンションを使います。@tanakaのような名前の文字列は、ユーザーについては自動でメンションに変換されません。
2つ目は通知の届く条件です。アプリが投稿したユーザーメンションは、相手がそのチャンネルのメンバーのときだけ通知されます。IDは業務システム側のメールアドレスからusers.lookupByEmailで引くのが確実で、グループIDはusergroups.listで起動時に1回取得してキャッシュします。
宛先が固定の定型連絡ならワークフロービルダーで足ります。担当者を業務データから決める通知や、オンコール当番への呼び出しはAPIで組みます。自動通知で<!channel>を使う設計は採用しません。
Slack APIで送るメンションの記法と個人・グループ・全体通知の違い
Slackのメッセージでは、山かっこで囲んだ部分が特別な参照として解釈されます。Formatting message textの解説によると、山かっこの中身が@Uか@Wで始まればユーザーメンション、!subteamならユーザーグループ、!で始まれば特殊メンション、#Cならチャンネルへのリンクとして扱う仕組みです。Web APIとEvents APIの全体像はSlack APIとは?Web API・Events API・Boltの違いと実装手順にまとめており、この記事は送る側のメンションに絞ります。
<@ユーザーID>で個人を呼ぶ記法と名前の文字列で通知されない理由
個人へのメンションは<@U0123ABCD>と書きます。投稿されると、IDはSlackの画面上で相手の表示名に置き換わる仕組みです。公式の解説は、名前は変わりうるがIDは常に同じだという理由で、IDによる記法を勧めています。
古いサンプルにある「@usernameと書いてlink_names=1を付ける」方法は、ユーザーについては使えません。chat.postMessageのリファレンスでは、link_namesの説明が「ユーザーグループを探してリンクする。個々のユーザーのリンクはもうサポートしない」となっています。2019年前後の記事で「メンションが青くならない」と書かれている症状の多くは、この変更が原因です。
見落としやすいのがIDの先頭文字です。書式の解説には@Uと@Wの両方が挙がっており、Enterprise Gridの組織ではWで始まるIDが使われる場合があります。受信したメッセージからメンションを抜き出す正規表現を<@U[A-Z0-9]+>と書くと、Wのユーザーを取りこぼします。
ユーザーグループと特殊メンションを使い分ける記法と通知範囲の比較
個人以外の宛先は、記法ごとに通知が届く範囲が変わります。書式の公式解説の記述を表にまとめます。
| 宛先 | 記法 | 通知が届く範囲 | IDの取り方 |
|---|---|---|---|
| 個人 | <@U0123ABCD> |
チャンネルのメンバーである本人だけ | メール検索または一覧取得 |
| ユーザーグループ | <!subteam^ID> |
グループに属する各ユーザー | usergroups.list |
| @here | <!here> |
チャンネルのアクティブなメンバー | 不要 |
| @channel | <!channel> |
チャンネル全員(非アクティブも含む) | 不要 |
| @everyone | <!everyone> |
#general全員(ゲストを除く全メンバー) | 不要 |
表の個人IDの取得方法は、メール検索がusers.lookupByEmail、一覧取得がusers.listです。グループ記法のIDには、たとえばS0614TZR7を入れて<!subteam^S0614TZR7>と書きます。@everyoneの対象は、ゲストを除くワークスペースの全メンバーです。
公式ページが記法の例として載せているのは<!here>で、@channelと@everyoneも「!で始まる中身は特殊メンションとして扱う」という同じ規則で書きます。公式は特殊メンションを控えめに使い、できるだけ個人か特定のグループを指定するよう求めています。チャンネルへのリンク<#C123ABC456>は参照であって、誰にも通知は飛びません。
メールアドレスや一覧からユーザーIDとグループIDを取得する手順とスコープ
メンションの記法が分かっても、肝心のIDが手元に無ければ書けません。業務システムからの通知では、社員マスタのメールアドレスを起点にIDを引く構成が最も壊れにくく、ここではその順で手段を並べます。トークンへのスコープ追加と再インストールの手順はSlack Bot Tokenの取得と確認で扱っています。
users.lookupByEmailのID取得コードとTier 3の上限
users.lookupByEmailのリファレンスによると、必要なスコープはusers:read.emailで、users:readだけでは足りません。レート制限はTier 3(毎分50回以上)です。指定したメールアドレスが無効か、ユーザーが無効化されているとusers_not_foundが返ります。
次のコードは、slack_sdk 3.46系(PyPIの最新版は2026年10月30日公開の3.46.0)で、担当者のメールアドレスとユーザーグループのハンドル名からIDを引き、メンション付きで投稿する例です。トークン・メールアドレス・チャンネルIDは環境変数から読みます。
# pip install "slack_sdk==3.46.0"
import os
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
client = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
def user_id_by_email(email: str) -> str | None:
"""メールアドレスからユーザーIDを引く(users:read.email が必要)"""
try:
res = client.users_lookupByEmail(email=email)
except SlackApiError as e:
if e.response["error"] == "users_not_found":
return None # 未登録または無効化済みのユーザー
raise
return res["user"]["id"]
def load_usergroups() -> dict[str, str]:
"""ハンドル名→ユーザーグループIDの対応表を作る(usergroups:read が必要)"""
res = client.usergroups_list()
return {g["handle"]: g["id"] for g in res["usergroups"]}
GROUPS = load_usergroups() # Tier 2 のため起動時に1回だけ取得する
uid = user_id_by_email(os.environ["ASSIGNEE_EMAIL"])
gid = GROUPS.get("infra-oncall")
to_user = f"<@{uid}>" if uid else "(担当者がSlack未登録)"
to_group = f"<!subteam^{gid}>" if gid else ""
client.chat_postMessage(
channel=os.environ["SLACK_CHANNEL_ID"],
text=f"{to_user} {to_group} 本番DBのディスク使用率が85%を超えました",
)
担当者が見つからないときに例外で止めず、文面に「未登録」と出して投稿を続けるため、IDを取得できない場合も通知全体の停止を避けられます。退職や異動で無効化されたアカウントはusers_not_foundになるため、通知そのものが消えると障害の見落としにつながります。
users.listのカーソル取得とプロフィールのメンバーIDコピー
メールアドレスが無く表示名しか分からない場合は、users.listのリファレンスどおり全ユーザーを取得して手元で照合します。スコープはusers:read、レート制限はTier 2(毎分20回以上)です。limitを省くと全件を一度に返そうとしてlimit_requiredやHTTP 500になることがあるため、200件以下を指定し、response_metadata.next_cursorを次のcursorに渡してページを送ります。
表示名は重複も変更もありうるので、照合結果はIDで保存し、名前で毎回引き直す設計は避けます。数人分を一度だけ設定するなら、Slackの画面で相手のプロフィールを開き、その他のメニューからメンバーIDをコピーする方法が手早いです。メニューの位置はクライアントの版で変わるため、手順書には画面の名称より「Uで始まる英数字のID」をメモする旨を書いておきます。
usergroups.listでハンドル名からグループIDを引く手順と利用プラン
usergroups.listのリファレンスでは、スコープはusergroups:read、レート制限はTier 2です。応答の各要素にid(例:S0614TZR7)、handle(@の後ろの名前)、name(表示名)が入り、上のコードではhandleをキーにした辞書を作っています。並び順は不定と明記されているので、先頭要素を決め打ちで使ってはいけません。
無効化したグループは既定では返らず、含めたいときはinclude_disabledを付けます。エラーplan_upgrade_requiredの説明には、ユーザーグループはStandard以上のプランでのみ使えると書かれています。旧プラン名の表記なので、現行のどのプランが該当するかは契約中の管理画面で確かめてください。フリープランのワークスペース向けに配る通知は、グループ指定を前提にせず個人メンションで組みます。
chat.postMessageのメンション送信:curlとBlock Kitの例
IDが揃ったら、あとは本文に記法を埋めて送るだけです。ここでは疎通確認のcurlと、見た目を整えるBlock Kitの2通りを示します。毎朝の定時投稿や予約投稿の組み方はSlack Botで自動投稿する方法に譲ります。
curlでtextに<@U…>を入れて送る最小リクエストとokの確認
Botをチャンネルに招待したうえで、chat:writeスコープを持つxoxbで次を実行します。JSONで送るときはContent-typeヘッダを明示します。
# メンション付きの1行通知を送る(チャンネルIDとユーザーIDは自分の環境の値に置き換える)
curl -s -X POST https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
-H "Content-type: application/json; charset=utf-8" \
-d '{"channel":"C0123ABCD","text":"<@U0123ABCD> レビュー依頼が1件届いています"}'
成功すると"ok": trueと投稿のtsが返ります。Botがチャンネルに入っていないとnot_in_channel、チャンネルIDの誤りならchannel_not_foundです。投稿は1チャンネルあたり毎秒1件が目安なので、アラートを1件ずつ個別メンションで連投する作りは避け、宛先をまとめた1通にします。
Block Kitのrich_textで宛先別のメンション要素を組む例
Block Kitでは2つの書き方があります。sectionブロックのmrkdwnテキストに<@U…>をそのまま入れる方法と、rich_textブロックにメンション専用の要素を置く方法です。後者はUser element(user_id)、Usergroup element(usergroup_id)、Broadcast element(rangeにhere・channel・everyoneのいずれか)で構成します。
{
"channel": "C0123ABCD",
"text": "<!subteam^S0614TZR7> <@U0123ABCD> 決済APIの5xxが閾値を超えました",
"blocks": [
{
"type": "rich_text",
"elements": [
{
"type": "rich_text_section",
"elements": [
{ "type": "usergroup", "usergroup_id": "S0614TZR7" },
{ "type": "text", "text": " " },
{ "type": "user", "user_id": "U0123ABCD" },
{ "type": "text", "text": " 決済APIの5xxが閾値を超えました", "style": { "bold": true } }
]
}
]
}
]
}
要素で組む利点は、文字列の連結で山かっこを作らずに済むことです。IDの埋め込みとユーザーが入力した文章を別々の要素に分けられるため、後述するエスケープ漏れの事故も起きにくくなります。
blocks使用時もtextに同じメンションを入れて通知文を保つ理由
上の例でトップレベルのtextにもメンションを書いているのは意図的です。chat.postMessageのリファレンスでは、blocksを使う場合のtextは通知に表示するフォールバック文字列とされ、必須ではないものの含めることが強く推奨されています。スクリーンリーダーも通常はtextを読み上げます。
textを省いたり「通知」とだけ書いたりすると、スマートフォンのプッシュ通知に誰宛ての何の件かが出ません。textには「宛先のメンション+1行の要件」を入れ、ブロック側に詳細と操作ボタンを置く分担にすると、通知だけ見た人も判断できます。
メンションしたのに通知されない・記法が崩れるときの原因の切り分け
「投稿はされたが相手に通知が来ない」「山かっこがそのまま表示される」という相談は、原因がほぼ3つに絞れます。上から順に確かめると早く片付きます。
相手がチャンネル未参加で通知が届かない条件と招待・DM送信の対処
最初に疑うのはチャンネルの参加状況です。書式の公式解説には、アプリが投稿したメッセージのユーザーメンションは、相手がすでにそのチャンネルのメンバーである場合にだけ通知されると書かれています。人が手で入力したメンションのように「招待しますか」と聞かれる流れは、APIの投稿にはありません。
担当者が参加していない通知用チャンネルへ流すなら、送信前にconversations.membersで所属を確かめるか、担当者へのchat.postMessageをchannelにユーザーIDを入れたDMとして別に送ります。ユーザーグループのメンションについては、公式はグループ内の各ユーザーに通知すると記しています。
入力値の<!everyone>で全体通知が暴発する事故と3文字のエスケープ
フォームやチケットの件名など、外部の文字列を本文に差し込む通知では、その中に<!channel>や@everyoneが含まれていると、意図しない全体通知になりえます。公式の書式ページも、ユーザー入力をそのまま投稿する危険を警告しています。
対策は、Slackが制御文字として使う&・<・>の3文字だけをHTMLエンティティに置き換えることです。公式は文字列全体をエンコードしないよう明記しているので、URLエンコードやHTMLエスケープ関数をまるごと通すのは誤りです。
def escape_slack(s: str) -> str:
"""外部入力をSlackの本文に差し込む前に制御文字の3つだけを置き換える"""
return s.replace("&", "&").replace("<", "<").replace(">", ">")
title = escape_slack(ticket["title"]) # 件名に <!channel> が入っていても文字として表示される
client.chat_postMessage(channel=channel_id, text=f"<@{uid}> 新しい問い合わせ:{title}")
置き換える順番は&が先です。後にすると、<を変換して生まれた<の&まで二重に変換されます。Incoming Webhookでも同じ規則が効く点はSlack Incoming Webhookの実装手順で整理しています。
plain_textやverbatimの設定で記法が文字のまま表示される原因
山かっこの記法がそのまま見えるときは、テキストオブジェクトの種類を確かめます。公式の解説では、typeをplain_textにすると書式の処理が止まり、一部のブロックや要素はplain_textしか受け付けません。ボタンのラベルやヘッダーブロックにメンションを入れても、名前には変わりません。
もう1つは自動解析の設定です。ブロック内のmrkdwnはverbatimの既定値がfalseで、生のURLなどを自動でリンクにします。verbatimをtrueにしたり、トップレベルでparseをfullにしたりした設定が残っていると、表示が想定と変わります。メンションを確実に出したいなら、rich_textのuser要素で組むのが最も迷いません。
ワークフロービルダーで済ませるかAPIでメンションを組むかの判断基準
メンション付きの通知は、コードを書かずにワークフロービルダーでも作れます。どちらで作るかは、宛先を誰がいつ決めるかで分かれます。
Webhookトリガーの変数にユーザーIDやメールの型を選ぶ宛先の渡し方
ワークフローのメッセージ送信ステップでは、変数として「このワークフローを使用した人」を挿入すると、その人へのメンションになります。画面操作はSlackワークフローの作り方の変数の章にまとめています。
外部システムから宛先を指定したい場合は、Webhookトリガーのカスタム変数を使います。Slackヘルプのワークフロー作成ページによると、変数は最大20個で、選択できるデータタイプはチャンネルID・ユーザーID・ユーザーのメール・テキストです。ネストしたJSONは使えず、リクエストURLはhttps://hooks.slack.com/triggersが先頭です。担当者を外から渡すなら、型をユーザーIDかユーザーのメールにして、メッセージ本文へその変数を挿入します。
業務システムの担当者へ届けるならAPIで組む条件と全体通知を見送る線引き
宛先が固定か、ワークフローを起動した本人だけなら、ワークフロービルダーで済ませて構いません。保守する人がエンジニアでなくても直せる点は、コードにない強みです。
次のどれかに当てはまるなら、APIで組みます。担当者が案件・顧客・シフトなど業務データで決まる場合、無効化済みアカウントの扱いや未参加チャンネルへの振り替えなど例外処理が要る場合、1件の通知で個人とグループを組み合わせる場合です。社員マスタとSlackのIDの対応づけ、障害時の再送、通知の重複防止まで含めて設計するなら、API開発・システム連携の範囲として要件から整理するのが近道です。
反対に、監視アラートや定期レポートの自動通知で<!channel>や<!here>を使う設計は見送ります。毎回全員に通知が飛ぶ運用はチャンネルごとミュートされやすく、本当に呼びたい障害通知まで読まれなくなるためです。呼び出したい相手が当番なら、当番用のユーザーグループを作って<!subteam^ID>で呼び、当番交代はグループのメンバー変更で済ませる形が長く保ちます。
よくある質問
Slack APIのメンションについて、実装の前後でよく出る質問に答えます。
Incoming Webhookでもメンションできますか?
Incoming Webhookでもメンションの送信は可能です。Webhookに送るJSONのtextやBlock Kitのmrkdwnに、<@U0123ABCD>や<!subteam^ID>をそのまま書けば、APIと同じ記法で解釈されます。ただしWebhookはIDを調べる機能を持たないため、ユーザーIDは事前にusers.lookupByEmailなどで取得して設定に持たせる必要があります。投稿先が発行時に選んだ1チャンネルに固定される点も含め、相手がそのチャンネルに参加しているかを先に確かめてください。
Botがメンションされたときに反応させるにはどうしますか?
必要なのは、メンションを送る側ではなく受け取る側の実装です。Events APIのapp_mentionイベントを購読し、app_mentions:readスコープを付けると、Botがメンションされたときにイベントが届きます。Bolt for Pythonでの受け取り方とスレッドへの返信はSlack Botの作り方:Bolt for Pythonでメンション返信からLambda運用までで、コード付きで説明しています。
表示名からユーザーIDを調べることはできますか?
専用のメソッドは無いため、users.listでユーザー一覧を取得し、各ユーザーのprofileにある表示名や本名と照合します。表示名は重複でき、本人がいつでも変更できるので、照合で見つけたIDを保存して、以後はIDで扱うのが安全です。社内の通知であれば、社員マスタのメールアドレスからusers.lookupByEmailで引く方が取り違えが起きません。
ユーザー名や表示名を変えるとメンションは壊れますか?
IDで書いたメンションは壊れません。公式の書式ページは、名前は変わりうる一方でIDは常に同じままだと説明しており、投稿時に表示名へ置き換えて表示されます。壊れるのは@tanakaのように名前の文字列で書いた古い実装で、こちらはユーザーについては自動変換自体が廃止されています。改名のたびに設定を直している場合は、IDでの記法へ書き換える時期です。
ワークフロービルダーでユーザーグループにメンションできますか?
外から宛先を差し替える用途では、グループは選べません。Slackヘルプによると、Webhookトリガーのカスタム変数で選べるデータタイプはチャンネルID・ユーザーID・ユーザーのメール・テキストの4つで、ユーザーグループの型はありません。固定のグループを毎回呼ぶだけなら、メッセージ本文の入力欄でグループを指定できるかを実際の画面で確かめてください。当番グループを条件で切り替えたい場合は、APIで<!subteam^ID>を組み立てて投稿する方が確実です。
関連記事
- Slack APIとは?Web API・Events API・Boltの違いと実装手順:メンションを送るWeb APIと受け取るEvents APIの全体像
- Slack Botの作り方:Bolt for Pythonでメンション返信からLambda運用まで:メンションを受け取って返信する側の実装
- Slack Bot Tokenの取得と確認:xoxbの発行からauth.testでの検証・ローテーション・漏えい時の失効まで:users:read.emailなどのスコープを足す手順
- Slackワークフローの作り方:フォーム・条件分岐・変数・Webhookの設定とカスタムステップ開発:コードを書かずに変数でメンションする方法
- Teams APIでメッセージ送信する方法:Graph・Workflows・Botの実装とメンション・カードのコード例:Teamsで同じ通知を組む場合のメンションの書き方