Skip to content

MCP Server

Last updated: 2026-08-11. The map of every tool the Sender MCP server exposes, the conventions they all share, and the recipes that span multiple tools. Per-tool input schemas are self-describing (an MCP client discovers them via tools/list); this document is the overview a human — or an LLM’s system prompt — reads first.

EndpointPOST https://mcp.sender.net/mcp (Streamable HTTP, JSON-RPC 2.0)
AuthOAuth 2 bearer token, scope mcp:use
DiscoveryRFC 9728 protected-resource metadata at /.well-known/oauth-protected-resource[/mcp]
PermissionsEach tool checks an RBAC permission (READ_WORKFLOW, WRITE_CAMPAIGN, …) for the calling user’s role
  • Hashed ids. All identifiers are opaque hashed strings, a hashed id from one tool is valid input to any other.
  • Timestamps are ISO 8601 UTC.
  • Pagination is a uniform envelope: {items, page, per_page, total, has_more}.
  • Failures are readable. Every failure is a tool error with a message written for the caller — what went wrong, and usually which tool to call next (see list_domains, use get_event_types, …). No opaque transport errors, no silent partial successes.
  • Idempotency. Creation tools accept an optional idempotency_key; retrying with the same key returns the originally created resource instead of duplicating it (24 h window).
  • Warnings, not surprises. Responses carry a warnings array when something is legal but probably not what the caller meant (legacy {$…} variables, counts still recalculating).
  • Round-tripping. What a client writes is what it reads back, in the same vocabulary — create_segment/get_segment, create_workflow/get_workflow, update_email_campaign_content/get_email_campaign all share shapes. Stored data the public vocabulary cannot express is surfaced as {"supported": false, "reason": …, "raw": …} rather than dropped.
  • Humanized readbacks. Complex structures come with an English rendering (conditions_for_humans on segments, steps_for_humans on workflows) so a caller can verify intent — branch direction, AND/OR scope — without re-deriving it from the structure.
  • Draft-first. Nothing this server creates sends or runs by itself: campaigns are created as drafts and never sent here; workflows are created as drafts, and activation is a separate, deliberate tool call.

Shared by campaigns and workflow email steps:

content_typeMeaningWritable here?
htmlA complete raw-HTML email the caller designsyes
textNot literal plain text — a simple person-to-person style email in basic HTML (<p>, <br>, <a>), the way a Gmail message looks. Only the inner markup is submitted/stored; a full document wrapper (<html>, <body>, styling) is added automatically at send timeyes
drag_and_dropDesigned in the dashboard’s block builder. The block structure, not the compiled HTML, is the source of truthread-only; replacing one with html/text requires overwrite: true and permanently discards the design
  • Personalization is Liquid: {{ firstname }}, {{ email }}, {{ unsubscribe_link }}. Content is stored exactly as passed; legacy {$variable} tokens are not converted and would reach subscribers as literal text — every content write scans for them and warns.
  • Read-backs include content, content_type, and editable_via_mcp.
  • from_email must be on an account sending domain — list_domains reports each domain’s status and a ready_to_send flag (the exact conjunction sending/activation requires).

Access legend: R read-only · W writes · D destructive (has an explicit acknowledgement flag).

ToolAccessWhat it does
get_account_detailsRThe authenticated account: name, timezone, country, status, plan, calling user
list_domainsRSending domains with verified/SPF/DKIM/DMARC status and ready_to_send — how a caller picks a valid from_email
list_phone_numbersRSMS sender numbers (number, type, purpose, active) — prerequisite for SMS steps
list_storesRConnected ecommerce stores (platform, domain) — prerequisite for store-driven triggers/conditions
get_event_typesRCustom event types (account and built-in) — ids used by event triggers/conditions
get_eventsREvent instances for one type, newest first
ToolAccessWhat it does
list_subscribers / get_subscriberRBrowse subscribers; fetch one by id, email, or phone
create_subscriber / update_subscriberWCreate / update profile fields, groups, channel statuses
subscribe_subscribers / unsubscribe_subscribersW(Re)activate or unsubscribe one or more subscribers per channel
list_groups / get_groupRSubscriber groups (tags)
ToolAccessWhat it does
list_segments / get_segmentRDynamic filters; get_segment returns conditions in the public vocabulary + conditions_for_humans
create_segment / update_segmentWAuthor segments in the condition vocabulary (docs/mcp-segment-vocabulary.md); updates are wholesale replacement
ToolAccessWhat it does
list_email_campaigns / get_email_campaignRBrowse campaigns; the single fetch includes audience, reports, and the body (content, content_type, editable_via_mcp)
get_email_campaign_reportRPerformance report for a sent campaign
create_email_campaignWDraft campaign: envelope, audience (groups/segments/send_to_all), optional body (§3). Requires a verified sending domain. Never sends
update_email_campaign_contentWReplace a draft campaign’s body (§3 rules; drag & drop protected)

