dingtalk-message

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

钉钉消息技能

DingTalk Messaging Skill

负责钉钉消息发送的所有操作。本文件为策略指南,仅包含决策逻辑和工作流程。完整 API 请求格式见文末的
references/api.md
查阅索引。

Responsible for all operations related to DingTalk message sending. This document is a Strategy Guide that only includes decision logic and workflows. For complete API request formats, refer to the lookup index in
references/api.md
at the end of this document.

四种消息通道概览

Overview of Four Messaging Channels

通道适用场景认证方式特点
Webhook 机器人往指定群发通知无需 token,URL 自带凭证最简单;支持加签安全模式
企业内部应用机器人单聊私信 / 群聊消息新版 accessToken可撤回、查已读;需 userId 或 openConversationId
工作通知以应用身份推送到"工作通知"会话旧版 access_token + agentId可推全员/部门;出现在工作通知而非聊天
sessionWebhook回调中直接回复当前对话无需任何认证回调消息自带临时 URL,约 1.5 小时有效

ChannelUse CaseAuthentication MethodFeatures
Webhook BotSend notifications to designated groupsNo token required, URL includes credentialsSimplest to use; supports signature-based security mode
Internal Enterprise App BotOne-on-one private messages / group chat messagesNew version accessTokenSupports message recall and read status query; requires userId or openConversationId
Work NotificationPush to "Work Notification" session as an app identityLegacy access_token + agentIdCan push to all users/departments; appears in Work Notifications instead of chats
sessionWebhookDirectly reply to the current conversation in callbacksNo authentication requiredCallback messages include a temporary URL, valid for approximately 1.5 hours

工作流程(每次执行前)

