Use saved responses from your agent
Find the right approved reply for a thread, send it by reference with attribution, and manage saved responses and response groups over MCP.
Saved responses are your team's approved replies. Response groups collect them by program, and a campaign offers the groups (and individually picked responses) attached to it. A connected agent can do everything the inbox can: find the right response for a thread, send it, and — with admin access — curate the library. Connect a client first: Connect an AI agent to Beep (MCP).
Scopes and roles at a glance:
| Action | Scope | Role required |
|---|---|---|
| List / read responses and groups | inbox:read | Any workspace member |
| Send a saved response (single or bulk) | inbox:write | Any workspace member |
| Create, update, delete responses and groups | admin:workspace | Workspace Admin+ |
| Set a campaign's campaign values | admin:workspace | Workspace Admin+ |
Authoring is gated twice: the connection must hold admin:workspace and the connected user must be a Workspace Admin (or higher) in the workspace.
Find the right response for a thread
Pass the thread to list_responses. You get the responses offered by the campaign the thread belongs to — the same set the inbox shows by default — so one program's replies never end up on another program's thread:
{ "workspace_id": "…", "thread_id": "…" }The result carries a thread_scope summary and a section on every item:
section | Meaning |
|---|---|
campaign_group | Offered through an attached response group (campaign_group_id). |
campaign_individual | Picked individually for the campaign. |
campaign_variant | A child of one of the campaign's A/B variant groups. |
other | Elsewhere in the library (only with include_all: true). |
library | No campaign scoping applies (no thread, or nothing attached). |
A/B variants match the inbox. When the thread's campaign has an A/B variant group, the thread is offered only its assigned variant — the same one the inbox shows — and the item carries experiment_variant_id. If the thread has no assignment yet, it is assigned on the first call exactly as opening it in the inbox would, so repeated calls (and the inbox) always agree.
Set include_all: true to see the rest of the library after the campaign's responses — the inbox's Show all responses toggle. A thread with no campaign, or whose campaign has nothing attached, returns the whole library. Narrow any listing with name_contains, group_ids, or ungrouped.
Send a response by reference
send_saved_response sends an approved response to one thread through the same path as the inbox composer: tracked links are rendered for the recipient, opted-out recipients are recorded as suppressed and never sent, and the send is attributed to the response.
{
"workspace_id": "…",
"thread_id": "…",
"response_id": "…",
"idempotency_key": "reply-7f3a"
}- Edits are attributed.
body_overridereplaces the whole body. When the body you send differs from the saved one (compared after trimming, before rendering), the send is recorded as edited (send_edited); otherwise as direct (send_direct). - Campaign values are filled in for you. A response's
campaign_values(see Campaign values) are resolved by Beep from the thread's campaign: that campaign's value, else the response's default. Send the response as it is; don't replace a campaign value with your own text in abody_override. To change what a campaign sends, useset_campaign_response_values. - Variables are filled in for each recipient. As in the inbox composer,
{{first_name}},{{last_name}},{{full_name}},{{phone_number}}and the workspace's contact fields are rendered for the thread's contact before sending. Abody_overrideis rendered the same way. If the body cannot be rendered — an unknown variable, an invalid template, or nothing left to send — the send is refused with a422validation_error(details.reason: "template_render_failed") and nothing is sent; inbulk_send_saved_responsethat thread fails with codetemplate_render_failed. - You are not limited to the thread's campaign. Scoping is a default view, not a permission: any response in the workspace can be sent.
- It sends now, like an inbox reply. A send that is not part of a campaign is treated as an agent working the inbox: it goes out immediately and is not held for the campaign's quiet hours. This applies to
reply_to_threadandbulk_reply_to_threadstoo. Campaigns and workflows still respect quiet hours, and opted-out recipients are never sent. - Retries are safe. Reusing an
idempotency_keynever sends twice.
To send one response to many threads, use bulk_send_saved_response with up to 25 thread_ids, or a search_preview_id from search_messages (at most 25 threads). Results are per thread (queued, suppressed, or failed), and one failure never stops the rest.
Manage the library (Workspace Admin+)
create_response/update_response— name, body (1–1,600 characters), optional context. Supported variables: the standard{{first_name}},{{last_name}},{{full_name}}and{{phone_number}}, the workspace's active contact fields, and the response's own campaign values; a body with an unknown variable or invalid template is refused.group_idsfiles the response into groups andcampaign_valuesdeclares campaign values; on update each one, when given, replaces what the response had.create_response_group/update_response_group— group names are unique per workspace. Add and remove members withadd_response_ids/remove_response_ids.delete_response/delete_response_group— deleting a group keeps its responses.- Attach groups and responses to a new campaign with
create_campaign_draft(response_group_ids,response_ids);get_campaignreports them and the resolved response count.
Group changes are live. A campaign follows its attached groups, including campaigns that are already sending. Removing a response from a group, or deleting a group or response, changes what running campaigns offer immediately. These tools return affected_active_campaigns; check get_response_group (active_campaigns) first and confirm with the user before removing anything a running campaign uses.
Campaign values
A campaign value is a placeholder in a saved response for something that changes from campaign to campaign but not from contact to contact — a sign-up link, a deadline, an event date. The response declares it once with a default, and each campaign can set its own value. One "Sign up here" response then serves every program, each with its own link.
Campaign values are not contact personalization: every contact on a campaign gets the same value. For per-contact data, use a contact field.
Declare them on the response. Pass campaign_values to create_response or update_response:
{
"workspace_id": "…",
"name": "Sign-up link",
"body": "Sign up here: {{signup_link}} (closes {{deadline}})",
"campaign_values": [
{
"key": "signup_link",
"label": "Sign-up link",
"default_value": "https://example.org/signup"
},
{ "key": "deadline", "label": "Deadline", "default_value": "Friday" }
],
"idempotency_key": "signup-v1"
}keyis the{{key}}placeholder: lowercase letters, digits and underscores, starting with a letter. It must appear in the body, and it can't be a standard variable or one of the workspace's contact fields.default_valueis required. It is used whenever a campaign hasn't set its own value, and for sends outside any campaign.- Values are plain text: no
{{or{%, up to 1,000 characters. A value counts at its full length toward the message's segments, so a long pasted link makes a longer message. - On
update_response,campaign_valuesreplaces the declarations ([]removes them all). A campaign's value for a key you keep is kept.
get_response and every list_responses item carry campaign_values: [{ id, key, label, default_value }].
See a campaign's values. get_campaign returns response_values: every response the campaign offers that declares campaign values, and for each value its response_variable_id, key, label, default_value, override (this campaign's value, or null when the default applies), and value (what a send for this campaign uses).
Set a campaign's values. set_campaign_response_values sets or clears values for one response on one campaign:
{
"workspace_id": "…",
"campaign_id": "…",
"response_id": "…",
"values": [
{ "response_variable_id": "…", "value": "https://example.org/fall-signup" },
{ "response_variable_id": "…", "value": null }
]
}- Only
response_variable_ids listed in that campaign'sresponse_valuesfor that response are accepted. Any other id, or a response the campaign doesn't offer, is refused with a400validation_error(details.reasonisunknown_response_variableorresponse_not_on_value_sheet) and nothing is written. nullclears the campaign's value and restores the default. Values you don't list are left as they are.- It works in every campaign status, including a campaign that is sending. The change applies to messages sent from now on; messages already sent keep their text.
- Setting a value is idempotent, so retrying is safe; the tool takes no idempotency key. The result is the response's refreshed entry, in the same shape as
get_campaign.
Sending. Campaign values resolve the same way on every send path — the inbox, workflows, keyword auto-replies, and send_saved_response / bulk_send_saved_response — using the campaign the thread belongs to. A thread with no campaign gets the defaults.
Updated 4 days ago