# Send Feishu

> Use when user says "发到飞书", "飞书通知", "通知飞书", "feishu", "发给我", "私发飞书", or explicitly invokes /send-feishu to send messages, images, or files to Feishu group chat or individual via webhook or API

- Skill: `gmh5225/send-feishu` (Agent Skill)
- Install (CLI): `npx skillmds@latest add gmh5225/send-feishu`
- Raw SKILL.md: https://api.skillmd.com/api/skills/gmh5225/send-feishu/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: gmh5225 (https://skillmd.com/u/gmh5225)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/gmh5225/send-feishu

---


# Send Feishu Message

Send messages, images, and files to Feishu groups or individuals.

## Environment Variables

| Variable | Purpose | Required |
|----------|---------|----------|
| `FEISHU_WEBHOOK` | Group Webhook URL | For text/card/image to group |
| `FEISHU_APP_ID` | App credential | For image/file upload & API send |
| `FEISHU_APP_SECRET` | App credential | For image/file upload & API send |
| `FEISHU_CHAT_ID` | Group chat ID (`oc_xxx`) | For API send to group |
| `FEISHU_USER_OPEN_ID` | Personal open_id (`ou_xxx`) | For send to individual |
| `FEISHU_WEBHOOK_SECRET` | Webhook signing secret | Only when group webhook has signature enabled |

## Decision Logic

Choose the sending method based on content type and target:

```
Text/Card to group     → Webhook (simple, no auth needed)
Image to group         → Get token → Upload image → Webhook send image_key
File to group          → Get token → Upload file → API send to chat_id
Image/File to person   → Get token → Upload resource → API send to open_id
Text/Card to person    → Get token → API send to open_id
```

## Choosing Format

**Use text** — simple one-liner (status update, quick note).

**Use card** — content has title + body, structured data, summary, or report.

**Use image** — user explicitly asks to send an image file, screenshot, or chart.

**Use file** — user asks to send .html/.md/.pdf/.doc/.xls etc., or content is too long for card display.

**Send to individual** — user says "发给我", "私发", "发到我的飞书", or specifies a person.

## Step A: Get tenant_access_token

Required for image upload, file upload, and API message sending. Skip this step if only sending text/card via Webhook.

```bash
curl -s -X POST 'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal' \
  -H 'Content-Type: application/json' \
  -d '{"app_id":"'"$FEISHU_APP_ID"'","app_secret":"'"$FEISHU_APP_SECRET"'"}'
```

Response: `{"code":0,"msg":"ok","tenant_access_token":"t-xxx","expire":7200}`

Extract the token and store in a variable for subsequent steps.

## Step B: Upload Image (get image_key)

Required when sending an image. Supports JPEG/PNG/WEBP/GIF/TIFF/BMP/ICO, max 10MB.

```bash
curl -s -X POST 'https://open.feishu.cn/open-apis/im/v1/images' \
  -H "Authorization: Bearer $TOKEN" \
  -F 'image_type="message"' \
  -F 'image=@"/path/to/image.png"'
```

Response: `{"code":0,"data":{"image_key":"img_xxx"}}`

## Step C: Upload File (get file_key)

Required when sending a file. Max 30MB.

```bash
curl -s -X POST 'https://open.feishu.cn/open-apis/im/v1/files' \
  -H "Authorization: Bearer $TOKEN" \
  -F 'file_type="stream"' \
  -F 'file_name="report.md"' \
  -F 'file=@"/path/to/file"'
```

`file_type` values: `opus` (audio), `mp4` (video), `pdf`, `doc`, `xls`, `ppt`, `stream` (binary, use as default).

Response: `{"code":0,"data":{"file_key":"file_xxx"}}`

## Sending via Webhook

If the webhook has **signature verification enabled**, generate `timestamp` and `sign` first, then include them in the webhook payload. If no secret is configured, use the unsigned examples below.

### Signed Webhook Payload

```bash
python3 - <<'PY'
import base64
import hashlib
import hmac
import json
import os
import time

secret = os.environ["FEISHU_WEBHOOK_SECRET"]
timestamp = str(int(time.time()))
string_to_sign = f"{timestamp}\n{secret}".encode("utf-8")
sign = base64.b64encode(hmac.new(string_to_sign, b"", digestmod=hashlib.sha256).digest()).decode("utf-8")

payload = {
    "timestamp": timestamp,
    "sign": sign,
    "msg_type": "text",
    "content": {"text": "MESSAGE_HERE"},
}
print(json.dumps(payload, ensure_ascii=False))
PY
```

