企业微信 × AI 对接第四步:应用消息 message/send——和群机器人怎么分工,配额与字节红线都算给你(附代码)
W2 用群机器人把审批结果播到了群里。但下一批需求马上会撞墙:“能不能只发给主管本人?”“能不能在消息里带同意/驳回按钮?”——群机器人做不到:它只活在群里,也没有交互回调。这两件事都是应用消息的领地。
一、接口速览表
| 项 | 官方事实(2026-11-05 核对) |
|---|---|
| 请求 | POST https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=ACCESS_TOKEN |
| 定向 | touser(成员,` |
| 消息类型 | text / image / voice / file / video / textcard / news / mpnews / markdown / miniprogram_notice / template_card |
| text.content | ≤2048 字节(utf8,中文约680字),超了截断不报错;支持 \n 换行与 <a> 链接 |
| markdown.content | ≤2048字节;微工作台不展示 markdown 消息 |
| textcard | title≤128字符、description≤512字符(div class 仅 gray/normal/highlight 三色)、url≤2048字节须带协议头 |
| 频率 | 每应用 ≤ 账号上限数×200 人次/天(一次发1000人=1000人次);对同一成员 ≤30次/分钟、≤1000次/小时,超出直接丢弃不下发 |
| 防重复 | enable_duplicate_check:1 时相同请求 json 在 duplicate_check_interval(默认1800秒,最大4小时)内不重复送达 |
| 返回 | invaliduser/invalidparty/invalidtag/unlicenseduser 列出无效接收人(errcode 仍为0);全部无效才报 81013 |
| 官方建议 | 大部分企业习惯整点/半点推送造成挤占,建议避开 :00 与 :30 触发 |
二、先想清楚:哪条消息走机器人、哪条走应用消息
| 需求 | 通道 | 理由 |
|---|---|---|
| 告警/审批播报到项目群 | 群机器人 webhook(W2) | 零配置免token,群内协作天然可见 |
| 只提醒审批人本人 | 应用消息 | 机器人做不到定向到人 |
| 消息里带"同意/驳回"按钮 | 应用消息 template_card | 机器人卡片无回调(W2 讲过),交互卡片是应用消息独有 |
| 全员通知(发工资条类) | 应用消息 touser=@all | 吃配额,见第四节预算 |
| 富文本长文(公告) | 应用消息 mpnews | 内容存企微侧,可统计阅读 |
一句话分工:群协作走机器人,一对一与交互走应用消息。W2+W4 拼起来,"AI 生成内容"就有了完整投递矩阵。
三、最容易踩的三个坑
坑1:errcode=0 不等于发出去了。 部分接收人不在应用可见范围时,返回 invaliduser 但整体成功。不检查这个字段,你会以为发了、实际没发。81013 才是全灭。
坑2:字节不是字数。 '巡检'*700 是 1400 个字符,但 utf8 下 4200 字节——超 2048 直接截断,而且不报错。中文为主的正文,预算按 680 字规划。
坑3:超频静默丢弃。 对同一成员超过 30次/分钟,消息不送达也没有错误码返回——AI 循环重试场景最容易滚成"看起来发了实际全丢"。
四、报文装配 + 预算计算器(实测跑通)
"""应用消息报文装配 + 发送预算计算器(对齐官方限制,dry-run 不出网)"""
import json
TEXT_MAX_BYTES = 2048 # text.content 最长2048字节,超过截断
TOUSER_MAX = 1000 # touser 最多1000人
PEAK_MINUTES = (0, 30) # 官方:整点/半点挤占,建议避开
def blen(s): # utf8字节数(企微按字节限,中文3字节)
return len(s.encode('utf-8'))
def build_text(content, agentid, users, dup=0, dup_interval=1800):
assert content.strip(), 'text.content 必填'
warn = []
if blen(content) > TEXT_MAX_BYTES:
warn.append('内容%d字节>2048,将被截断' % blen(content))
if len(users) > TOUSER_MAX:
raise ValueError('touser 超1000人,需拆批(官方上限)')
msg = {'touser': '|'.join(users), 'msgtype': 'text', 'agentid': agentid,
'text': {'content': content}, 'safe': 0,
'enable_duplicate_check': dup, 'duplicate_check_interval': dup_interval}
return msg, warn
def budget(app_quota_per_day, planned_batch, batch_times_per_day):
"""每应用每日上限=账号上限数*200人次。返回预算判断。"""
need = planned_batch * batch_times_per_day # 一次发1000人算1000人次
ok = need <= app_quota_per_day
return {'日配额人次': app_quota_per_day, '计划消耗人次': need,
'占用率': '%.1f%%' % (need * 100 / app_quota_per_day),
'判定': '可发' if ok else '超配额,降频或分批',
'建议': '避开整点/半点触发' if ok else '先砍 batch_times_per_day'}
if __name__ == '__main__':
c = '【运维播报】CPU 水位连续3次超85%,巡检单已自动创建,请今日内处理。\n详情见工作台。'
msg, warn = build_text(c, agentid=1000088, users=['zhangsan', 'lisi', 'wangwu'])
print('例1 报文:'); print(json.dumps(msg, ensure_ascii=False, indent=1)[:420])
print('告警:', warn or '无')
big = 'x' * 3000
_, warn2 = build_text(big, 1, ['a'])
print('\n例2 3000字节内容告警:', warn2)
zh = '巡检' * 700
_, warn3 = build_text(zh, 1, ['a'])
print('例3 %d字中文(%d字节)告警: %s' % (len(zh), blen(zh), warn3))
print('\n例4 预算:', json.dumps(budget(500 * 200, planned_batch=1000, batch_times_per_day=90), ensure_ascii=False))
print('例5 超配额:', json.dumps(budget(500 * 200, planned_batch=2000, batch_times_per_day=60), ensure_ascii=False))
实测输出:
例1 报文:
{
"touser": "zhangsan|lisi|wangwu",
"msgtype": "text",
"agentid": 1000088,
"text": {
"content": "【运维播报】CPU 水位连续3次超85%,巡检单已自动创建,请今日内处理。\n详情见工作台。"
},
"safe": 0,
"enable_duplicate_check": 0,
"duplicate_check_interval": 1800
}
告警: 无
例2 3000字节内容告警: ['内容3000字节>2048,将被截断']
例3 1400字中文(4200字节)告警: ['内容4200字节>2048,将被截断']
例4 预算: {"日配额人次": 100000, "计划消耗人次": 90000, "占用率": "90.0%", "判定": "可发", "建议": "避开整点/半点触发"}
例5 超配额: {"日配额人次": 100000, "计划消耗人次": 120000, "占用率": "120.0%", "判定": "超配额,降频或分批", "建议": "先砍 batch_times_per_day"}
例3 专门演示"字数没超、字节爆了":1400 个汉字看着不多,4200 字节直接过线。预算算法按官方口径"账号上限×200 人次/天"算总账(例4:500 人企业→10 万人次/天),AI 批量推送前先跑这个函数。
五、错误码表
| errcode | 含义 | 响应 |
|---|---|---|
| 0 + invaliduser | 部分人不可达(常见:不在应用可见范围) | 把 invaliduser 记进日志,补发给可替代人 |
| 81013 | 全部接收人无权限或不存在 | 检查可见范围配置,不是重试能解决的 |
| 45009 | 接口调用超过限制 | 降频 + 用 enable_duplicate_check 防重复轰炸 |
| 40003 | invalid userid | userid 大小写不敏感但拼错就是拼错 |
六、可复制提示词:AI 写播报文案的字节预算版
你是运维播报文案生成器。把监控原始数据压成一条企业微信 text 消息:
1. 全文 ≤600 个汉字(utf8 字节红线 2048,留安全余量)
2. 结构固定三行:【结论】一句话 → 【影响】范围+级别 → 【动作】谁在何时前做什么
3. 数字保留原值,不许"大约/可能";数据来源不明时写"待核实"
4. 禁止 emoji、禁止感叹号堆叠、禁止"亲"式称呼
监控数据:{metrics}
第 1 条约束就是把第三节的字节坑直接写进提示词——模型不懂字节,但你懂,所以让它按字数收敛。
七、关键参数(速查)
| 参数 | 必填 | 说明 |
|---|---|---|
touser/toparty/totag | 三者至少一 | 定向;@all 慎用(吃配额大户) |
msgtype | 是 | 11 种可选,见第一节表 |
agentid | 是 | 应用设置页可见,整型 |
safe | 否 | 1=保密消息带水印不可分享;仅 mpnews 支持 2(企业内分享) |
enable_duplicate_check | 否 | AI 重试链路建议开,防用户收到轰炸 |
八、学完有什么
到这里消息通路全通了:W2 群播报 + W4 一对一/卡片。但 template_card 的"同意/驳回"点了之后事件去哪、怎么把卡片状态更新成"已处理"——那套回调+更新接口是下一篇(W6 交互卡片闭环)的正题。
九、参考与说明
本文系列涉及的提示词模板与配套教程合集,收录于提示岛(prompts.ssjintaitong.com)。
接口原文在企业微信开发者中心官方文档(管理后台侧栏可进入,或搜"发送应用消息"),本文参数、限制值、频率算法均以 2026-11-05 官方页面为准;第四节日历代码实测跑通(输出见正文)。
message/send 全部事实(定向规则/字节上限/频率配额算法/invaliduser与81013/重复消息检查/避开整半点建议/错误码)逐条摘自企业微信开发者中心官方文档 2026-11-05 核对原文;装配与预算代码本地真跑;本文不含任何自动批量发送实现,均为报文与配额讲解。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)