# Send a Message：发送消息

在该渠道当前的消息窗口内发送自由文本 —— 可以带附件，发到客户当前使用的渠道，或指定的渠道。

## 快速上手

1. 进入 **Automate → Workflows** 打开一个工作流。点 **+**，选 **Send a Message**。
2. 选择一个渠道，或者保持客户上次使用的那个不变。
3. 在 **Message Content · Default** 里写文本。插入 `{{contact.name}}`（联系人的名字）即可个性化。
4. 可选：最多附加 5 个文件；或者在 **Advanced Settings** 里添加发送失败时要运行的步骤。
5. 在顶部工具栏把 **Status** 设成 **Published**，点 **Save**，再确认 **发布**。客服请找所有者或管理员。

## 注意

- 在 WhatsApp、Messenger 或 Instagram 上，客户最后一次发消息（WhatsApp 上也包括来电）之后满 24 小时，就不会发出任何消息。WhatsApp 请改用 **Send WhatsApp Template**。
- 发送失败不会让工作流停下。排在它后面的标签或关闭操作照样会发生，除非你加了失败分支。

## 在哪里找到它

**Automate → Workflows**，打开一个工作流，在画布上点 **+**，然后在 **Add Steps** 里选 **Send a Message**。

## 开始之前

- **所有者**、**管理员** 或 **客服** 都可以添加这一步并保存工作流。只有 **所有者** 或 **管理员** 能发布它，而发布才会让它对真实对话生效。
- 能不能搭建和运行工作流取决于你的套餐，详见[价格](/pricing)。
- 不需要连接 Inchat 以外的任何东西。这一步会发到对话当前使用的渠道，或者你指定的渠道。

## 设置步骤

1. 打开工作流，在需要发消息的位置点 **+**。
2. 选 **Send a Message**。
3. 在渠道选择器里选一个渠道 —— 客户最后使用的那个，或者某个已连接的指定渠道。
4. 在 **Message Content · Default** 里写文本。想要个性化的地方，插入 `{{contact.name}}`、`{{answer.text}}` 或其他字段变量。
5. 可选：在 **Attachment** 里最多附加 5 个文件（超过一个后变成 **Attachments (n/max)**）。需要不同渠道用不同措辞时，在 **Channel Response** 里添加对应文本。
6. 可选：勾选 **Also send on other channels** 或 **Track links**，并打开 **Advanced Settings** 添加失败分支。
7. 保存这一步。要发布：在编辑器顶部的工具栏把 **Status** 设成 **Published**，点 **Save**，再用 **发布** 确认。只改下拉框、不保存的话什么都不会生效。

## 字段

| 字段 | 含义 | 默认值 | 限制 |
| --- | --- | --- | --- |
| Channel | 消息发送的位置：对话最近互动过的渠道，或者你指定的渠道。 | Last interacted channel | — |
| Message Content · Default | 没有单独设置文案的渠道，都会发这段文本，最多 2,000 个字符。像 `{{contact.name}}` 这样的字段变量会在发送时填入实际值。 | — | 带附件时会跟第一个附件一起发：在 WhatsApp 图片、视频或文件下方内嵌显示。若是 WhatsApp 语音消息，或 Messenger、Instagram 上的任何附件，文本会紧跟在文件后面单独发一条消息。 |
| Attachment / Attachments (n/max) | 最多 5 个文件（图片、视频、音频或文件），按列出的顺序发送。 | — | 会关闭 **Also send on other channels** —— 这项多渠道同发只能发文本。 |
| Also send on other channels | 把同样的文本同时发到同一个人名下最多三个其他渠道，按联系人记录里相同的电话号码或邮箱匹配。 | 关闭 | 本步骤加了附件时不可用。即使这个人有多条联系人记录都在同一个渠道上，这个渠道也只发一次。 |
| Track links | 把文本里的链接改写成短链接，这样点击会计入链接点击统计，并且可以触发 **Link clicked** 触发器。 | 关闭 | — |
| Channel Response | 针对某个渠道类型单独设置的文案，当这一步实际在该渠道类型上发送时，会替代默认文本使用。 | — | — |
| Add Message Failure Branch | 发送失败时运行的一条分支，用来代替原本会接着执行的那些步骤。 | 关闭 | 不开启的话，发送失败会被记录下来，但流程照样往下一步走，就像什么都没发生一样。 |

## 它如何运作

