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 消息
textcardtitle≤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 防重复轰炸
40003invalid useriduserid 大小写不敏感但拼错就是拼错

六、可复制提示词: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 核对原文;装配与预算代码本地真跑;本文不含任何自动批量发送实现,均为报文与配额讲解。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