将输出的 JSON 作为 `curl -d` 请求体发送到 `$FEISHU_WEBHOOK`。卡片、图片消息同理，只需替换 `msg_type` 和 `content`。

### Send Text

```bash
curl -s -X POST "$FEISHU_WEBHOOK" -H "Content-Type: application/json" \
  -d '{"msg_type":"text","content":{"text":"MESSAGE_HERE"}}'
```

### Send Card

Pick header color by sentiment:
- `green` — success, complete, done
- `orange` — warning, attention
- `red` — error, failure
- `blue` — info, neutral (default)
- `purple` — highlight, special

**Short content (no special characters):**

```bash
curl -s -X POST "$FEISHU_WEBHOOK" -H "Content-Type: application/json" \
  -d '{
    "msg_type":"interactive",
    "card":{
      "header":{"title":{"content":"TITLE","tag":"plain_text"},"template":"COLOR"},
      "elements":[{"tag":"div","text":{"content":"BODY_LARK_MD","tag":"lark_md"}}]
    }
  }'
```

**Long content or content with special characters (`$`, `"`, `\n`, etc.) — always use this pattern:**

```bash
python3 - << 'EOF' | curl -s -X POST "$FEISHU_WEBHOOK" \
  -H "Content-Type: application/json" -d @-
import json
card = {
    "msg_type": "interactive",
    "card": {
        "header": {"title": {"content": "TITLE", "tag": "plain_text"}, "template": "COLOR"},
        "elements": [
            {"tag": "div", "text": {"content": "BODY LINE 1\nBODY LINE 2", "tag": "lark_md"}},
            {"tag": "hr"},
            {"tag": "div", "text": {"content": "SECTION 2 CONTENT", "tag": "lark_md"}}
        ]
    }
}
print(json.dumps(card))
EOF
```

Card body supports lark_md: `**bold**`, `*italic*`, `~~strike~~`, `[link](url)`, `\n` for newlines.

### Send Image via Webhook

After uploading image (Step B) to get `image_key`:

```bash
curl -s -X POST "$FEISHU_WEBHOOK" \
  -H "Content-Type: application/json" \
  -d '{"msg_type":"image","content":{"image_key":"img_xxx"}}'
```

## Sending via API

Use API when sending files, or sending any content to an individual.

### Send to Group (chat_id)

```bash
curl -s -X POST "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=chat_id" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json; charset=utf-8" \
  -d '{"receive_id":"'"$FEISHU_CHAT_ID"'","msg_type":"MSG_TYPE","content":"CONTENT_JSON_STRING"}'
```

### Send to Individual (open_id)

```bash
curl -s -X POST "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json; charset=utf-8" \
  -d '{"receive_id":"'"$FEISHU_USER_OPEN_ID"'","msg_type":"MSG_TYPE","content":"CONTENT_JSON_STRING"}'
```

### API Content Formats

The `content` field must be a **JSON-encoded string** (escaped JSON inside JSON).

**Text:**
```json
{"receive_id":"ID","msg_type":"text","content":"{\"text\":\"Hello\"}"}
```

**Image** (after upload):
```json
{"receive_id":"ID","msg_type":"image","content":"{\"image_key\":\"img_xxx\"}"}
```

**File** (after upload):
```json
{"receive_id":"ID","msg_type":"file","content":"{\"file_key\":\"file_xxx\"}"}
```

## After Sending

Check the response JSON:
- Webhook: `{"code":0,"msg":"success"}` → success
- API: `{"code":0,"msg":"success","data":{...}}` → success
- `code` != 0 → report the error:
  - 19001: webhook URL invalid
  - 19021: signature check failed
  - 19022: IP not whitelisted
  - 19024: keyword check failed
  - 99991663: token invalid or expired (re-run Step A)
  - 230001: bot not in chat or no permission

## Important

- **Always escape** special characters in JSON (quotes, backslashes, newlines)
- Use `printf '%s'` or heredoc to safely pass message content to curl if it contains special characters
- If required env vars are not set, tell user which ones to configure
- If webhook signature is enabled, require `FEISHU_WEBHOOK_SECRET` and include `timestamp` + `sign`
- Keep messages concise — Feishu cards have display limits
- Token from Step A is valid for 2 hours; no need to refresh within a session
- **App permissions required**: `im:message:send_as_bot` (send messages), `im:resource` (upload images and files)