- 在 WhatsApp、Messenger 或 Instagram 上，这一步会先检查客户最后一条消息 —— WhatsApp 上还包括最后一次来电 —— 是不是在最近 24 小时内。超出这个窗口，发送会在调用渠道的发送接口之前就被拒绝，什么都不会送到 Meta。
- 发送前，字段变量会被填入实际值；如果开了 **Track links**，链接也会被缩短。
- 在 WhatsApp 上，文本会在发送前一刻，从 Inchat 自己的简易格式标记转换成 WhatsApp 的格式（`**bold**` 变成 `*bold*`，以此类推）。保存到对话里的这一行记录的是转换前的文本，不是 WhatsApp 实际展示给客户的那些字符。
- 如果是通过这一步自带的上传工具录制的语音消息，会以单声道 OGG/Opus 格式发出 —— 它会被自动转换。粘贴的 MP3 或 M4A 链接如果被标成语音消息，会在到达 Meta 之前就被拒绝，因为 WhatsApp 只接受单声道 OGG/Opus 格式的语音消息。
- 粘贴的 `/api/media/<message>/<index>` 链接 —— 这是 Inchat 自己需要登录才能访问的收件箱代理 —— 会先尝试解析。如果它指向这个工作区确实存过的附件，发送前会被换成一个短期有效的签名链接，然后正常发出。
- 只有无法解析的 `/api/media/…` 链接（形态不明或不属于本工作区的 `/api/media` 格式）才会走到下面的格式检查并失败。
- 除此之外，发送前只会检查附件链接的格式 —— 必须是 https 且格式正确。系统不会真的去请求这个链接，确认文件是否真的存在。
- 一个格式正确但已失效、私密或根本错误的 https 链接，照样会通过这个检查并送到 Meta。它可能在 Meta 自己下载文件时失败 —— 这一步看不到这种失败，所以即使客户什么都没收到，也会被记录为已发送。
- 有多个附件时，每个文件会作为单独一条消息，按列出的顺序发送。消息文本只会跟第一个文件一起发 —— WhatsApp 图片、视频或文件是内嵌显示，否则会在它后面单独发一条文本消息。
- **Also send on other channels** 会在这个工作区里查找与这位联系人共享电话号码或邮箱的其他联系人记录。它最多取三个最近活跃的对话，每个渠道一条 —— 即使有两条匹配的联系人记录，同一个渠道也绝不会发两次。
- 多渠道同发会在主发送成功之后，把同样的文本发到那些对话里，但只能发到 Messenger、Instagram、WhatsApp 或网站插件。如果某条相关联系人记录最近使用的渠道是别的（邮件、Telegram 等），那一次发送会以 **unsupported channel type** 失败，然后继续处理下一个目标；这一步本身不受影响。
- 和主发送不同，这项多渠道同发本身不做消息窗口检查。
- 每一次成功送达的消息都会作为工作流的回复写入对话，这样 **Awaiting reply** 才会显示为已回复，而不是卡在那里。

## 它在什么时候会失败

- 消息窗口已关闭：在 WhatsApp、Messenger 或 Instagram 上，超出窗口之后，发送会在调用 Graph 之前就被拒绝。这一步在执行日志里的那一行写着 `Messaging window closed — free-form send blocked`，提示你改用已批准的模板。
- 没有设置 **Add Message Failure Branch**：这一步会被记为失败，流程继续往下一步走。排在它后面的标签、分配或关闭操作照样会发生，尽管这个对话根本没收到这条消息。
- 开启失败分支后，会运行分支自己的步骤，而不是原本会接着执行的那些；这条路径上分支之后的内容都不会运行。
- 粘贴 MP3/M4A 并标成语音消息的会直接失败：执行日志会写出解决办法（换成单声道 OGG/Opus 文件，或者通过这一步上传让它自动转换）。
- 非 https 或格式错误的附件链接，会在尝试发送之前就让这一步失败。这项检查不会去请求链接，所以一个格式正确但已失效或私密的 https 链接照样会通过，送到 Meta，可能在那边才失败 —— 这一步自己记录的结果看不出这种情况。
- 粘贴的 `/api/media/…` 链接只有在无法对应到这个工作区确实存过的文件时才会失败；能对应上的会被换成签名链接，正常发送。
- 当主发送的送达情况无法确认时，即使没有设置失败分支，流程也会在这一步之后停下。最后一次发送状态未确认的对话，不会再尝试执行后面的步骤。

## 它不会做什么