A workflow is a strict tree: a single trigger, linear runs as arrays, conditions branching via then/else, splits via a/b.

Reading:

ToolAccessWhat it does
list_workflowsRBrowse automations with light engagement stats
get_workflowRThe full graph in the public vocabulary: trigger (with its step id), nested steps, steps_for_humans, incomplete_steps (what blocks activation, with reasons), unreachable-step surfacing. SMS bodies are inlined; email bodies are by reference
get_workflow_reportRPerformance report (+ revenue when entitled)
get_workflow_step_contentROne email step’s envelope + body (§3), or an SMS step’s text + content_as_sent

Creating & validating:

ToolAccessWhat it does
create_workflowWThe whole graph in one call (title + trigger + nested steps). Always a draft. Steps may carry a key so conditions can reference them (if.step). Throttled at 50 creations/account/day
validate_workflowRDry-run: a document (pre-create structural + reference checks with pathed errors) or an existing workflow (the full activation checklist, all problems at once)

Editing (all refuse while the workflow is active — deactivate first; subscribers keep their place):

ToolAccessWhat it does
rename_workflowWTitle only
update_workflow_stepWReconfigure one step by id. Type is immutable. Per type: email envelope, delay settings, a wholesale condition if, split percentages, action settings, a wholesale trigger object (on the trigger step — replacements that would break context conditions are refused)
add_workflow_stepsWInsert a steps fragment anywhere — (parent_step_id, branch: next/then/else/a/b). Existing steps in the slot re-attach after the fragment (non-destructive); continue_branch picks the leg when the fragment ends in a branch. Fragment conditions may reference existing steps via if.step_id
delete_workflow_stepDSplice one step out (children take its place; branching steps need keep_branch — the other branch is deleted) or delete a subtree (delete_children). Refuses to drop subscribers parked on affected steps without force: true; reports dropped_subscribers
update_workflow_email_contentWAn email step’s body (§3 rules)
update_workflow_sms_contentWAn SMS step’s message + sending number (list_phone_numbers); returns content_as_sent (opt-out line, org name applied). Max 1600 chars

Lifecycle:

ToolAccessWhat it does
activate_workflowWRuns the full pre-flight checklist (shared with the dashboard); if anything blocks, nothing activates and all problems are returned. On success the workflow goes live and mid-journey subscribers resume
deactivate_workflowWactive → draft. Nothing is lost; required before any editing
ToolAccessWhat it does
list_image_categoriesRImage folders
upload_image_from_file / upload_image_from_urlWAdd images (base64 bytes / public https URL) for use in email content

5. Recipe: authoring an automation end to end

Section titled “5. Recipe: authoring an automation end to end”
list_domains → pick a from_email on a ready_to_send domain
list_groups / get_event_types / list_stores
→ resolve the ids the trigger and conditions need
create_workflow → the whole graph, as a draft:
{ "title": "Welcome series",
"trigger": { "type": "list_add", "group_ids": ["…"], "frequency": "any_time" },
"steps": [
{ "type": "email", "key": "welcome", "subject": "Welcome!",
"from_name": "Ana", "from_email": "[email protected]" },
{ "type": "delay", "amount": 2, "unit": "day" },
{ "type": "condition",
"if": { "type": "workflow_email_activity", "step": "welcome", "activity": "opened" },
"then": [ { "type": "action", "action": "copy_to_group", "group_id": "…" } ],
"else": [ { "type": "email", "subject": "Did you miss this?", … } ] } ] }
update_workflow_email_content (per email step)
→ bodies in html or text (§3); read steps_for_humans and
incomplete_steps on every response to verify
validate_workflow → the activation checklist, all problems at once
activate_workflow → deliberate go-live (or every remaining problem)
get_workflow_report → monitor; deactivate_workflow to edit again

Editing later: get_workflow for the graph and step ids → add_workflow_steps / update_workflow_step / delete_workflow_step — insertion is always non-destructive (existing steps splice underneath), deletion never silently drops waiting subscribers.

  • Creation is always draft; going live is one deliberate tool (activate_workflow) with a full pre-flight checklist shared with the dashboard.
  • Active workflows are immutable through this surface; deactivation preserves all subscriber progress.
  • Destructive operations name their blast radius and require an explicit flag (force for dropping waiting subscribers, overwrite for discarding drag & drop designs).
  • create_workflow is rate-limited (50/day/account) as an agent-runaway backstop; idempotency keys make retries safe without consuming quota.