Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/sms-channel-test-tool-name.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@getmunin/backend-core': minor
---

Rename `conv_send_voice_sms_channel_test` → `conv_send_sms_channel_test`

The send path dispatches on `capabilities.sendTest`, which only Twilio and MessageBird set. Vapi and Threll both declare `sendTest: false`, so on a voice channel the tool has always answered `channel vendor 'vapi' does not support test sends` — the `voice_sms` qualifier 4.76.0 gave it promised a path that does not exist.

Its sibling `conv_test_voice_sms_channel` keeps the qualifier: all four vendors implement the credential check. The pair reads asymmetrically now, which is the point — their reach differs.

The description says SMS-only and points voice at `conv_test_voice_sms_channel` for credentials and the dashboard's **Make a test call** for end-to-end. `skill://conv/setup-voice-sms-channel` names the error a voice channel gets. There is still no MCP tool that places a call: `ChannelAdminService.call()` stays `/v1`-only, human-initiated.
6 changes: 3 additions & 3 deletions packages/backend-core/docs-fixtures/mcp-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -3984,9 +3984,9 @@
}
},
{
"name": "conv_send_voice_sms_channel_test",
"title": "Conv: Send a real test message on a voice/SMS channel",
"description": "Send a real test message through a voice or SMS channel that supports it, addressed to `to`. Useful for end-to-end deliverability checks. Email channels use conv_send_email_channel_test instead.",
"name": "conv_send_sms_channel_test",
"title": "Conv: Send a real test SMS on a channel",
"description": "Send a real test SMS through an SMS channel (Twilio, MessageBird), addressed to `to`. Useful for end-to-end deliverability checks. Voice channels have no test send — their credentials are checked with conv_test_voice_sms_channel, and a test call is placed by a human from the dashboard. Email channels use conv_send_email_channel_test.",
"audiences": [
"admin"
],
Expand Down
2 changes: 1 addition & 1 deletion packages/backend-core/docs-fixtures/skills.json
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
"title": "Set up a voice or SMS channel",
"description": "Configure a Vapi/Threll voice channel or Twilio/MessageBird SMS channel with non-secret config, hand off the API keys through a credential link, and verify the result.",
"mimeType": "text/markdown",
"content": "# Set up a voice or SMS channel\n\nUse this when a customer wants Munin on a phone number — an AI voice line (Vapi, Threll) or two-way SMS (Twilio, MessageBird).\n\n## TL;DR\n\n1. `conv_list_voice_sms_vendors` — see the supported vendors and each one's config fields; fields marked `secret: true` are never passed by you.\n2. `conv_configure_voice_sms_channel` with `vendor`, a `name`, and the **non-secret** config fields. The channel is created inactive and the response includes a one-time **credential link**.\n3. Share the credential link — the human enters the vendor API keys in the dashboard. Saving completes the vendor-side setup (webhook registration where applicable), verifies the credentials, and activates the channel. The link works once and expires after 24 hours; mint a fresh one with `conv_request_channel_credentials`.\n4. `conv_test_voice_sms_channel` re-verifies stored credentials any time; `conv_send_voice_sms_channel_test` (SMS) sends a real message.\n\n**Never ask for an API key, auth token, or signing key in the conversation** — the tool rejects secret fields.\n\n## Per-vendor non-secret config\n\n- **Vapi (voice)** — `assistantId` (required; the human finds it in the Vapi dashboard), `phoneNumberId` (only for PSTN calls), `publicKey` (only for in-browser voice via the widget), `replaceWebhook: true` if the assistant already has a non-Munin server URL. The link asks for the API key and a webhook secret of your choosing; on save Munin configures the assistant's server URL automatically.\n- **Threll (voice)** — `workerId` (required; from the Threll webapp), `accountId` (optional, resolved from the API key). The link asks for the API key; on save Munin creates the webhook subscription and stores the signing secret Threll returns.\n- **Twilio (SMS)** — `accountSid` (required) plus `fromNumber` or `messagingServiceSid`. The link asks for the auth token.\n- **MessageBird (SMS)** — `originator` (required). The link asks for the access key and signing key.\n\n## While the channel is pending\n\nA channel waiting on its credential link is `active: false` and every admin action on it (`conv_test_voice_sms_channel`, `conv_send_voice_sms_channel_test`, `conv_list_channel_options`, updates) answers `conv_invalid: channel is awaiting credentials`. If the link expired, mint a new one with `conv_request_channel_credentials { channelId }`.\n\n## Picking assistant/worker ids\n\n`conv_list_channel_options` lists a vendor's selectable options (Vapi assistants, Threll workers) **using a channel's stored credentials** — so it works only after the credential link is completed. For the initial create, the human reads the id from the vendor dashboard; to switch later, complete setup first, then call `conv_list_channel_options { channelId }` and update with `conv_configure_voice_sms_channel`.\n\n## Inbound behaviour\n\nReplies thread. A second message from the same number on the same channel lands in that number's most recent open conversation rather than opening a new one; a snoozed conversation is reopened. A new conversation is started only when the previous one was closed.\n\n**Set `defaultAgentMode` per SMS channel.** `conv_configure_voice_sms_channel { channelId, vendor, defaultAgentMode }` decides what the agent does with texts arriving on that number: `auto` replies directly, `draft_only` files a draft for a human to approve, `off` does neither. Use `draft_only` on a number you only send campaigns from, so a reply is never auto-answered. It applies to SMS only — an inbound call is run by the vendor's assistant, not the Munin agent, so passing it on a voice channel is rejected.\n\n**SMS opt-out is automatic and irreversible from your side.** An inbound SMS whose entire body is an opt-out keyword — `STOP`, `STOPP`, `SLUTT`, `AVMELD`, `UNSUBSCRIBE`, `END`, `QUIT`, `CANCEL`, `STOPALL`, case-insensitive, trailing punctuation ignored — suppresses the CRM contact holding that phone number: `doNotContact` is set, `unsubscribedAt` is stamped, and a `crm_activities` note records it. The message is still ingested so the conversation reads truthfully. A sentence that merely contains one of those words (\"can you cancel my order?\") is an ordinary message and suppresses nothing.\n\nSuppressed contacts drop out of `crm_list_contacts_in_segment`, so they stop appearing in outreach audiences. Do not clear the flag to re-add someone — if they ask to opt back in, that is a new consent decision a human records.\n\n## Verify\n\n- `conv_test_voice_sms_channel { channelId }` — vendor-shaped credential check (Twilio account fetch, MessageBird balance, etc.), no message sent.\n- SMS: `conv_send_voice_sms_channel_test { channelId, to }` sends a real message end-to-end.\n- Voice: there is no tool that places a call. A human verifies the channel end-to-end from the dashboard — Channels → the channel's ⋯ menu → **Make a test call**.\n"
"content": "# Set up a voice or SMS channel\n\nUse this when a customer wants Munin on a phone number — an AI voice line (Vapi, Threll) or two-way SMS (Twilio, MessageBird).\n\n## TL;DR\n\n1. `conv_list_voice_sms_vendors` — see the supported vendors and each one's config fields; fields marked `secret: true` are never passed by you.\n2. `conv_configure_voice_sms_channel` with `vendor`, a `name`, and the **non-secret** config fields. The channel is created inactive and the response includes a one-time **credential link**.\n3. Share the credential link — the human enters the vendor API keys in the dashboard. Saving completes the vendor-side setup (webhook registration where applicable), verifies the credentials, and activates the channel. The link works once and expires after 24 hours; mint a fresh one with `conv_request_channel_credentials`.\n4. `conv_test_voice_sms_channel` re-verifies stored credentials any time; on an SMS channel `conv_send_sms_channel_test` also sends a real message.\n\n**Never ask for an API key, auth token, or signing key in the conversation** — the tool rejects secret fields.\n\n## Per-vendor non-secret config\n\n- **Vapi (voice)** — `assistantId` (required; the human finds it in the Vapi dashboard), `phoneNumberId` (only for PSTN calls), `publicKey` (only for in-browser voice via the widget), `replaceWebhook: true` if the assistant already has a non-Munin server URL. The link asks for the API key and a webhook secret of your choosing; on save Munin configures the assistant's server URL automatically.\n- **Threll (voice)** — `workerId` (required; from the Threll webapp), `accountId` (optional, resolved from the API key). The link asks for the API key; on save Munin creates the webhook subscription and stores the signing secret Threll returns.\n- **Twilio (SMS)** — `accountSid` (required) plus `fromNumber` or `messagingServiceSid`. The link asks for the auth token.\n- **MessageBird (SMS)** — `originator` (required). The link asks for the access key and signing key.\n\n## While the channel is pending\n\nA channel waiting on its credential link is `active: false` and every admin action on it (`conv_test_voice_sms_channel`, `conv_send_sms_channel_test`, `conv_list_channel_options`, updates) answers `conv_invalid: channel is awaiting credentials`. If the link expired, mint a new one with `conv_request_channel_credentials { channelId }`.\n\n## Picking assistant/worker ids\n\n`conv_list_channel_options` lists a vendor's selectable options (Vapi assistants, Threll workers) **using a channel's stored credentials** — so it works only after the credential link is completed. For the initial create, the human reads the id from the vendor dashboard; to switch later, complete setup first, then call `conv_list_channel_options { channelId }` and update with `conv_configure_voice_sms_channel`.\n\n## Inbound behaviour\n\nReplies thread. A second message from the same number on the same channel lands in that number's most recent open conversation rather than opening a new one; a snoozed conversation is reopened. A new conversation is started only when the previous one was closed.\n\n**Set `defaultAgentMode` per SMS channel.** `conv_configure_voice_sms_channel { channelId, vendor, defaultAgentMode }` decides what the agent does with texts arriving on that number: `auto` replies directly, `draft_only` files a draft for a human to approve, `off` does neither. Use `draft_only` on a number you only send campaigns from, so a reply is never auto-answered. It applies to SMS only — an inbound call is run by the vendor's assistant, not the Munin agent, so passing it on a voice channel is rejected.\n\n**SMS opt-out is automatic and irreversible from your side.** An inbound SMS whose entire body is an opt-out keyword — `STOP`, `STOPP`, `SLUTT`, `AVMELD`, `UNSUBSCRIBE`, `END`, `QUIT`, `CANCEL`, `STOPALL`, case-insensitive, trailing punctuation ignored — suppresses the CRM contact holding that phone number: `doNotContact` is set, `unsubscribedAt` is stamped, and a `crm_activities` note records it. The message is still ingested so the conversation reads truthfully. A sentence that merely contains one of those words (\"can you cancel my order?\") is an ordinary message and suppresses nothing.\n\nSuppressed contacts drop out of `crm_list_contacts_in_segment`, so they stop appearing in outreach audiences. Do not clear the flag to re-add someone — if they ask to opt back in, that is a new consent decision a human records.\n\n## Verify\n\n- `conv_test_voice_sms_channel { channelId }` — vendor-shaped credential check (Twilio account fetch, MessageBird balance, etc.), no message sent.\n- SMS: `conv_send_sms_channel_test { channelId, to }` sends a real message end-to-end. Voice vendors have no test send — the tool answers `channel vendor 'vapi' does not support test sends`.\n- Voice: there is no tool that places a call. A human verifies the channel end-to-end from the dashboard — Channels → the channel's ⋯ menu → **Make a test call**.\n"
},
{
"uri": "skill://conv/strip-email-signature",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,10 @@ export class ChannelAdminTools {
}

@McpTool({
name: 'conv_send_voice_sms_channel_test',
title: 'Conv: Send a real test message on a voice/SMS channel',
name: 'conv_send_sms_channel_test',
title: 'Conv: Send a real test SMS on a channel',
description:
'Send a real test message through a voice or SMS channel that supports it, addressed to `to`. Useful for end-to-end deliverability checks. Email channels use conv_send_email_channel_test instead.',
'Send a real test SMS through an SMS channel (Twilio, MessageBird), addressed to `to`. Useful for end-to-end deliverability checks. Voice channels have no test send — their credentials are checked with conv_test_voice_sms_channel, and a test call is placed by a human from the dashboard. Email channels use conv_send_email_channel_test.',
audiences: ['admin'],
scopes: ['conv:write'],
input: SendTestInput,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Use this when a customer wants Munin on a phone number — an AI voice line (Vap
1. `conv_list_voice_sms_vendors` — see the supported vendors and each one's config fields; fields marked `secret: true` are never passed by you.
2. `conv_configure_voice_sms_channel` with `vendor`, a `name`, and the **non-secret** config fields. The channel is created inactive and the response includes a one-time **credential link**.
3. Share the credential link — the human enters the vendor API keys in the dashboard. Saving completes the vendor-side setup (webhook registration where applicable), verifies the credentials, and activates the channel. The link works once and expires after 24 hours; mint a fresh one with `conv_request_channel_credentials`.
4. `conv_test_voice_sms_channel` re-verifies stored credentials any time; `conv_send_voice_sms_channel_test` (SMS) sends a real message.
4. `conv_test_voice_sms_channel` re-verifies stored credentials any time; on an SMS channel `conv_send_sms_channel_test` also sends a real message.

**Never ask for an API key, auth token, or signing key in the conversation** — the tool rejects secret fields.

Expand All @@ -26,7 +26,7 @@ Use this when a customer wants Munin on a phone number — an AI voice line (Vap

## While the channel is pending

A channel waiting on its credential link is `active: false` and every admin action on it (`conv_test_voice_sms_channel`, `conv_send_voice_sms_channel_test`, `conv_list_channel_options`, updates) answers `conv_invalid: channel is awaiting credentials`. If the link expired, mint a new one with `conv_request_channel_credentials { channelId }`.
A channel waiting on its credential link is `active: false` and every admin action on it (`conv_test_voice_sms_channel`, `conv_send_sms_channel_test`, `conv_list_channel_options`, updates) answers `conv_invalid: channel is awaiting credentials`. If the link expired, mint a new one with `conv_request_channel_credentials { channelId }`.

## Picking assistant/worker ids

Expand All @@ -45,5 +45,5 @@ Suppressed contacts drop out of `crm_list_contacts_in_segment`, so they stop app
## Verify

- `conv_test_voice_sms_channel { channelId }` — vendor-shaped credential check (Twilio account fetch, MessageBird balance, etc.), no message sent.
- SMS: `conv_send_voice_sms_channel_test { channelId, to }` sends a real message end-to-end.
- SMS: `conv_send_sms_channel_test { channelId, to }` sends a real message end-to-end. Voice vendors have no test send — the tool answers `channel vendor 'vapi' does not support test sends`.
- Voice: there is no tool that places a call. A human verifies the channel end-to-end from the dashboard — Channels → the channel's ⋯ menu → **Make a test call**.
Loading