- 它不会自己打开一个已关闭的对话，也不会重新打开已关闭的 WhatsApp/Messenger/Instagram 窗口 —— 那是 **Send WhatsApp Template** 的作用。
- **Also send on other channels** 是把同样的文本同时发到这个人最多三个相关渠道。它不能带附件或按渠道单独设文案，而且这一步一旦发送媒体文件，它就完全不会运行。
- 和主发送不同，这项多渠道同发本身没有消息窗口检查。
- 附件链接检查不会去请求文件 —— 只检查链接的格式（https、格式正确）。已失效或私密的 https 链接不会在这里被拦下；它会送到 Meta，可能在 Meta 自己下载时才失败，而这一步仍然会被记录为已发送。
- 只有无法解析的 `/api/media/…` 链接会被直接拒绝；能对应到真实存储附件的会被换成签名链接并发送。
- 它不会等待回复。发送一确认（或失败），流程会立刻、在同一轮往下一步走 —— 想要暂停等回答的步骤，请搭配 **Ask a Question**。
- 它是发到这个渠道上，不是发到你输入的某个存好的号码。要联系一个从没发过消息的号码，请用 **Start a WhatsApp Chat**。

## 最佳做法

- 凡是涉及金钱或截止时间的地方 —— 付款链接、预约确认 —— 都开启 **Add Message Failure Branch**。这样窗口关闭或附件链接格式错误时，流程就不会误以为客户已经收到通知。
- 失败分支捕捉不到格式正确但已失效的 https 链接 —— 那种链接照样会送到 Meta，并被记录为已发送。
- 想让同一个或另一个工作流里的 **Link clicked** 触发器之后能响应某个链接，就保持 **Track links** 开启。

## 使用场景

- 确认已收到订单，并把收据图片作为媒体文件附上。
- 在 AI 无法回答的路径上，紧接在 **AI Reply** 之后发一条简短的回答。加一条失败分支，渠道拒绝第一次尝试时回退发送纯文本版本。

## 常见问题与排查

### 为什么我的消息一直没送到，客户那边也没看到任何错误？

最常见的原因是消息窗口已经关闭。在 WhatsApp、Messenger 和 Instagram 上，窗口之外的自由文本 Send a Message 会在到达 Meta 之前就被拒绝 —— 什么都不会发出，这一步会在工作流的 **Activity** 标签页里标为失败。要在窗口之外联系 WhatsApp 客户，请用 **Send WhatsApp Template**。

### 这一步失败了。工作流的其余部分还会继续运行吗？

会，除非你开启了 **Add Message Failure Branch**。不开的话，流程照样会往下一步走 —— 排在它后面的标签或分配照样会发生。开了的话，会运行分支自己的步骤，这条路径上分支之后的内容都不会运行。

### 我能发给联系人从未用来发过消息的号码吗？

用这一步不行 —— 它只会发到这个对话自己的渠道，或者你为这个对话指定的渠道。要联系这个对话从没用过的 WhatsApp 号码，请用 **Start a WhatsApp Chat**。

### 添加附件会不会影响 Also send on other channels 的行为？

会，它会被关闭。发到相关渠道的这项功能只会发文本，所以这一步一旦设了附件，它就会被禁用。

## 相关文章

- [Send WhatsApp Template](https://inchat.inseller.my/help/workflows/send-whatsapp-template)
- [Send a File (voice / image / video)](https://inchat.inseller.my/help/workflows/send-a-file)
- [AI Reply](https://inchat.inseller.my/help/workflows/ai-reply)
- [Start a WhatsApp Chat (new number)](https://inchat.inseller.my/help/workflows/start-a-whatsapp-chat)
- [Workflow triggers](https://inchat.inseller.my/help/workflows/workflow-triggers)
- [Workflows: how a flow runs](https://inchat.inseller.my/help/workflows/workflows-overview)

---

Source files: `src/lib/workflows/ai-builder/spec.ts`, `src/lib/workflows/canvas-model.ts`, `src/components/workflows/WorkflowStepConfigDrawer.tsx`, `src/lib/workflows/engine-message-actions.ts`, `src/lib/workflows/engine.ts`, `src/lib/inbox/wa-window.ts`, `src/lib/workflows/send-message-media.ts`, `src/lib/workflows/media-type.ts`, `src/lib/workflows/media-url.ts`, `src/lib/send/rich-text.ts`, `src/lib/workflows/dispatch-send.ts`, `src/lib/access/permissions.ts`