Workflow (Before Each Execution)

  1. 先理解用户意图 → 判断属于哪个消息通道(见下方「场景路由」)
  2. 读取配置 → 用一条
    grep -E '^KEY1=|^KEY2='
    命令一次性读取该通道所需的全部键值,不要分多次查询。(跨会话保留,所有 dingtalk-skills 共用同一文件)具体需要哪些配置,见[各通道所需配置](### 各通道所需配置)表格
  3. 仅收集该通道所需的缺失配置 → 一次性询问,不要逐条问
  4. 持久化 → 写入 config,后续无需再问
  5. 执行任务
  1. Understand User Intent First → Determine which messaging channel it belongs to (see "Scenario Routing" below)
  2. Read Configuration → Use a single
    grep -E '^KEY1=|^KEY2='
    command to read all required key-values for the channel at once, instead of querying multiple times. (Retained across sessions, shared by all dingtalk-skills) For specific required configurations, see the "Configurations Required for Each Channel" table below
  3. Collect Only Missing Configurations for the Channel → Ask all missing configurations at once, not one by one
  4. Persistence → Write to config, no need to ask again in subsequent operations
  5. Execute Task

各通道所需配置

Configurations Required for Each Channel

通道所需配置来源说明
Webhook
DINGTALK_WEBHOOK_URL
群设置 → 智能群助手 → 添加自定义机器人
Webhook(加签)额外
DINGTALK_WEBHOOK_SECRET
创建机器人时选择"加签"模式获得
机器人消息
DINGTALK_APP_KEY
+
DINGTALK_APP_SECRET
开放平台 → 应用管理 → 凭证信息
工作通知
DINGTALK_APP_KEY
+
DINGTALK_APP_SECRET
+
DINGTALK_AGENT_ID
agentId 在应用管理 → 基本信息
  • robotCode
    =
    appKey
    (完全一致,无需额外配置)
  • 凭证禁止在输出中完整打印,确认时仅显示前 4 位 +
    ****
ChannelRequired ConfigurationsSource Description
Webhook
DINGTALK_WEBHOOK_URL
Group Settings → Smart Group Assistant → Add Custom Bot → Copy URL
Webhook (with Signature)Additional
DINGTALK_WEBHOOK_SECRET
Obtained when selecting "Signature" mode during bot creation
Bot Messaging
DINGTALK_APP_KEY
+
DINGTALK_APP_SECRET
Open Platform → App Management → Credential Information
Work Notification
DINGTALK_APP_KEY
+
DINGTALK_APP_SECRET
+
DINGTALK_AGENT_ID
agentId is located in App Management → Basic Information
  • robotCode
    =
    appKey
    (exactly the same, no additional configuration required)
  • Credentials must not be printed in full in outputs; only show the first 4 characters +
    ****
    during confirmation

执行规范(推荐)

Execution Specifications (Recommended)

始终使用脚本文件执行:凡是包含变量替换(
$(...)
)、管道(
|
)或多行逻辑的命令,一律用
create_file
写到
/tmp/<task>.sh
bash /tmp/<task>.sh
执行,不要内联到终端。内联命令会被终端工具截断或污染,导致变量读取失败。
禁止 heredoc(
<<'EOF'
),会被工具截断。
典型脚本模板(读取配置 → 获取 token(带缓存)→ 执行 API):
bash
#!/bin/bash
set -e
CONFIG=~/.dingtalk-skills/config
Always Execute via Script Files: For commands involving variable substitution (
$(...)
), pipes (
|
) or multi-line logic, always use
create_file
to write to
/tmp/<task>.sh
then execute with
bash /tmp/<task>.sh
. Do not inline them in the terminal. Inlined commands may be truncated or contaminated by terminal tools, leading to variable reading failures.
Prohibit heredoc (
<<'EOF'
), as it will be truncated by tools.
Typical Script Template (Read Config → Get Token (with Cache) → Execute API):
bash
#!/bin/bash
set -e
CONFIG=~/.dingtalk-skills/config

一次性读取所有所需配置

Read all required configurations at once

APP_KEY=$(grep '^DINGTALK_APP_KEY=' "$CONFIG" | cut -d= -f2-) APP_SECRET=$(grep '^DINGTALK_APP_SECRET=' "$CONFIG" | cut -d= -f2-)
APP_KEY=$(grep '^DINGTALK_APP_KEY=' "$CONFIG" | cut -d= -f2-) APP_SECRET=$(grep '^DINGTALK_APP_SECRET=' "$CONFIG" | cut -d= -f2-)

Token 缓存:有效期内复用,避免重复请求

Token Cache: Reuse within validity period to avoid repeated requests

CACHED_TOKEN=$(grep '^DINGTALK_ACCESS_TOKEN=' "$CONFIG" 2>/dev/null | cut -d= -f2-) TOKEN_EXPIRY=$(grep '^DINGTALK_TOKEN_EXPIRY=' "$CONFIG" 2>/dev/null | cut -d= -f2-) NOW=$(date +%s)
if [ -n "$CACHED_TOKEN" ] && [ -n "$TOKEN_EXPIRY" ] && [ "$NOW" -lt "$TOKEN_EXPIRY" ]; then TOKEN=$CACHED_TOKEN else RESP=$(curl -s -X POST https://api.dingtalk.com/v1.0/oauth2/accessToken
-H 'Content-Type: application/json'
-d "{"appKey":"$APP_KEY","appSecret":"$APP_SECRET"}") TOKEN=$(echo "$RESP" | grep -o '"accessToken":"[^"]*"' | cut -d'"' -f4) EXPIRY=$((NOW + 7000)) # token 有效期 2h,提前约 13 分钟过期

更新缓存(先删旧值再追加)

sed -i '/^DINGTALK_ACCESS_TOKEN=/d;/^DINGTALK_TOKEN_EXPIRY=/d' "$CONFIG" echo "DINGTALK_ACCESS_TOKEN=$TOKEN" >> "$CONFIG" echo "DINGTALK_TOKEN_EXPIRY=$EXPIRY" >> "$CONFIG" fi
CACHED_TOKEN=$(grep '^DINGTALK_ACCESS_TOKEN=' "$CONFIG" 2>/dev/null | cut -d= -f2-) TOKEN_EXPIRY=$(grep '^DINGTALK_TOKEN_EXPIRY=' "$CONFIG" 2>/dev/null | cut -d= -f2-) NOW=$(date +%s)
if [ -n "$CACHED_TOKEN" ] && [ -n "$TOKEN_EXPIRY" ] && [ "$NOW" -lt "$TOKEN_EXPIRY" ]; then TOKEN=$CACHED_TOKEN else RESP=$(curl -s -X POST https://api.dingtalk.com/v1.0/oauth2/accessToken
-H 'Content-Type: application/json'
-d "{"appKey":"$APP_KEY","appSecret":"$APP_SECRET"}") TOKEN=$(echo "$RESP" | grep -o '"accessToken":"[^"]*"' | cut -d'"' -f4) EXPIRY=$((NOW + 7000)) # Token validity is 2 hours, set to expire about 13 minutes early

Update cache (delete old values first then append)

sed -i '/^DINGTALK_ACCESS_TOKEN=/d;/^DINGTALK_TOKEN_EXPIRY=/d' "$CONFIG" echo "DINGTALK_ACCESS_TOKEN=$TOKEN" >> "$CONFIG" echo "DINGTALK_TOKEN_EXPIRY=$EXPIRY" >> "$CONFIG" fi

在此追加具体 API 调用

Append specific API calls here


JSON 字段提取:`grep -o '"key":"[^"]*"' | cut -d'"' -f4`

JSON Field Extraction: `grep -o '"key":"[^"]*"' | cut -d'"' -f4`

消息内容缺失时的处理

Handling Missing Message Content

用户未指定消息内容时,不要自行编造,应先询问用户想发送什么内容。仅当用户明确表示"随便发一条测试"时,才使用默认内容:
这是一条来自钉钉机器人的测试消息。

If the user does not specify message content, do not fabricate it yourself; first ask the user what content they want to send. Only when the user clearly states "send a random test message" should you use the default content:
This is a test message from a DingTalk bot.

场景路由(收到用户请求后的判断逻辑)

Scenario Routing (Decision Logic After Receiving User Request)

用户想发消息
├─ 发到群里?
│  ├─ 通用群消息(含 @某人)→ 询问用户选择发送方式(见下方「群消息发送方式选择」)
│  ├─ 明确需要撤回或查已读 → 企业机器人群聊(直接跳过询问)
│  └─ 正在处理机器人回调,直接回复 → sessionWebhook
├─ 发给个人?
│  ├─ 以机器人身份发私信 → 企业机器人单聊
│  └─ 以应用身份推工作通知 → 工作通知
├─ 撤回/查已读?
│  ├─ 机器人消息 → 企业机器人的撤回/已读 API
│  └─ 工作通知 → 工作通知的查询/撤回 API
└─ 回复机器人收到的消息? → sessionWebhook

User wants to send a message
├─ Send to a group?
│  ├─ General group message (including @someone) → Ask the user to select a sending method (see "Group Message Sending Method Selection" below)
│  ├─ Explicitly needs recall or read status query → Enterprise Bot Group Chat (skip the inquiry directly)
│  └─ Processing bot callback, direct reply → sessionWebhook
├─ Send to an individual?
│  ├─ Send private message as a bot → Enterprise Bot One-on-One Chat
│  └─ Push work notification as an app identity → Work Notification
├─ Recall message / check read status?
│  ├─ Bot message → Enterprise Bot's recall/read status API
│  └─ Work notification → Work notification query/recall API
└─ Reply to messages received by the bot? → sessionWebhook

群消息发送方式选择

Group Message Sending Method Selection

用户发起群消息请求时,必须先询问他们选择哪种方式,并说明各自需要什么配置:
请问您想通过哪种方式发这条群消息?
方式需要提供如何获取说明
Webhook 机器人
WEBHOOK_URL
群设置 → 智能群助手 → 添加自定义机器人 → 复制 URL无需应用权限,配置最简单;支持 @某人(
at.atUserIds
企业内部应用机器人
openConversationId
(群会话 ID)
机器人收到群消息时,回调体的
conversationId
字段即为该值
需要
APP_KEY
/
APP_SECRET
;支持撤回、查已读
推荐 Webhook,只需一个 URL 即可,无需任何应用权限。
收到用户选择后按以下方式收集配置:
  • Webhook:收集
    DINGTALK_WEBHOOK_URL
    (若启用加签还需
    DINGTALK_WEBHOOK_SECRET
    ),持久化后执行
  • 企业机器人:收集
    openConversationId
    ,复用已有的
    APP_KEY
    /
    APP_SECRET
    (若未配置则一并收集),调用
    groupMessages/send

执行 API 前按通道获取对应 token:
通道token 类型获取方式使用方式
机器人消息新版 accessToken
POST https://api.dingtalk.com/v1.0/oauth2/accessToken
请求头
x-acs-dingtalk-access-token
工作通知旧版 access_token
GET https://oapi.dingtalk.com/gettoken?appkey=&appsecret=
URL 参数
?access_token=
Webhook无需 token直接 POST 到 Webhook URL
sessionWebhook无需 token直接 POST 到回调中的 sessionWebhook URL
token 有效期均为 2 小时,遇 401 重新获取即可。具体请求/返回格式见 api.md 对应章节。

When a user initiates a group message request, must first ask them to select a method and explain the required configurations for each:
Which method would you like to use to send this group message?
MethodRequired InformationHow to ObtainDescription
Webhook Bot
WEBHOOK_URL
Group Settings → Smart Group Assistant → Add Custom Bot → Copy URLNo app permissions required, simplest configuration; supports @someone (
at.atUserIds
)
Internal Enterprise App Bot
openConversationId
(group session ID)
When the bot receives a group message, the
conversationId
field in the callback body is this value
Requires
APP_KEY
/
APP_SECRET
; supports message recall and read status query
Webhook is recommended, as it only requires a URL and no app permissions.
After receiving the user's selection, collect configurations as follows:
  • If Webhook is selected: Collect
    DINGTALK_WEBHOOK_URL
    (if signature is enabled, also collect
    DINGTALK_WEBHOOK_SECRET
    ), then execute after persistence
  • If Enterprise Bot is selected: Collect
    openConversationId
    , reuse existing
    APP_KEY
    /
    APP_SECRET
    (collect them together if not configured), then call
    groupMessages/send

Obtain the corresponding token by channel before executing the API:
ChannelToken TypeObtaining MethodUsage Method
Bot MessagingNew version accessToken
POST https://api.dingtalk.com/v1.0/oauth2/accessToken
Request header
x-acs-dingtalk-access-token
Work NotificationLegacy access_token
GET https://oapi.dingtalk.com/gettoken?appkey=&appsecret=
URL parameter
?access_token=
WebhookNo token requiredDirect POST to Webhook URL
sessionWebhookNo token requiredDirect POST to the sessionWebhook URL in the callback
All tokens are valid for 2 hours. Re-obtain them when encountering 401 errors. For specific request/response formats, refer to the corresponding sections in api.md.

身份标识(关键决策知识)

Identity Identification (Key Decision Knowledge)

所有消息发送 API 均只接受 userId(staffId),不接受 unionId。这一点已通过实际 API 调用验证。
标识作用域能否用于发消息
userId
(=
staffId
单个企业内唯一✅ 唯一接受的 ID
unionId
跨组织唯一❌ 会被判定无效用户
All message sending APIs only accept userId (staffId), not unionId. This has been verified through actual API calls.
IdentifierScopeUsable for Sending Messages
userId
(=
staffId
)
Unique within a single enterprise✅ The only accepted ID
unionId
Unique across organizations❌ Will be judged as an invalid user

如何获取 userId

How to Obtain userId

  1. 机器人回调(最常用):消息体的
    senderStaffId
    字段
  2. unionId → userId
    POST /topapi/user/getbyunionid
  3. 手机号 → userId
    POST /topapi/v2/user/getbymobile
  4. 管理后台:PC 端钉钉 → 工作台 → 管理后台 → 通讯录
  1. Bot Callback (Most commonly used):
    senderStaffId
    field in the message body
  2. unionId → userId:
    POST /topapi/user/getbyunionid
  3. Mobile Number → userId:
    POST /topapi/v2/user/getbymobile
  4. Admin Backend: PC DingTalk → Workbench → Admin Backend → Address Book

回调消息中的身份字段

Identity Fields in Callback Messages

字段含义可靠性
senderStaffId
发送者 userId企业内部群始终存在;外部群中外部用户可能为空
senderUnionId
发送者 unionId始终存在
userId ↔ unionId 互转的 API 细节:
grep -A 8 "^#### userId ↔ unionId" references/api.md
注意
result.unionid
(无下划线)有值,
result.union_id
(有下划线)在部分企业中为空。

FieldMeaningReliability
senderStaffId
Sender's userIdAlways exists in internal enterprise groups; may be empty for external users in external groups
senderUnionId
Sender's unionIdAlways exists
For details on userId ↔ unionId conversion APIs:
grep -A 8 "^#### userId ↔ unionId" references/api.md
Note that
result.unionid
(no underscore) has a value, while
result.union_id
(with underscore) may be empty in some enterprises.

消息类型速查

Quick Reference for Message Types

Webhook 消息类型

Webhook Message Types

直接在请求 body 的
msgtype
字段指定:
text
|
markdown
|
actionCard
|
link
|
feedCard
各类型完整 JSON 格式:
grep -A 30 "^#### 文本消息" references/api.md
(将
文本消息
替换为
Markdown 消息
ActionCard
等)
Specify directly in the
msgtype
field of the request body:
text
|
markdown
|
actionCard
|
link
|
feedCard
Complete JSON formats for each type:
grep -A 30 "^#### Text Message" references/api.md
(replace "Text Message" with "Markdown Message", "ActionCard", etc.)

机器人消息类型

Bot Message Types

通过
msgKey
+
msgParam
(JSON 字符串)指定:
msgKey类型msgParam 关键字段
sampleText
文本
content
sampleMarkdown
Markdown
title
,
text
sampleActionCard
ActionCard
title
,
text
,
singleTitle
,
singleURL
sampleLink
链接
title
,
text
,
messageUrl
,
picUrl
sampleImageMsg
图片
photoURL
重要
msgParam
必须是 JSON 字符串,不是对象。完整格式:
grep -A 16 "^### 消息类型" references/api.md
Specify via
msgKey
+
msgParam
(JSON string):
msgKeyTypeKey Fields in msgParam
sampleText
Text
content
sampleMarkdown
Markdown
title
,
text
sampleActionCard
ActionCard
title
,
text
,
singleTitle
,
singleURL
sampleLink
Link
title
,
text
,
messageUrl
,
picUrl
sampleImageMsg
Image
photoURL
Important:
msgParam
must be a JSON string, not an object. For complete formats:
grep -A 16 "^### Message Types" references/api.md

工作通知消息类型

Work Notification Message Types

msg
对象的
msgtype
字段指定:
text
|
markdown
|
action_card
注意工作通知的
action_card
用下划线(不同于 Webhook 的
actionCard
)。完整格式:
grep -A 62 "^### 工作通知消息类型" references/api.md

Specify in the
msgtype
field of the
msg
object:
text
|
markdown
|
action_card
Note that
action_card
uses an underscore for work notifications (different from
actionCard
for Webhook). For complete formats:
grep -A 62 "^### Work Notification Message Types" references/api.md

典型场景

Typical Scenarios

"发个群消息通知大家" / "@某人的群消息"

"Send a group message to notify everyone" / "Group message @someone"

→ 先按「群消息发送方式选择」询问用户用 Webhook 还是企业机器人,收集对应配置后执行。
Webhook 支持 @某人:body 中加
at.atUserIds
(用户 ID 数组),正文同时写
@userId
以高亮显示。
→ First ask the user to select between Webhook or Enterprise Bot per "Group Message Sending Method Selection", then execute after collecting corresponding configurations.
Webhook supports @someone: Add
at.atUserIds
(array of user IDs) in the body, and write
@userId
in the body content to highlight the user.

"用 Markdown 发个部署通知到群里"

"Send a deployment notification to the R&D group using Markdown"

→ 先询问发送方式(同上),确认选 Webhook 后用
msgtype: markdown
构造 body;若选企业机器人则用
msgKey: sampleMarkdown
→ First ask the sending method (same as above). If Webhook is selected, construct the body with
msgtype: markdown
; if Enterprise Bot is selected, use
msgKey: sampleMarkdown
.

"给张三发条消息"

"Send a message to Zhang San"

机器人单聊。需
APP_KEY
/
APP_SECRET
+ 张三的 userId。调用
oToMessages/batchSend
Enterprise Bot One-on-One Chat. Requires
APP_KEY
/
APP_SECRET
+ Zhang San's userId. Call
oToMessages/batchSend
.

"往研发群发个带按钮的消息"

"Send a message with buttons to the R&D group"

→ 先按「群消息发送方式选择」询问用户用 Webhook 还是企业机器人,收集对应配置后执行。
Webhook 用
msgtype: actionCard
;企业机器人用
msgKey: sampleActionCard
→ First ask the user to select between Webhook or Enterprise Bot per "Group Message Sending Method Selection", then execute after collecting corresponding configurations.
Webhook uses
msgtype: actionCard
; Enterprise Bot uses
msgKey: sampleActionCard
.

"发工作通知给全员"

"Send a work notification to all staff"

工作通知。需
APP_KEY
/
APP_SECRET
/
AGENT_ID
to_all_user: true
Work Notification. Requires
APP_KEY
/
APP_SECRET
/
AGENT_ID
. Set
to_all_user: true
.

"撤回刚才那条消息"

"Recall the message I just sent"

→ 找到上一次发送返回的
processQueryKey
(机器人)或
task_id
(工作通知),调用对应撤回 API。
→ Find the
processQueryKey
(for bots) or
task_id
(for work notifications) returned from the last send, then call the corresponding recall API.

"回复机器人收到的消息"

"Reply to messages received by the bot"

sessionWebhook。从回调取
sessionWebhook
URL,直接 POST,无需任何认证。

sessionWebhook. Retrieve the
sessionWebhook
URL from the callback, then directly POST to it without any authentication.

错误处理速查

Quick Reference for Error Handling

场景错误特征处理
Webhook
310000 keywords not in content
需包含自定义关键词
Webhook
310000 sign not match
检查签名计算和 timestamp
Webhook
302033 send too fast
限 20 条/分钟,等待重试
机器人
invalidStaffIdList
非空
userId 无效,确认在组织内
机器人
flowControlledStaffIdList
非空
限流,稍候重试
工作通知
errcode: 88
agentId 错误
工作通知
errcode: 33
access_token 过期
通用401 / 403token 过期 / 权限不足
完整错误码表:
grep -A 33 "^## 错误码" references/api.md

ScenarioError CharacteristicsHandling
Webhook
310000 keywords not in content
Must include custom keywords
Webhook
310000 sign not match
Check signature calculation and timestamp
Webhook
302033 send too fast
Limited to 20 messages/minute, wait and retry
Bot
invalidStaffIdList
is non-empty
userId is invalid, confirm the user is in the organization
Bot
flowControlledStaffIdList
is non-empty
Rate-limited, retry later
Work Notification
errcode: 88
Incorrect agentId
Work Notification
errcode: 33
access_token expired
General401 / 403Token expired / Insufficient permissions
Complete error code table:
grep -A 33 "^## Error Codes" references/api.md

所需应用权限

Required App Permissions

功能权限
机器人单聊
Robot.Message.Send
机器人群聊
Robot.GroupMessage.Send
消息已读查询
Robot.Message.Query
消息撤回
Robot.Message.Recall
工作通知
Message.CorpConversation.AsyncSend
Webhook / sessionWebhook无需应用权限

FunctionPermission
Bot One-on-One Chat
Robot.Message.Send
Bot Group Chat
Robot.GroupMessage.Send
Message Read Status Query
Robot.Message.Query
Message Recall
Robot.Message.Recall
Work Notification
Message.CorpConversation.AsyncSend
Webhook / sessionWebhookNo app permissions required

references/api.md 查阅索引

references/api.md Lookup Index

确定好要做什么之后,用以下命令从
references/api.md
中提取对应章节的完整 API 细节(请求格式、参数表、返回值示例):
bash
grep -A 196 "^## 一、群自定义 Webhook 机器人" references/api.md
grep -A 19 "^### 加签计算" references/api.md
grep -A 192 "^## 二、企业内部应用机器人" references/api.md
grep -A 36 "^#### 钉钉身份标识体系" references/api.md
grep -A 16 "^### 消息类型" references/api.md
grep -A 145 "^## 三、工作通知" references/api.md
grep -A 60 "^## 四、sessionWebhook" references/api.md
grep -A 33 "^## 错误码" references/api.md
grep -A 30 "^#### 文本消息" references/api.md
grep -A 30 "^### 批量发送单聊消息" references/api.md
grep -A 30 "^### 发送工作通知" references/api.md
After confirming the operation, use the following commands to extract complete API details (request formats, parameter tables, return value examples) from the corresponding sections in
references/api.md
:
bash
grep -A 196 "^## 一、群自定义 Webhook 机器人" references/api.md
grep -A 19 "^### 加签计算" references/api.md
grep -A 192 "^## 二、企业内部应用机器人" references/api.md
grep -A 36 "^#### 钉钉身份标识体系" references/api.md
grep -A 16 "^### 消息类型" references/api.md
grep -A 145 "^## 三、工作通知" references/api.md
grep -A 60 "^## 四、sessionWebhook" references/api.md
grep -A 33 "^## 错误码" references/api.md
grep -A 30 "^#### 文本消息" references/api.md
grep -A 30 "^### 批量发送单聊消息" references/api.md
grep -A 30 "^### 发送工作通知" references/api.md