Dify 插件开发实验(06):通知渠道插件——如何把 Dify 推送到钉钉/企业微信等渠道?
Dify 插件开发实验(06):通知渠道插件——如何把 Dify 推送到钉钉/企业微信等渠道?
Dify 实验系列 · 插件开发 06/12 | 实验编号:DIFY-106-06
基于 Dify 1.16.1 实测(2026-08)
1. 业务场景
先讲一个我们实际遇到的场景。
客服工单 SaaS 的通知场景:用户提交工单后,客服要收到「新工单待处理」的提醒;工单状态变了,用户要收到「您的工单已更新」的消息。团队内部用企业微信,对外通知走钉钉风格 webhook,还可能对接自建的消息网关——同一个通知内容,要按渠道适配格式发出去,发失败了还要自动重试、能查回执。
我们第一次做通知时,第一反应也是「POST 一下 webhook 不就行了」。真正动手才发现——「发出去」和「确保送达」,是两回事:企微和钉钉的 payload 格式不一样,混着用直接发不出去;webhook 静默失败没人知道,工单晾几个小时没人处理;网络抖动失败不重试就丢通知,4xx 的错重试一百遍也没用。
这不是个例。任何「系统要主动触达用户/内部协作」的场景都是这个模式:工单通知、告警推送、审批提醒、营销触达——渠道多、格式杂、发送还可能失败,通知能力必须是一个「带渠道校验/模板/重试/回执的完整能力」,而不是一段裸 http 调用。
2. 场景痛点
这个流程的痛点,在通知链路上体现得最直接:
- 渠道格式各不同:企业微信的 payload 和钉钉不一样,混着用直接发不出去——每接一个渠道就要写一套适配。
- 发送失败无感知:webhook 静默失败,客服没收到提醒,工单晾了几个小时没人处理,业务无感知。
- 重试无策略:临时网络抖动就失败,不重试会丢通知;4xx 类错误(URL 错/鉴权错)重试又毫无意义还拖时间。
- 渠道地址写死在代码:每个环境(测试/生产/客户现场)的 webhook 地址都不同,写死意味着每换一处就改代码。
本质上,通知的难点不在「发出一条消息」,而在「多发、可重试、可回执」——渠道要抽象、失败要降级、状态要可查。
3. 方案:为什么是通知工具插件
选通知工具插件,我们实际对比过:
- 渠道抽象:同一消息按渠道适配 payload(企业微信/钉钉/mock),新增渠道只加一个适配函数,不破坏现有;
- 重试策略分层:仅对可重试错误(超时/5xx)重试 3 次×2s,4xx 不重试直接失败——不浪费时间也不丢通知;
- 回执结构化:
{sent, message_id?, reason?}显式返回,下游 IF-ELSE 按回执分流,失败走降级分支,业务有感知。
这篇文章我们就用它把工单流程里的 http 通知节点升级为正式插件:notify 工具(channel 枚举 wecom/dingtalk/mock + title/body/priority),跑通「渠道适配 → 重试 → 回执 → 降级」的完整通知链路。
4. 整体架构
链路很清晰:收通知参数 → 渠道校验 → 模板渲染 → payload 适配 → 带重试发送 → 回执分流。关键设计是「失败显式化」——回执里带 sent/reason,下游永远知道这次通知到底发出去没有。
5. 模块设计
5.1 渠道适配与重试(tools/notify.py)
同一消息按渠道输出不同 payload,重试只对可重试错误:
CHANNELS = ("wecom", "dingtalk", "mock")
RETRY_TIMES = 3
RETRY_INTERVAL = 2
TIMEOUT = 5
def adapt_payload(channel, title, body, priority):
content = f"[{priority}] {title}\n{body}" if priority and priority != "normal" else f"{title}\n{body}"
if channel == "wecom":
return {"msgtype": "text", "text": {"content": content}}
if channel == "dingtalk":
return {"msgtype": "text", "text": {"content": content}}
return {"channel": channel, "title": title, "body": body, "priority": priority}
def _send_with_retry(self, url, payload):
last_reason = ""
for attempt in range(RETRY_TIMES):
try:
resp = requests.post(url, json=payload, timeout=TIMEOUT)
if resp.status_code < 400:
return {"sent": True, "message_id": mid} # 成功回执
if 400 <= resp.status_code < 500:
# 4xx 不重试:URL 错/鉴权错,重试无意义
return {"sent": False, "reason": f"http {resp.status_code} (not retried)"}
last_reason = f"http {resp.status_code}"
except requests.exceptions.Timeout:
last_reason = "timeout"
except requests.exceptions.RequestException as e:
last_reason = f"network error: {type(e).__name__}"
if attempt < RETRY_TIMES - 1:
time.sleep(RETRY_INTERVAL)
return {"sent": False, "reason": f"{last_reason} (retried {RETRY_TIMES}x)"}
5.2 渠道 URL 走凭证(provider/notify_tool.yaml)
wecom_url/dingtalk_url/mock_url 三个 credential(环境差异),模板在代码(业务差异)——换环境只改凭证。注意 manifest tags:1.16 daemon 合法枚举没有 communication,用 utilities(详见采坑点)。
6. 运行验证
| 输入 | 预期 | 结果 |
|---|---|---|
| 三渠道各发一条(mock 端核对) | payload 与平台格式一致(wecom/dingtalk text 结构) | ✅ 一致 |
| priority=high / normal | high 拼 [high] 前缀,normal 不拼 |
✅ 一致 |
| mock 配置 ?fail=2 | 前两次 500 第三次成功,回执 sent=true | ✅ 实测耗时 4.0s(2 次间隔) |
| mock 持续 500 | 重试耗尽 sent=false + reason | ✅ 4.0s;4xx 不重试 0.0s 直接失败 |
| 非法 channel / 缺 title | 参数错误 param_invalid | ✅ 一致 |
| workflow 注入 fail=99 | 回执 contains "sent": false → 降级分支 |
✅ 双出口正确 |
环境:Dify 1.16.1(Docker Compose),mock webhook 服务 tmp/mock_webhook.py(127.0.0.1:8004,?fail=N 模拟失败 / ?delay=秒 模拟慢响应 / GET /received 查看),凭证指向 host.docker.internal:8004。
7. 实战坑
| 坑 | 现象 | 修复 |
|---|---|---|
| plugin_tag 枚举 | manifest tags 写 communication → 打包报错 plugin_tag 校验失败 | 用合法枚举 utilities(1.16 合法集:search/image/videos/weather/finance/design/travel/social/news/medical/productivity/education/business/entertainment/utilities/other/agent/rag/trigger) |
| 4xx 也重试 | URL 错/凭证错重试无意义还拖时间 | 4xx 不重试直接失败,仅 5xx/超时/网络重试 3 次×2s |
| 降级静默 | 发送失败不告知下游,业务无感知 | 回执 sent=false 显式返回,IF-ELSE contains "sent": false 分流降级分支 |
| 渠道 payload 差异 | 企微与钉钉格式混用发不出去 | 每渠道 adapt_payload 函数,新增渠道加函数不破坏现有 |
| webhook URL 写死 | 换环境就要改代码 | 渠道 URL 在 credentials(环境差异),模板在代码(业务差异) |
| 通知超时拖慢主流程 | webhook 慢响应卡住整个流程 | requests timeout=5,超时归重试路径 |
| mock 未知渠道不报错 | 测不出 4xx 不重试路径 | mock 对白名单外渠道返回 404(模拟真实 URL 错) |
8. 实验文档及源码获取
- 实验文档:DIFY-106-06:通知渠道插件.md
- 验证应用 DSL:dify106_06_验证应用.yml
- 插件安装包:dify106_06_notify_tool.signed.difypkg
- 源码目录:dify-106/dsl | dify-106/plugins
文章聚焦核心配置与采坑点,完整分步操作与渠道 payload 对照表见实验文档原文。
下一篇:Dify 插件开发实验(07):私有模型网关接入——如何让 Dify 用上私有模型网关?
💬 你在这个实验的场景里踩过什么坑?欢迎评论区分享你的实战经验。
更多推荐

所有评论(0)