Send Feishu messages, replies, forwards, merged forwards, urgent notifications, and download message resources via feishu-cli.
feishu-cli: If not yet installed, please go to riba2534/feishu-cli to get the installation method.
Boundary of responsibilities with the feishu-cli-chat skill
Important: CLI path ≠ SKILL ownership.
feishu-cli msgThe subcommands under it are managed by two SKILLs at the same time,divided by action type, not by CLI path.
Action type Subcommand Owning SKILL Sending type(covered by this SKILL) send/reply/forward/merge-forward/urgent/flag/resource-downloadfeishu-cli-msg Reading type(please use the chat SKILL) history/list/get/mget/thread-messages/search-chats/read-users/pinsfeishu-cli-chat Interaction type(please use the chat SKILL) reaction/pin/unpin/deletefeishu-cli-chat To pull group messages over a time window end-to-end (including thread expansion, name resolution, card parsing), just run:
python3 skills/feishu-cli-chat/scripts/fetch_chat_history.py oc_xxx --since 24h
Core concepts
Message architecture
The content field of the Feishu message API is a JSON string(not a JSON object). The CLI provides three input methods:
| Input method | Parameter | Applicable scenario |
|---|---|---|
| Quick text | --text "内容" |
Plain text message, the simplest |
| Send file | --file <路径> or -f |
Local file automatically uploaded and sent (limit 30MB) |
| Send image | --image <路径> |
Local image automatically uploaded and sent (limit 10MB) |
| Inline JSON | --content '{"key":"val"}' or -c |
Simple JSON, done in one line |
| JSON file | --content-file file.json |
Complex messages (cards, rich text, etc.) |
Mutually exclusive: The above 5 input methodsonly one can be specified, specifying more than one at the same time will cause an error.
Receiver type
| --receive-id-type | Description | Example |
|---|---|---|
| Email address | user@example.com | |
| open_id | Open ID | ou_xxx |
| user_id | User ID | xxx |
| union_id | Union ID | on_xxx |
| chat_id | Group chat ID | oc_xxx |
Message type selection
Decision tree (automatically selected when Claude does not specify a type)
By default, prefer interactive(card message), which is visually appealing, rich in content, and supports colors/multiple columns/buttons, etc.
用户需求
├─ 【默认】通知/报告/告警/任何有信息量的消息 → interactive(卡片)
├─ 发送已上传的图片/文件/音视频 → image/file/audio/media
├─ 分享群聊或用户名片 → share_chat/share_user
└─ 仅以下场景才用 text/post:
├─ 用户明确要求发纯文本 → text
└─ 用户明确要求发富文本 → post
Why prefer cards: text does not support any format rendering, post has limited styling, cards support colored headers, multi-column fields, buttons, dividers, notes, etc., and their visual effect is far superior to other types.
Message type overview
| Type | Description | content format | Size limit |
|---|---|---|---|
| text | Plain text | {"text":"内容"} |
150 KB |
| post | Rich text | {"zh_cn":{"title":"","content":[[...]]}} |
150 KB |
| image | Image | {"image_key":"img_xxx"} |
— |
| file | File | {"file_key":"file_v2_xxx"} |
— |
| audio | Voice | {"file_key":"file_v2_xxx"} |
— |
| media | Video | {"file_key":"...","image_key":"..."} |
— |
| sticker | Sticker | {"file_key":"file_v2_xxx"} |
Forward only |
| interactive | Card | Card JSON / template_id / card_id | 30 KB |
| share_chat | Group card | {"chat_id":"oc_xxx"} |
— |
| share_user | Personal card | {"user_id":"ou_xxx"} |
— |
systemSystem divider (p2p only) CLI--msg-typeNot yet included in the whitelist (seecmd/send_message.go:261-266), when needed usefeishu-cli api POST /open-apis/im/v1/messagesto pass through directly.
Identity description
This skill is mainly based onsend-type commands, and by default uses App Token (Bot identity), no login required.
- User Token required:
msg flagfavorite/bookmark subcommands (im:feed.flag:read/write), see the "Message bookmarks" section at the end. - Prefer User Token + Tenant fallback:
msg resource-downloadWhen logged in, automatically use User Token to download (requires that you can see the message); if not logged in, try App Token (requires that the Bot can see the message).
For the Token strategy of other subcommands (reaction/pin/delete/history/list/get/mget/thread-messages/search-chats), see the feishu-cli-chat skill.
Send commands
Basic format
feishu-cli msg send \
--receive-id-type <type> \
--receive-id <id> \
[--msg-type <msg_type>] \
[--text "<text>" | --file <path> | --image <path> | --content '<json>' | --content-file <file.json>]
file type (send file directly)
# 直接发送本地文件(自动上传,限 30MB)
feishu-cli msg send \
--receive-id-type email \
--receive-id user@example.com \
--file /path/to/report.pdf
Automatically infer the file MIME type (opus/mp4/pdf/doc/xls/ppt); for unknown types use stream. For files larger than 30MB, first use file upload to upload to cloud storage, then use --msg-type file --content '{"file_key":"..."}' to send.
image type (send image directly)
# 直接发送本地图片(自动上传,限 10MB)
feishu-cli msg send \
--receive-id-type chat_id \
--receive-id oc_xxx \
--image /path/to/screenshot.png
Supports JPEG, PNG, BMP, GIF, TIFF, WebP formats.
text type
# 最简形式(默认 msg-type 为 text)
feishu-cli msg send \
--receive-id-type email \
--receive-id user@example.com \
--text "你好,这是一条测试消息"
Inline @ syntax supported by the text type:
@User:<at user_id="ou_xxx">Tom</at>——ou_xxxmust be a real open_id@Everyone:<at user_id="all"></at>@Bot: same syntax as @ user, just replaceou_xxxwith the bot open_id
Only open_id can be used:<at email="..."> In a text message,will not trigger an @ entity(the Feishu IM client will automatically render the email string as a hyperlink, which looks like an @ but has no notification and is not a real mention). To @ an email user, first look up the open_id:
# 第一步:查 open_id
feishu-cli user search --email alice@example.com -o json
# 第二步:用真实 open_id @ 人
feishu-cli msg send --receive-id-type chat_id --receive-id oc_xxx \
--text '<at user_id="ou_xxx">Alice</at> 你好'
Fault tolerance (only --text mode):msg send / msg reply In --text mode, it will automatically correct @ tag formats that AI easily writes incorrectly; the following forms will all be normalized to the standard <at user_id="...">:
<at id=ou_xxx>(missing quotes / usingidinstead ofuser_id)<at open_id="ou_xxx"/>(self-closing / usingopen_id)<at user_id=ou_xxx/>(self-closing without quotes)
--content / --content-file modedoes not perform implicit normalize(users are responsible for the JSON they write themselves, to avoid breaking the structure).
Note: text typedoes not supportrich text styles (bold, italic, underline, strikethrough, hyperlinks, etc. will not be rendered). If formatting is needed, please use the post type.
post type (rich text)
Recommended to use the md tag to carry Markdown; one md tag occupies one paragraph:
cat > /tmp/msg.json << 'EOF'
{
"zh_cn": {
"title": "项目进展通知",
"content": [
[{"tag": "md", "text": "**本周进展**\n- 完成功能 A 开发\n- 修复 3 个 Bug\n- [查看详情](https://example.com)"}],
[{"tag": "md", "text": "**下周计划**\n1. 功能 B 开发\n2. 性能优化"}]
]
}
}
EOF
feishu-cli msg send \
--receive-id-type email \
--receive-id user@example.com \
--msg-type post \
--content-file /tmp/msg.json
Tag types supported by post:
| tag | Description | Main attributes |
|---|---|---|
| text | Text | text, style(bold/italic/underline/lineThrough) |
| a | Hyperlink | text, href |
| at | @User | user_id, user_name |
| img | Image | image_key, width, height |
| media | Video | file_key, image_key |
| emotion | Emoji | emoji_type |
| hr | Divider | — |
| code_block | Code block | language, text |
| md | Markdown | text (occupies a paragraph, recommended) |
Automatic image upload (--upload-images)
msg send Supports --upload-images, scans post / interactive Markdown image syntax in messages , and path pointing tolocal imagesautomatically uploads to the Feishu IM image host and replaces them with image_keybefore sending.
| Dimension | Behavior |
|---|---|
| Trigger condition | Only takes effect when --msg-type post or --msg-type interactive (other types are ignored even if passed, see cmd/send_message.go:212) |
| Applicable syntax | the  marker in the content; URLs (http/https) and existing image_key will not be rewritten |
| Path resolution | Relative path: use the directory containing --content-file as basePath; when using --content with inline JSON, usecurrent working directoryas basePath (see cmd/send_message.go:213-220) |
| Failure fallback | On upload failure, error out directly and do not continue sending an incomplete message (to avoid the server rejecting it due to a missing image_key) |
| Progress indication | When uploading > 0 images, print to stderr 已自动上传 N 张本地图片 |
# post 内嵌本地图:自动上传相对路径 ./diagrams/foo.png
feishu-cli msg send --receive-id-type email --receive-id user@example.com \
--msg-type post --content-file /path/to/post.json --upload-images
# interactive 卡片内嵌本地图同理
feishu-cli msg send --receive-id-type chat_id --receive-id oc_xxx \
--msg-type interactive --content-file /tmp/card.json --upload-images
No need to call
feishu-cli media uploadin advance: this flag already wraps the full flow of "upload + replace + send". To send an image as a standaloneimagemessage, use the--image <path>shortcut directly.
interactive type (card message)
There are three ways to send card messages:
Method 1: Full Card JSON (send only; for complex cards, first generate with feishu-cli-card)
cat > /tmp/card.json << 'EOF'
{
"schema": "2.0",
"header": {
"template": "blue",
"title": {"tag": "plain_text", "content": "任务完成通知"}
},
"body": {
"direction": "vertical",
"elements": [
{"tag": "markdown", "content": "**项目**: feishu-cli\n**状态**: 已完成\n**负责人**: <at id=all></at>"}
]
}
}
EOF
feishu-cli msg send \
--receive-id-type email \
--receive-id user@example.com \
--msg-type interactive \
--content-file /tmp/card.json
Method 2: template_id
cat > /tmp/card.json << 'EOF'
{
"type": "template",
"data": {
"template_id": "AAqk1xxxxxx",
"template_variable": {"name": "张三", "status": "已完成"}
}
}
EOF
feishu-cli msg send \
--receive-id-type email \
--receive-id user@example.com \
--msg-type interactive \
--content-file /tmp/card.json
Method 3: card_id
feishu-cli msg send \
--receive-id-type email \
--receive-id user@example.com \
--msg-type interactive \
--content '{"type":"card","data":{"card_id":"7371713483664506900"}}'
Interactive card responsibility boundaries
This skill is only responsible for sending interactive messages, not for designing card JSON.
- Structured or visually appealing cards must first be generated as v2 JSON (schema=2.0) using feishu-cli-card.
- This skill sends: feishu-cli msg send --msg-type interactive --content-file <card.json>.
- Do not write new v1 elements/action/note card templates within this skill; the old v1 examples are only for historical compatibility troubleshooting.
Execution flow
Message sending flow
- Determine the recipient: default
user@example.com(email), or obtain from context - Choose the message type:
- User explicitly specifies a type → use the specified type
- Structured or visually appealing notification → first use
feishu-cli-cardto construct JSON, then useinteractiveto send - User explicitly requests plain text/rich text, or the content is very short → use
text/post
- Prepare the content: for plain text, pass directly to
--text; for card JSON, use--content-file; for files/images, use--file/--image - Send and check the result: execute the command and confirm the returned message_id
Permission requirements
| Permission | Description |
|---|---|
im:message |
Message read/write (send/reply/forward) |
im:message:send_as_bot |
Send messages as a bot |
Notes
| Limit | Description |
|---|---|
| text size limit | Maximum 150 KB per message |
| Card/rich text size limit | Maximum 30 KB per message |
| system message | CLI --msg-type Not supported yet; only valid for p2p sessions; requires feishu-cli api passthrough |
| sticker message | Only forwarding received stickers is supported; uploading your own is not supported |
| Card button callback | Button interaction callbacks require application server-side support; for buttons sent via CLI, only URL redirects are effective |
| API rate limit | Requests that are too fast return 429; wait a few seconds and retry |
| Delete message | Only messages sent by the bot can be deleted |
Error handling
| Error | Cause | Solution |
|---|---|---|
content format of a post type is incorrect |
post type JSON format error | Ensure the format is {"zh_cn":{"title":"","content":[[...]]}} |
invalid receive_id |
Invalid receiver ID | Check whether --receive-id-type and --receive-id match |
bot has no permission |
Bot has no permission | Confirm the application has the im:message:send_as_bot permission |
rate limit exceeded |
API rate limiting | Wait a few seconds and retry |
user not found |
User does not exist | Check whether the email or ID is correct |
card content too large |
Card JSON exceeds 30 KB | Simplify the card content or split it into multiple messages |
Bot/User can NOT be out of the chat |
Bot is not in the target group | Add --user-access-token switch to User identity and retry |
Batch get messages
To read message details and batch get messages, use the feishu-cli-chat skill.
msg get/list/history/mgetBy default requestsuser_card_contentand additionally extractscard_texts; this behavior and troubleshooting instructions are maintained in the chat skill to avoid mixing sending and reading responsibilities.
Download message resources
Download image or file attachments in messages.
# 下载消息中的图片
feishu-cli msg resource-download <message_id> <file_key> --type image -o /tmp/photo.png
# 下载消息中的文件
feishu-cli msg resource-download <message_id> <file_key> --type file -o /tmp/attachment.pdf
# Bot 不可见但当前用户可见的历史消息资源,可显式用 User Token
feishu-cli msg resource-download <message_id> <file_key> --type file --user-access-token u-xxx -o /tmp/attachment.pdf
# 下载大文件时指定超时时间
feishu-cli msg resource-download <message_id> <file_key> --type file --user-access-token u-xxx -o /tmp/large.bin --timeout 30m
| Parameter | Description | Default |
|---|---|---|
<message_id> |
Message ID | Required |
<file_key> |
Resource file_key | Required |
--type |
Resource type image/file |
Required |
-o, --output |
Output file path | — |
--user-access-token |
Use user identity to download historical message resources that the user can see but the Bot cannot | — |
--timeout |
Download timeout (Go duration format, e.g. 10m、30m、1h) |
5m |
file_key source: via
msg get <message_id>get message details, and extract from contentimage_keyorfile_key. When downloading directly with user identity, if you encounter Feishu large file limits, HTTP Range chunked download and merge will be used automatically.
Thread message
Send to an existing thread
msg send supports --thread-id(equivalent to --receive-id-type thread_id --receive-id <thread_id>):
# 在已有话题内追加一条消息
feishu-cli msg send --thread-id omt_xxx --text "话题内继续聊"
# 卡片消息也支持
feishu-cli msg send --thread-id omt_xxx \
--msg-type interactive \
--content "$(cat card.json)"
--thread-idand--receive-id-type/--receive-idare mutually exclusive; only one group can be specified.
Start a thread when replying
msg reply supports --reply-in-thread(reply_in_thread=true):
# 在非话题群聊中,以话题形式回复某条消息(会开启一个新话题)
feishu-cli msg reply om_xxx --text "这里开个话题" --reply-in-thread
# 若群聊已是话题模式,--reply-in-thread 会自动回复到消息所在话题
Thread reply list
Getting thread replies is reading messages, please use the feishu-cli-chat skill. Sending messages within a thread still uses this skill's msg send --thread-id。
Reference documentation
references/message_content.md: detailed explanation of the content JSON structure for each message typereferences/card_schema.md: interactive sending format and historical card troubleshooting; for new card construction, preferfeishu-cli-card
Message bookmark (msg flag, added in v1.23+)
The server calls it a message flag, used to add a Feed mark to a message and push the message to the user's Feed/bookmarks.
Corresponds to HTTP API POST/GET/PATCH /open-apis/im/v1/flags. Requires User Access Token:list requires im:feed.flag:read,create/cancel requires im:feed.flag:write。
Command quick reference
feishu-cli msg flag create <message_id>— create a message-level bookmark (default--item-type default --flag-type message)feishu-cli msg flag create <message_id> --flag-type feed— feed-level bookmark, automatically readschat_modeto determinethread/msg_threadfeishu-cli msg flag create <message_id> --item-type msg_thread --flag-type feed— feed-level bookmark (explicitly specifies a normal group message thread)feishu-cli msg flag list [--page-size 50] [--page-token xxx]— list the current user's bookmarksfeishu-cli msg flag cancel <message_id> [--item-type ... --flag-type ...]— by default tries to cancel both the message level and the feed level; when item/flag is explicitly passed, only the specified level is canceled
Key enums (server-side ground truth, never fabricate by the order 0/1/2)
CLI flags use strings, mapped underneath to OpenAPI integer enums:
| Field | CLI string | OpenAPI integer | Meaning |
|---|---|---|---|
| --item-type | default | 0 | Normal message |
| --item-type | thread | 4 | topic-style topic group |
| --item-type | msg_thread | 11 | Normal group message thread |
| --flag-type | message | 2 | Message-level bookmark (default) |
| --flag-type | feed | 1 | feed level (sidebar bookmark) |
⚠️ Reverse trap: Some third-party tutorials online (including some older lark-cli examples) have
flag_type: 1=message / 2=feedwritten backwards. This project usesFeishu OpenAPI official ground truthas the standard (1=feed / 2=message, see the table above).listThe command output also uses this same ground truth; when writing code to process the JSON, follow the table above.
listThe command does not accept--flag-type/--item-typeas an input parameter (list returns everything), it can only be filtered on the outputflag_items[*].flag_typefield. To see which bookmarks you have, justfeishu-cli msg flag list -o json。
Supported combinations (the rest are rejected by the server):
default + message: message-level bookmark, the most commonthread + feed: topic-style topic group feed levelmsg_thread + feed: normal group message thread feed level
Source code reference:internal/client/flag.go:23-28(authoritative for this repository, aligned with lark-cli shortcuts/im ItemType/FlagType definitions).