For AI agents
SendHQ MCP server
Give an AI agent full, safe control of one SendHQ workspace: send and receive email, verify domains, publish templates, and investigate deliverability through 104 strictly typed tools. Written for agents first; humans welcome.
curl -fsSL https://downloads.sendhq.cc/install.sh | sh
claude mcp add sendhq --env SENDHQ_API_KEY=re_your_key -- sendhq mcpWhat this server is
The SendHQ MCP server lets an AI agent operate one SendHQ workspace through the Model Context Protocol: send email (single, batch, templated, replies, attachments, idempotent retries), read and search sent and received mail (subjects, bodies, and attachment names) and its delivery events, organize mail into labels with auto-filing rules, manage drafts and private attachments, author and publish hosted templates, add and verify domains and their DNS, set up inbound receiving and inbound addresses, inspect deliverability, bounces, complaints, and suppressions, run newsletters (consented contacts with tags and custom fields, segments, lists with signup forms, welcome emails, and newsletter sites, and broadcasts with pre-send checks, test sends, scheduling, and bot-filtered open and click reports), and read account usage, billing state, analytics, and API-key metadata.
It is a local stdio server built into the sendhq CLI binary. Your MCP client starts sendhq mcp as a child process and speaks JSON-RPC over stdin/stdout. Every tool call becomes one documented request to the SendHQ REST API at https://sendhq.cc/api/v1 authenticated with your workspace API key, so the MCP server has exactly the permissions of that key and no more.
- 104 tools in 9 groups, generated from one catalog that is also published as tools.json.
- Strict JSON Schemas: unknown arguments, wrong types, and missing required fields are rejected locally before anything reaches SendHQ.
- Structured errors with a stable
code, the HTTPstatus, anexplanation, a concreteremedy, and whether retrying can help. - Every tool that sends real email or destroys data says so in the first words of its description and carries MCP safety annotations.
--read-onlymode hides every sending and mutating tool.- Nothing is logged. stdout carries only protocol messages; the API key and message content never reach a log.
https://sendhq.cc/api/mcp (pricing and docs lookup, no account access). The server on this page is the full, account-scoped one; it runs locally or as the hosted connector below.Use SendHQ in Claude, ChatGPT and Grok
No install needed: SendHQ also runs this server as a hosted connector at https://mcp.sendhq.cc/mcp with the same tools. You sign in with your SendHQ account instead of pasting a key.
Claude
- Open Settings → Connectors and find SendHQ in the directory, or choose Add custom connector and paste
https://mcp.sendhq.cc/mcp. - Click Connect, sign in to SendHQ, review the access and click Allow.
- Ask Claude to check your inbox, send an email from your verified domain, or explain a bounce.
ChatGPT
- Open Settings → Security and login and turn on Developer mode.
- Go to chatgpt.com/plugins, click Create MCP app, name it SendHQ and enter
https://mcp.sendhq.cc/mcp. - Sign in to SendHQ and click Allow, then pick SendHQ from the tools menu in a new chat.
Grok
- Go to grok.com/connectors, click New Connector, then Custom.
- Enter
https://mcp.sendhq.cc/mcp, sign in to SendHQ and click Allow.
Muse by Meta
In Muse, open Connectors and search for SendHQ. Click Connect, sign in to SendHQ and click Allow.
Approval and disconnecting
- The
request_featuretool sends a feature request to the SendHQ team with your account details, so we can follow up by email. - Tools that send real email or delete data are labelled as such. Whether the assistant asks you first is set per tool in the assistant: in Claude, choose Needs approval for those tools under Settings → Connectors → SendHQ.
- The connector gets its own API key, named after the assistant (for example “Claude (AI connector)”). Delete it under API Keys to disconnect immediately.
- It cannot create or revoke API keys or change billing. Attachments are sent and returned as base64; there is no local file access.
- Unpaid workspaces (integration trial) can deliver only to the account email or a mailbox-simulator test address.
Questions: postmaster@sendhq.cc. Privacy: sendhq.cc/privacy.
Install
Install the sendhq binary (Linux, macOS, and Windows on x86-64 and arm64). The installer verifies the release checksum and puts the binary in ~/.local/bin by default.
curl -fsSL https://downloads.sendhq.cc/install.sh | shirm https://downloads.sendhq.cc/install.ps1 | iexsendhq version
SENDHQ_API_KEY=re_your_key sendhq doctorCreate an API key in the dashboard at https://sendhq.cc/app#/keys. The MCP server cannot create keys. The one command that runs the server is:
SENDHQ_API_KEY=re_your_key sendhq mcpYou normally never run that by hand: the MCP client launches it. When run in a terminal it waits for JSON-RPC on stdin.
Configure your client
Claude Code
claude mcp add sendhq --env SENDHQ_API_KEY=re_your_key -- sendhq mcp
# read-only variant
claude mcp add sendhq-readonly --env SENDHQ_API_KEY=re_your_key -- sendhq mcp --read-onlyAdd --scope user to make it available in every project, or --scope project to write it to the project's .mcp.json. For a shared .mcp.json, reference the key from the environment instead of committing it; Claude Code expands ${VAR} in .mcp.json.
{
"mcpServers": {
"sendhq": {
"command": "sendhq",
"args": [
"mcp"
],
"env": {
"SENDHQ_API_KEY": "${SENDHQ_API_KEY}"
}
}
}
}OpenAI Codex
[mcp_servers.sendhq]
command = "sendhq"
args = ["mcp"]
env = { SENDHQ_API_KEY = "re_your_key" }Or from the command line: codex mcp add sendhq --env SENDHQ_API_KEY=re_your_key -- sendhq mcp.
Claude Desktop
Edit claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/, Windows: %APPDATA%\Claude\) and restart the app. Desktop apps do not inherit your shell PATH, so use the absolute path of the binary (which sendhq).
{
"mcpServers": {
"sendhq": {
"command": "/Users/you/.local/bin/sendhq",
"args": [
"mcp"
],
"env": {
"SENDHQ_API_KEY": "re_your_key"
}
}
}
}Any other MCP client
Configure a stdio server with command sendhq, arguments ["mcp"] (optionally "--read-only"), and the environment variables below. The server supports MCP protocol versions 2024-11-05, 2025-03-26, 2025-06-18, and 2025-11-25, and implements initialize, ping, tools/list, and tools/call. Tool results carry both a JSON text block and structuredContent.
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"1"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_service_health","arguments":{}}}There is no hosted HTTP transport for the account-scoped server. A remote, write-capable MCP endpoint would need per-user OAuth, which SendHQ does not offer; the local binary keeps the key on the machine that already holds it.
Environment and flags
| Variable or flag | Required | Meaning |
|---|---|---|
SENDHQ_API_KEY | yes | Workspace API key (re_…). Every tool except get_service_health needs it. Without it the server still starts and every call returns a structured auth_error explaining how to fix it. |
SENDHQ_API_BASE_URL | no | API base URL. Default https://sendhq.cc/api/v1. Use it only for a local or staging deployment. SENDHQ_BASE_URL is accepted as an older alias. |
SENDHQ_MCP_READ_ONLY | no | 1, true, or yes behaves like --read-only. |
--read-only | no | Expose only tools that neither send email nor change state. Hidden tools are also refused if called by name. |
SENDHQ_PROFILE / --profile | no | Use a key stored by sendhq auth login in the OS keyring instead of SENDHQ_API_KEY. The environment variable wins when both exist. |
The key is sent only as the Authorization: Bearer header to the configured base URL. It is never printed, logged, echoed in errors, or included in tool results.
Safety model for agents
- Sends real email.
send_email,send_batch,send_template_test,send_broadcast,send_broadcast_test,schedule_broadcast(at the scheduled time), andsend_broadcast_to_moredeliver mail to real people and consume delivery credits. Their descriptions start withSENDS REAL EMAIL. Call them only when the user explicitly asked for that specific message to be sent, with the recipients, sender, and content confirmed. - Destructive.
delete_email,delete_draft,delete_attachment,delete_domain,delete_inbox,remove_suppression, the campaign deletes (delete_contact,delete_list,remove_list_contact,delete_broadcast,cancel_broadcast,delete_segment,delete_contact_field), andbulk_update_contactsare markeddestructiveHint: trueand their descriptions start withDESTRUCTIVE. Confirm with the user first.remove_suppressionweakens a safety block and is appropriate only when a human confirms the address works again. - Changes state. Creating or updating drafts, templates, domains, and inboxes, publishing templates, and starting verification change the workspace but do not send mail.
- Read-only. Everything else is
readOnlyHint: trueand safe to call freely. - DNS is never changed by this server.
add_domainreturns records for a human to publish;get_domain_connect_linkreturns a consent URL that a person must open and approve at their DNS provider. - Billing is never changed by this server.
get_accountreads plan, usage, and subscription state only. - Unpaid workspaces (integration trial) can deliver only to the account owner's email (
get_account→user.email) or a mailbox-simulator test address such assuccess@simulator.amazonses.com, and cannot send attachments. - Accepted is not delivered. A successful send returns an ID; delivery, bounce, and complaint evidence arrives later in
list_email_events. Never claim inbox placement or that a person read a message. Broadcast opens are estimates: privacy proxies and security scanners load images on their own, and requests that look automated are counted apart (machineinget_broadcast_report). - Do not rotate to a different From address to get around a
423pause, and never re-add unsubscribed or complained recipients.
API keys are out of scope
By design there are no tools that create, modify, rotate, revoke, or delete API keys. An agent must not mint or destroy credentials. list_api_keys returns only names, non-secret prefixes, and last-used times. Key management stays in the dashboard with a signed-in human.
Workflows
1. First send
get_service_healthconfirms the API is reachable (works without a key).get_accountshows the plan (access.tier), remaining quota, anduser.email. On the trial, that email is the only real recipient allowed.list_sending_identitieslists From addresses you can use. If it is empty, do the domain workflow first.- Confirm sender, recipient, subject, and body with the user, then
send_emailwith anidempotency_key. list_email_eventswith the returnedidshowsdelivery,bounce,complaint, orrejectonce the provider reports it (usually seconds to minutes).
{
"name": "send_email",
"arguments": {
"from": "Acme <hello@example.com>",
"to": [
"owner@example.com"
],
"subject": "SendHQ is connected",
"text": "It works.",
"idempotency_key": "first-send-2026-09-26"
}
}2. Domain verification end to end
add_domainwithname: "example.com". The result includes the DNS records (DKIM CNAMEs, domain verification, SPF, recommended DMARC).get_dns_providerwith thedomain_iddetects the authoritative DNS provider and returns the exact relative host to enter for each record at that provider.- If
providers.domainConnect.availableis true,get_domain_connect_linkreturns a consent URL. Give it to the human; nothing changes until they approve at the provider. Otherwise, give the human the records to publish. Never publish a second SPF record: merge SendHQ's SPF include (include:amazonses.com) into the existingv=spf1value. verify_domainrechecks DNS and DKIM verification. Status moves throughpending,checking, andpropagatingtoverified. Pollverify_domainorget_domainevery 30–60 seconds; DNS can take minutes to hours.- When
statusisverified, the domain's addresses appear inlist_sending_identities.
3. Bounces, complaints, and suppressions
list_blocked_recipientsreturns every blocked address with its reason (bounce,complaint,unsubscribe) and a summary count.list_suppressionsreturns hard-bounce and complaint suppressions;deliverability_statsgives 30-day delivery, bounce, and complaint rates;list_sender_reputationshows which From addresses are throttled or paused.- A send containing a suppressed recipient fails with
422 recipient_suppressed. Remove that recipient and send again. - Only when a human confirms a bounced mailbox now works, call
remove_suppression. Complaint suppressions are permanent (409 complaint_suppression_locked).
4. Receive inbound email
- The domain (often a subdomain such as
inbound.example.com) must be verified. setup_inboundprovisions receiving and returns one MX record. A human publishes it.verify_inbounduntilstatusisready.create_inboxwithdomain_idandlocal_part(for examplesupport) createssupport@inbound.example.com.- Poll
list_emailswithdirection: "in"andunread: true(optionallyinbox_id). Read a message withget_email, its conversation withget_thread, attachments withdownload_attachment, and mark it handled withmark_email(read: true). - Reply in-thread with
send_emailandreply_to_email_id; SendHQ sets In-Reply-To, References, and the thread.
5. Webhooks and event notifications
Webhooks push signed email events (email.sent, email.delivered, email.delivery_delayed, email.bounced, email.complained, email.failed, email.received) to an endpoint a human configures in the dashboard or with the REST API (see /docs/webhooks). There is no webhook tool, because endpoint responses carry signing secrets. An agent without a webhook polls instead: list_email_events for one message's outcome, list_emails with status (for example bounced) or after for recent changes, list_emails with direction: "in" and unread: true for new inbound mail, and list_blocked_recipients for new suppressions. Poll no more than about once a minute per question.
6. Diagnose a delivery failure
- Find the message:
list_emailswithdirection: "out"andtoorquery, orget_emailif you have the ID.status: failedmeans SendHQ or the provider rejected it at submission; the email's error explains why. list_email_events:bounce(permanent or transient, with the provider diagnostic),complaint,reject, ordelivery. No events yet means the provider has not reported; wait and check again.- If the send call itself failed, read the error
code:sender_domain_unverified→ finish domain verification;recipient_suppressed→ the address hard-bounced or complained before;sender_paused→ inspectlist_sender_reputationand fix the list source;trial_recipient_restricted→ trial limits;quota_exhausted→get_accountusage. get_domainchecks that DKIM, SPF, and DMARC are still published;deliverability_statsshows whether the problem is one message or a trend.- Report what the evidence shows. A
deliveryevent means the recipient's server accepted the message, not that it reached the inbox or was read.
7. Own a task bucket (labels)
create_labelwithname(for exampleAgent/Orders) andskip_inbox: true. That makes the label a bucket: received mail that gets it is archived, so it appears only in the label, never the human's Inbox.- Send task mail with
send_email(orsend_batch) andlabels: ["Agent/Orders"]. Replies to that conversation inherit the label automatically and skip the Inbox. - For mail that starts outside your conversations, add a filing rule:
create_label_rulewithinbox_id(a dedicated address such asorders@…),from,to, orsubject. Passapply_to_existing: trueto file mail already received. - Work the bucket:
list_emailswithlabel: "Agent/Orders",direction: "in", andunread: true; read withget_emailorget_thread, reply withsend_emailandreply_to_email_id, andmark_emailread: truewhen handled. - Move a stray message in or out with
label_email(add/remove). Adding a bucket label to a received message also archives it. - Optionally
set_inbox_forwardingsends a copy of everything a receiving address gets to another mailbox (the target confirms by email first).
{
"name": "send_email",
"arguments": {
"from": "Orders <orders@example.com>",
"to": [
"customer@example.net"
],
"subject": "Order 1042: confirm delivery window",
"text": "Reply with a time that works.",
"labels": [
"Agent/Orders"
],
"idempotency_key": "order-1042-window"
}
}8. Attachments and templates
Attach up to 10 files with send_email attachments (each needs content_base64 or a local file_path; filename defaults to the file's basename) on a paid plan. For hosted templates: create_template → update_template_draft → render_template to preview with sample data → send_template_test (sends one real test) → publish_template, then send with send_email or send_batch using template: {key, data} and exactly one to recipient.
9. Send a newsletter
- Audience:
list_lists(orcreate_listwith a signup form), and optionallypreview_segmentthencreate_segmentto narrow it, for example to contacts taggedVIPwho opened in the last 90 days. Add only people who asked for your email: every subscribed contact needs aconsent_basis. get_newsletter_settings: ifpostalAddressis empty, ask the user for one and set it withupdate_newsletter_settings(marketing email needs it).create_broadcastwithlist_id, a verifiedfrom,subject,preview_text, content (markdownis simplest), and optionallyaudience(segment_id,limit,order: engaged).check_broadcast: fix everyblockitem and tell the user aboutwarnitems. Thensend_broadcast_testto the user's own address so they can look at it.- Only when the user says to send:
send_broadcastnow (with anidempotency_key), orschedule_broadcastwithscheduled_at(unschedule_broadcastto change it). - Follow up with
get_broadcast(delivery totals),get_broadcast_report(opens, clicks, links, inbox providers, against your average), andlist_broadcast_recipientswithactivity. Report opens as estimates. With a limited audience,send_broadcast_to_moresends to the next people.
{
"name": "create_broadcast",
"arguments": {
"list_id": "lst_…",
"from": "Acme <news@example.com>",
"subject": "What shipped in October",
"preview_text": "Three new features",
"markdown": "Hi {{first_name | there}},\n\nHere is what shipped this month.",
"audience": {
"segment_id": "seg_…"
}
}
}Results, pagination, and errors
A successful call returns the API's JSON object as structuredContent and as a JSON text block. Every list_* tool accepts limit (1–200, default 50) and offset, and adds a pagination object. Keep calling with offset: pagination.next_offset while has_more is true.
{
"data": [
"…"
],
"count": 50,
"pagination": {
"offset": 0,
"limit": 50,
"returned": 50,
"total": 180,
"has_more": true,
"next_offset": 50
}
}A failed call returns isError: true with a structured error. Follow remedy instead of retrying blindly; only retry when retryable is true.
{
"error": {
"code": "trial_recipient_restricted",
"status": 402,
"message": "The integration trial can deliver only to your account email or a mailbox-simulator test address",
"retryable": false,
"explanation": "This workspace is on the unpaid integration trial. Trial sends can be delivered only to the account owner's email address or a mailbox-simulator test address.",
"remedy": "Send to the account email (get_account -> user.email) or a simulator address such as success@simulator.amazonses.com to test. To email anyone else, the account owner must activate a paid plan in the dashboard (Profile & Billing). Do not retry the same recipients."
}
}Optional error fields: request_id (quote it to support), retry_after_seconds, problems (list of schema violations for invalid_arguments), and idempotent_replayed (see Idempotency).
Idempotency
send_email and send_batch accept idempotency_key (max 200 characters), sent as the Idempotency-Key header. Generate one stable key per logical message, for example invoice-4812-receipt.
- A retry must reuse the same key AND an identical request body. Same key with any change (recipient, subject, body, header, template data, even argument values) returns
409 idempotency_conflict. - Same key, same body, original finished: SendHQ returns the stored result without sending again. This is how you safely retry after a timeout or
network_error. - Same key while the original is still running:
409 idempotency_in_progress, retryable after a short wait. - A new logical message needs a new key.
- Stored failures replay too. If the first attempt failed, retrying with the same key returns that same failure with
idempotent_replayed: trueandretryable: false. Checklist_emails(direction: out) to confirm nothing went out, fix the cause, then send with a new key. - The server never retries a POST on its own. Only read-only GET calls are retried automatically (up to 3 attempts on network errors, 429, and 5xx).
send_emailwith inlineattachmentscannot take anidempotency_key, because it runs several requests. For retry-safe attachment sends:create_draft→upload_attachment→send_emailwithdraft_idandidempotency_key.
{
"name": "send_email",
"arguments": {
"from": "Acme <billing@example.com>",
"to": [
"owner@example.com"
],
"subject": "Receipt #4812",
"text": "Thanks for your payment.",
"idempotency_key": "receipt-4812"
}
}Rate limits and quotas
SendHQ does not publish a fixed requests-per-second limit for the API. The limits an agent actually meets are usage limits, returned as 429:
- Monthly recipient deliveries per plan. Each To, Cc, and Bcc address counts as one delivery. See
get_account→usage.recipientDeliveriesvsusage.emailQuotaMonth. - Daily recipients per exact From address, set by that sender's reputation state (
list_sender_reputation→dailyLimit, 2,000 by default on paid plans). - Integration trial: 100 recipients in total, only to the account email or mailbox-simulator test addresses.
- Attachments: at most 10 files and 10 MB per message; 10 GB of recipient-weighted attachment transfer per month on paid plans.
- Per request: To + Cc + Bcc up to 100 addresses;
send_batchup to 100 messages. - Reputation circuit breaker: in a rolling 7-day window, bounces or complaints above threshold throttle or pause one From address (
423 sender_paused). It recovers automatically once rates fall.
quota_exhausted is not retryable until the period resets or the plan changes. rate_limited is retryable after retry_after_seconds; for sends, retry with the same idempotency_key and identical body.
Error catalogue
code is stable; branch on it rather than on message.
| code | HTTP | Retry? | What it means and what to do |
|---|---|---|---|
invalid_arguments | — | no | Arguments failed the tool's JSON Schema locally; nothing reached SendHQ. Fix the fields listed in problems. |
auth_error | 401 | no | Missing, revoked, or wrong API key. Set SENDHQ_API_KEY for the server process; a human creates keys in the dashboard. |
trial_recipient_restricted | 402 | no | Integration trial can deliver only to the account email or a mailbox-simulator test address. Send there, or the owner activates a paid plan. |
payment_required | 402 | no | Feature needs a paid plan (for example attachments). Send without it or upgrade. |
sender_domain_not_owned | 403 | no | From domain is not in this workspace. Use list_sending_identities or add_domain. |
sender_domain_unverified | 403 | no | From domain is not verified yet. get_domain, publish missing records, verify_domain. |
domain_limit_reached | 403 | no | Plan domain limit reached. Remove an unused domain (with approval) or upgrade. |
marketing_not_enabled | 403 | no | Marketing class is not enabled for this domain or plan. Use transactional only if the message genuinely is. |
forbidden | 403 | no | Policy does not allow the operation. Adjust the request. |
not_found | 404 | no | ID is not in this workspace. List the resource to find the right ID; restore archived templates first. |
idempotency_conflict | 409 | no | Key reused with a different body. Resend the exact original, or use a new key for a new message. |
idempotency_in_progress | 409 | yes | Original request still running. Wait, then retry with the same key and body. |
revision_conflict | 409 | no | Template draft changed since you read it. get_template, merge, save again. |
complaint_suppression_locked | 409 | no | Recipient complained. Never email them again. |
inbound_not_ready | 409 | no | Inbound receiving not ready. setup_inbound, publish MX, verify_inbound. |
conflict | 409 | no | Resource already exists or is in the wrong state. Read it and adjust. |
attachments_too_large | 413 | no | More than 10 files or 10 MB. Remove or shrink attachments. |
recipient_suppressed | 422 | no | A recipient hard-bounced or complained before. Remove them; see list_blocked_recipients. |
recipient_unsubscribed | 422 | no | A recipient opted out of marketing mail. Remove them permanently. |
validation_failed | 422 | no | Content rejected, for example template data that breaks the variable contract. Fix the input. |
sender_paused | 423 | no | This From address is paused by the 7-day bounce/complaint circuit breaker. Stop, fix the list, wait for automatic recovery. |
quota_exhausted | 429 | no | Monthly, daily per-sender, attachment, or trial limit reached. Check get_account; wait for reset or upgrade. |
rate_limited | 429 | yes | Slow down; wait retry_after_seconds. Sends: same key, same body. |
server_error | 5xx | yes | Temporary SendHQ or provider failure. Back off and retry; sends with the same key and body. If idempotent_replayed is true, use a new key after confirming nothing was sent. |
network_error | — | yes | Request or response lost. Retry; for sends the same idempotency_key makes that safe. |
invalid_request | 400 | no | Malformed request. Read message and correct it. |
tool_error | — | no | Local failure inside the MCP server (for example an unreadable file_path). Read message. |
Tool reference
Every tool with its safety class, the REST endpoint it calls, its parameters, return shape, and an example tools/call params object. Parameters are exact: the server rejects anything not listed.
Emails and threads: send_email, send_batch, list_emails, get_email, mark_email, delete_email, list_email_events, get_thread
Labels and auto-filing rules: list_labels, get_label, create_label, update_label, delete_label, create_label_rule, delete_label_rule, label_email
Drafts, attachments, and sender identities: list_sending_identities, create_draft, list_drafts, get_draft, update_draft, delete_draft, upload_attachment, download_attachment, delete_attachment
Hosted templates: list_templates, create_template, get_template, update_template_draft, create_template_draft, render_template, send_template_test, publish_template, archive_template, restore_template
Domains and DNS: list_domains, get_domain, add_domain, verify_domain, delete_domain, get_dns_provider, get_domain_connect_link
Inbound email: setup_inbound, verify_inbound, list_inboxes, get_inbox, create_inbox, update_inbox, set_inbox_forwarding, delete_inbox
Deliverability, bounces, and suppressions: deliverability_stats, list_sender_reputation, list_suppressions, remove_suppression, list_blocked_recipients
Campaigns: list_contacts, create_contact, get_contact, update_contact, delete_contact, import_contacts, list_contact_imports, bulk_update_contacts, list_tags, list_contact_fields, set_contact_field, delete_contact_field, list_segments, get_segment, preview_segment, create_segment, update_segment, delete_segment, list_lists, create_list, get_list, update_list, delete_list, add_list_contacts, remove_list_contact, list_broadcasts, create_broadcast, get_broadcast, update_broadcast, delete_broadcast, send_broadcast, cancel_broadcast, list_broadcast_recipients, check_broadcast, send_broadcast_test, schedule_broadcast, unschedule_broadcast, duplicate_broadcast, send_broadcast_to_more, get_broadcast_report, get_newsletter_settings, update_newsletter_settings, get_campaign_overview, list_uploaded_images
Account, usage, analytics, and keys: get_account, get_account_diagnostics, get_analytics, list_api_keys, get_service_health
No tools match this filter.
Emails and threads
send_emailSend one email
SENDS REAL EMAIL. Send one message from a verified domain: raw html/text, a published hosted template, a reply in an existing thread, or a message with attachments. Pass idempotency_key so a retry cannot send twice; a retry must reuse the same key AND an identical request, otherwise SendHQ returns 409. attachments is a convenience that creates a draft, uploads each file, and sends with that draft; it cannot be combined with idempotency_key or draft_id (use create_draft + upload_attachment + send_email with draft_id for retry-safe attachment sends). Unpaid workspaces (integration trial) can deliver only to the account email or a mailbox-simulator test address, and cannot send attachments.
Provide at least one of: html, text, template.
| Parameter | Type | Required | Description |
|---|---|---|---|
from | string | yes | Sender, e.g. Acme <hello@example.com>. The domain must be verified in this workspace (see list_sending_identities). (max 998 chars) |
to | string[] | yes | Recipients. Each entry is an address, optionally with a display name. To+cc+bcc may total at most 100; every destination consumes one delivery credit. (1–100 items) |
cc | string[] | no | Carbon-copy recipients. (0–100 items) |
bcc | string[] | no | Blind-copy recipients. (0–100 items) |
subject | string | no | Subject line. Omit when sending a template. (max 998 chars) |
text | string | no | Plain-text body. Provide text, html, or template. |
html | string | no | HTML body. SendHQ sanitizes it and derives text when text is omitted. |
reply_to | string | no | Reply-To address. |
headers | object | no | Extra safe custom headers (string values), e.g. {"X-Entity-Ref-ID": "123"}. Routing headers such as From/To/Message-ID are controlled by SendHQ. |
message_class | string | no | transactional (default) or marketing. Marketing requires a marketing-enabled plan or domain and adds unsubscribe handling. (one of transactional, marketing) |
reply_to_email_id | string | no | Reply inside an existing conversation: the em_… ID of the message being answered. SendHQ sets In-Reply-To/References and the thread. |
thread_id | string | no | Explicit thread ID to file the message under. |
draft_id | string | no | Send a stored draft's attachments with this message (dr_…). The draft is deleted after a successful send. |
template | object | no | Send a published hosted template instead of raw html/text. Requires exactly one to recipient and no cc/bcc; the template supplies the subject. Provide at least one of: id, key. |
template.id | string | no | Template ID (tmpl_…). Provide id or key. |
template.key | string | no | Template key such as account-welcome. Provide id or key. |
template.version_id | string | no | Optional published release ID (tmplv_…). Defaults to the current published release. |
template.data | object | no | Values for the template's typed variables. |
labels | string[] | no | Label names or lbl_… IDs to file this message under. Unknown names are created. Replies in the conversation inherit the labels, and a bucket label (skip_inbox) keeps those replies out of the Inbox. Max 10. (0–10 items) |
idempotency_key | string | no | Idempotency-Key header (max 200 chars). Reuse it only to retry this exact request. (max 200 chars) |
attachments | object[] | no | Files to attach (max 10 files, 10 MB total). Each needs content_base64 (plus filename) or a local file_path. (0–10 items) Provide at least one of: content_base64, file_path. |
attachments[].filename | string | no | File name shown to the recipient. Required with content_base64; defaults to the basename of file_path. (max 255 chars) |
attachments[].content_type | string | no | MIME type, e.g. application/pdf. Defaults to application/octet-stream. |
attachments[].content_base64 | string | no | Standard base64 file content. |
attachments[].file_path | string | no | Absolute path of a local file readable by the MCP server process. |
{
"name": "send_email",
"arguments": {
"from": "Acme <hello@example.com>",
"to": [
"owner@example.com"
],
"subject": "Your export is ready",
"text": "Download it from your dashboard.",
"idempotency_key": "export-ready-42"
}
}send_batchSend a batch of individualized emails
SENDS REAL EMAIL. Send 1–100 independent messages in one request (use this for per-recipient template personalization). Each item has the same shape as send_email (without attachments/idempotency_key). Items succeed or fail individually: HTTP 207 means partial success; inspect each data[i].ok and data[i].error. One idempotency_key covers the whole batch body.
| Parameter | Type | Required | Description |
|---|---|---|---|
emails | object[] | yes | Messages to send. (1–100 items) Provide at least one of: html, text, template. |
idempotency_key | string | no | Idempotency-Key for the entire batch (max 200 chars). (max 200 chars) |
{
"name": "send_batch",
"arguments": {
"emails": [
{
"from": "Acme <hello@example.com>",
"to": [
"owner@example.com"
],
"template": {
"key": "account-welcome",
"data": {
"first_name": "Asha"
}
}
}
],
"idempotency_key": "welcome-batch-2026-09-26"
}
}list_emailsList and search email
List sent (direction: out) and received (direction: in) email newest-first with filters. Received mail is classified: read the human inbox with direction: in, archived: false, category: primary; triage with important: true; spam is hidden unless category: spam or include_spam: true. Paginated: the result includes pagination {offset, limit, returned, total?, has_more, next_offset}.
| Parameter | Type | Required | Description |
|---|---|---|---|
direction | string | no | in for received, out for sent. (one of in, out) |
status | string | no | Status filter, e.g. queued, sent, delivered, bounced, complained, failed. |
domain | string | no | Only messages for this domain, or a comma-separated list of domains (matches any). |
inbox_id | string | no | Only messages received by this inbox (inb_…). |
label | string | no | Only messages carrying this label: a label ID lbl_… or exact name, or a comma-separated list (matches any). Use list_labels to see folders. |
archived | boolean | no | false = the Inbox view (received mail not archived), true = archived only. Omit for all mail. |
category | string | no | primary (people), updates (newsletters, bulk, automated), or spam; or a comma-separated list. Spam is hidden unless requested. |
important | boolean | no | true = only messages flagged important (replies to conversations you started, and senders marked important). |
include_spam | boolean | no | Include spam in the results (for searches across every folder). |
from | string | no | Sender address contains this value. |
to | string | no | Recipient address contains this value. |
unread | boolean | no | true = unread only, false = read only. |
after | string | no | ISO-8601 timestamp; only messages created after it. (date-time) |
before | string | no | ISO-8601 timestamp; only messages created before it. (date-time) |
query | string | no | Free-text search over subjects, bodies, sender/recipient addresses, and attachment filenames. (max 200 chars) |
limit | integer | no | Page size. Defaults to 50. (default 50; 1–200) |
offset | integer | no | Number of records to skip. Use pagination.next_offset from the previous page. (default 0; 0–…) |
{
"name": "list_emails",
"arguments": {
"direction": "in",
"unread": true,
"limit": 25
}
}get_emailGet one email
Retrieve one message with headers, html/text body, status, thread metadata, and attachment metadata (download bytes with download_attachment).
| Parameter | Type | Required | Description |
|---|---|---|---|
email_id | string | yes | Email ID (starts with em_), as returned by a list or create tool. (max 128 chars) |
{
"name": "get_email",
"arguments": {
"email_id": "em_123"
}
}mark_emailMark read, archived, spam, or important
Update one message: read, archived, category (primary, updates, spam; received mail only), and important. Reporting spam or marking important teaches SendHQ about that sender for future mail; pass learn: false to change only this message. Pass at least one field.
| Parameter | Type | Required | Description |
|---|---|---|---|
email_id | string | yes | Email ID (starts with em_), as returned by a list or create tool. (max 128 chars) |
read | boolean | no | true = read, false = unread. |
archived | boolean | no | true = archive (skip the Inbox), false = move back to the Inbox. |
category | string | no | Move a received message to primary, updates, or spam. (one of primary, updates, spam) |
important | boolean | no | Flag or unflag the message as important. |
learn | boolean | no | false = do not remember this verdict for the sender (default true). |
{
"name": "mark_email",
"arguments": {
"email_id": "em_123",
"read": true
}
}delete_emailDelete an email
DESTRUCTIVE: permanently delete a retained message and its stored attachments from SendHQ. It does not recall a message that was already delivered.
| Parameter | Type | Required | Description |
|---|---|---|---|
email_id | string | yes | Email ID (starts with em_), as returned by a list or create tool. (max 128 chars) |
{
"name": "delete_email",
"arguments": {
"email_id": "em_123"
}
}list_email_eventsList delivery events for an email
Provider events for one sent message: delivery, bounce, complaint, reject, open, click. This is the evidence for whether a message was delivered or why it failed. Paginated: the result includes pagination {offset, limit, returned, total?, has_more, next_offset}.
| Parameter | Type | Required | Description |
|---|---|---|---|
email_id | string | yes | Email ID (starts with em_), as returned by a list or create tool. (max 128 chars) |
limit | integer | no | Page size. Defaults to 50. (default 50; 1–200) |
offset | integer | no | Number of records to skip. Use pagination.next_offset from the previous page. (default 0; 0–…) |
{
"name": "list_email_events",
"arguments": {
"email_id": "em_123"
}
}get_threadGet a conversation
Retrieve every message in a conversation in chronological order (sent and received), each with attachment metadata.
| Parameter | Type | Required | Description |
|---|---|---|---|
thread_id | string | yes | Thread ID (usually the em_… ID of the first message; see threadId on any email). (max 128 chars) |
{
"name": "get_thread",
"arguments": {
"thread_id": "em_123"
}
}Labels and auto-filing rules
list_labelsList labels
List the workspace's labels (folders) with total and unread counts and their auto-filing rules. Paginated: the result includes pagination {offset, limit, returned, total?, has_more, next_offset}.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Page size. Defaults to 50. (default 50; 1–200) |
offset | integer | no | Number of records to skip. Use pagination.next_offset from the previous page. (default 0; 0–…) |
{
"name": "list_labels",
"arguments": {}
}get_labelGet a label
Retrieve one label with counts and auto-filing rules.
| Parameter | Type | Required | Description |
|---|---|---|---|
label_id | string | yes | Label ID (starts with lbl_) or the exact label name. (max 128 chars) |
{
"name": "get_label",
"arguments": {
"label_id": "Billing"
}
}create_labelCreate a label
Create a folder-style label. Set skip_inbox: true to make it a bucket an agent owns: send with labels: [name] and the replies are filed into the label and kept out of the Inbox. Optional auto-filing rules file new sent/received mail (every condition on a rule must match). Set apply_to_existing to also file retained mail.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | Label name, e.g. Billing or Clients/Acme. Unique per workspace (case-insensitive). (max 64 chars) |
color | string | no | Hex color such as #1a73e8. Optional. |
skip_inbox | boolean | no | Bucket mode: received mail that gets this label (by rule, by replying to a conversation sent with this label, or by hand) is archived so it appears only in the label, not the Inbox. |
rules | object[] | no | Optional auto-filing rules (max 20). Each needs at least one of inbox_id, from, to, subject. (0–20 items) |
rules[].direction | string | no | Only in (received) or out (sent) mail. Omit for both. (one of in, out) |
rules[].inbox_id | string | no | Only mail received by this inbox (inb_…). Files each receiving address into its own folder. |
rules[].from | string | no | Sender contains this text (case-insensitive), e.g. @stripe.com. (max 200 chars) |
rules[].to | string | no | To/Cc contains this text (case-insensitive). (max 200 chars) |
rules[].subject | string | no | Subject contains this text (case-insensitive). (max 200 chars) |
rules[].skip_inbox | boolean | no | Archive matching received mail so it appears only in the label folder, not the Inbox. |
apply_to_existing | boolean | no | Also file already-retained mail that matches the rules. |
{
"name": "create_label",
"arguments": {
"name": "Agent/Orders",
"skip_inbox": true,
"rules": [
{
"from": "@stripe.com"
}
]
}
}update_labelRename, recolor, or bucket a label
Rename a label, change its color, or toggle bucket mode (skip_inbox). Turning bucket mode on archives received mail already in the label.
| Parameter | Type | Required | Description |
|---|---|---|---|
label_id | string | yes | Label ID (starts with lbl_) or the exact label name. (max 128 chars) |
name | string | no | New name. (max 64 chars) |
color | string | no | New hex color. |
skip_inbox | boolean | no | Bucket mode: received mail that gets this label (by rule, by replying to a conversation sent with this label, or by hand) is archived so it appears only in the label, not the Inbox. |
{
"name": "update_label",
"arguments": {
"label_id": "lbl_123",
"name": "Finance/Billing"
}
}delete_labelDelete a label
DESTRUCTIVE: delete a label and its rules. The email itself is kept; it only loses this label.
| Parameter | Type | Required | Description |
|---|---|---|---|
label_id | string | yes | Label ID (starts with lbl_) or the exact label name. (max 128 chars) |
{
"name": "delete_label",
"arguments": {
"label_id": "lbl_123"
}
}create_label_ruleAdd an auto-filing rule
Add a rule to a label so matching new mail is filed automatically. Every condition you set must match. Use inbox_id to give a receiving address its own folder; add skip_inbox to keep it out of the Inbox.
| Parameter | Type | Required | Description |
|---|---|---|---|
label_id | string | yes | Label ID (starts with lbl_) or the exact label name. (max 128 chars) |
direction | string | no | Only in (received) or out (sent) mail. Omit for both. (one of in, out) |
inbox_id | string | no | Only mail received by this inbox (inb_…). Files each receiving address into its own folder. |
from | string | no | Sender contains this text (case-insensitive), e.g. @stripe.com. (max 200 chars) |
to | string | no | To/Cc contains this text (case-insensitive). (max 200 chars) |
subject | string | no | Subject contains this text (case-insensitive). (max 200 chars) |
skip_inbox | boolean | no | Archive matching received mail so it appears only in the label folder, not the Inbox. |
apply_to_existing | boolean | no | Also file already-retained mail that matches. |
{
"name": "create_label_rule",
"arguments": {
"label_id": "Billing",
"inbox_id": "inb_123",
"skip_inbox": true
}
}delete_label_ruleDelete an auto-filing rule
DESTRUCTIVE: remove one auto-filing rule. Mail already filed keeps its label.
| Parameter | Type | Required | Description |
|---|---|---|---|
label_id | string | yes | Label ID (starts with lbl_) or the exact label name. (max 128 chars) |
rule_id | string | yes | Rule ID (starts with lrule_), from get_label. (max 128 chars) |
{
"name": "delete_label_rule",
"arguments": {
"label_id": "lbl_123",
"rule_id": "lrule_123"
}
}label_emailAdd or remove labels on an email
Move a message between folders: add and/or remove labels by name or lbl_… ID. Unknown names in add are created unless create is false.
| Parameter | Type | Required | Description |
|---|---|---|---|
email_id | string | yes | Email ID (starts with em_), as returned by a list or create tool. (max 128 chars) |
add | string[] | no | Labels to add. (0–10 items) |
remove | string[] | no | Labels to remove. (0–10 items) |
create | boolean | no | Create unknown labels in add (default true). |
{
"name": "label_email",
"arguments": {
"email_id": "em_123",
"add": [
"Billing"
],
"remove": [
"Support"
]
}
}Drafts, attachments, and sender identities
list_sending_identitiesList verified sender identities
Addresses and domains this workspace can send from right now (verified domains, their default From, and active inbox addresses). Call before send_email to pick a valid from.
No parameters.
{
"name": "list_sending_identities",
"arguments": {}
}create_draftCreate a draft
Create a composer draft. Drafts hold attachments: create a draft, upload_attachment, then send_email with draft_id. Does not send anything.
| Parameter | Type | Required | Description |
|---|---|---|---|
from | string | no | Sender address on a verified domain (may be empty while drafting). |
to | string[] | no | Recipients. (0–100 items) |
cc | string[] | no | Carbon-copy recipients. (0–100 items) |
bcc | string[] | no | Blind-copy recipients. (0–100 items) |
subject | string | no | Subject line. (max 998 chars) |
html | string | no | HTML body. |
text | string | no | Plain-text body. |
reply_to_email_id | string | no | Email ID this draft replies to. |
thread_id | string | no | Thread ID this draft belongs to. |
{
"name": "create_draft",
"arguments": {
"from": "hello@example.com",
"to": [
"owner@example.com"
],
"subject": "Invoice"
}
}list_draftsList drafts
List composer drafts, most recently updated first. Paginated: the result includes pagination {offset, limit, returned, total?, has_more, next_offset}.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Page size. Defaults to 50. (default 50; 1–200) |
offset | integer | no | Number of records to skip. Use pagination.next_offset from the previous page. (default 0; 0–…) |
{
"name": "list_drafts",
"arguments": {}
}get_draftGet a draft
Retrieve one draft with its attachment metadata.
| Parameter | Type | Required | Description |
|---|---|---|---|
draft_id | string | yes | Draft ID (starts with dr_), as returned by a list or create tool. (max 128 chars) |
{
"name": "get_draft",
"arguments": {
"draft_id": "dr_123"
}
}update_draftReplace draft content
Replace a draft's content and recipients. This is a full replacement: fields you omit are cleared, so read get_draft first and send every field you want to keep. Attachments are unaffected.
| Parameter | Type | Required | Description |
|---|---|---|---|
draft_id | string | yes | Draft ID (starts with dr_), as returned by a list or create tool. (max 128 chars) |
from | string | no | Sender address on a verified domain (may be empty while drafting). |
to | string[] | no | Recipients. (0–100 items) |
cc | string[] | no | Carbon-copy recipients. (0–100 items) |
bcc | string[] | no | Blind-copy recipients. (0–100 items) |
subject | string | no | Subject line. (max 998 chars) |
html | string | no | HTML body. |
text | string | no | Plain-text body. |
reply_to_email_id | string | no | Email ID this draft replies to. |
thread_id | string | no | Thread ID this draft belongs to. |
{
"name": "update_draft",
"arguments": {
"draft_id": "dr_123",
"from": "hello@example.com",
"to": [
"owner@example.com"
],
"subject": "Invoice (updated)",
"text": "Attached."
}
}delete_draftDiscard a draft
DESTRUCTIVE: discard a draft and permanently delete its stored attachments.
| Parameter | Type | Required | Description |
|---|---|---|---|
draft_id | string | yes | Draft ID (starts with dr_), as returned by a list or create tool. (max 128 chars) |
{
"name": "delete_draft",
"arguments": {
"draft_id": "dr_123"
}
}upload_attachmentUpload an attachment to a draft
Upload one file to a draft (max 10 files and 10 MB total per message). Provide content_base64 or a local file_path. Attachments require a paid plan at send time.
Provide at least one of: content_base64, file_path.
| Parameter | Type | Required | Description |
|---|---|---|---|
draft_id | string | yes | Draft ID (starts with dr_), as returned by a list or create tool. (max 128 chars) |
filename | string | no | File name shown to the recipient. Defaults to the basename of file_path. (max 255 chars) |
content_type | string | no | MIME type, e.g. application/pdf. Defaults to application/octet-stream. |
content_base64 | string | no | Standard base64 file content. |
file_path | string | no | Absolute path of a local file readable by the MCP server process. |
{
"name": "upload_attachment",
"arguments": {
"draft_id": "dr_123",
"filename": "invoice.pdf",
"content_type": "application/pdf",
"file_path": "/tmp/invoice.pdf"
}
}download_attachmentDownload an attachment
Download a private attachment (sent, received, or draft). Returns base64 content, or writes the file when save_to_path is set (refuses to overwrite unless overwrite is true).
| Parameter | Type | Required | Description |
|---|---|---|---|
attachment_id | string | yes | Attachment ID (starts with att_), as returned by a list or create tool. (max 128 chars) |
save_to_path | string | no | Optional absolute local path to write the file to instead of returning base64. |
overwrite | boolean | no | Allow replacing an existing file at save_to_path. Defaults to false. |
{
"name": "download_attachment",
"arguments": {
"attachment_id": "att_123",
"save_to_path": "/tmp/invoice.pdf"
}
}delete_attachmentDelete an attachment
DESTRUCTIVE: permanently delete a stored attachment (for example, remove a file from a draft before sending).
| Parameter | Type | Required | Description |
|---|---|---|---|
attachment_id | string | yes | Attachment ID (starts with att_), as returned by a list or create tool. (max 128 chars) |
{
"name": "delete_attachment",
"arguments": {
"attachment_id": "att_123"
}
}Hosted templates
list_templatesList hosted templates
List hosted email templates with publish state and usage. Paginated: the result includes pagination {offset, limit, returned, total?, has_more, next_offset}.
| Parameter | Type | Required | Description |
|---|---|---|---|
lifecycle | string | no | active (default), archived, or all. (one of active, archived, all) |
query | string | no | Search by name or key. (max 120 chars) |
limit | integer | no | Page size. Defaults to 50. (default 50; 1–200) |
offset | integer | no | Number of records to skip. Use pagination.next_offset from the previous page. (default 0; 0–…) |
{
"name": "list_templates",
"arguments": {
"lifecycle": "active"
}
}create_templateCreate a hosted template
Create a template with an editable draft, optionally from a starter (welcome, reset, receipt, or blank). Publish it before sending by key.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | Human name. (max 120 chars) |
key | string | no | Stable send key: lowercase letters, numbers, hyphens; starts with a letter (2–64 chars). Derived from name when omitted. |
starter | string | no | Starter content. (one of blank, welcome, reset, receipt) |
{
"name": "create_template",
"arguments": {
"name": "Account welcome",
"key": "account-welcome",
"starter": "welcome"
}
}get_templateGet a template
Retrieve a template's current draft (with revision), active published release, release history, and usage. Accepts ID or key.
| Parameter | Type | Required | Description |
|---|---|---|---|
template_id | string | yes | Template ID (tmpl_…) or key. (max 128 chars) |
{
"name": "get_template",
"arguments": {
"template_id": "account-welcome"
}
}update_template_draftSave a template draft
Save the template's editable draft using optimistic concurrency: pass the current revision from get_template (409 means someone else saved first; re-read and retry). This is a full replacement of draft content: omitted fields are cleared, so send every field you want to keep. Use {{variable}} placeholders.
| Parameter | Type | Required | Description |
|---|---|---|---|
template_id | string | yes | Template ID or key. (max 128 chars) |
revision | integer | yes | Current draft revision from get_template. (1–…) |
name | string | no | Template name. (max 120 chars) |
subject_template | string | no | Subject with placeholders. (max 998 chars) |
preheader_template | string | no | Preview text. (max 240 chars) |
html_template | string | no | HTML body with placeholders. |
text_template | string | no | Plain-text body with placeholders. |
from | string | no | Default sender for sends of this template. |
reply_to | string | no | Default Reply-To. |
variables | object[] | no | Typed variable contract. Each item: {key (lowercase/underscores), label, type: text|number|url|boolean, required (default true), fallback, description}. |
variables[].key | string | yes | |
variables[].label | string | no | |
variables[].type | string | no | (one of text, number, url, boolean) |
variables[].required | boolean | no | |
variables[].fallback | any | no | |
variables[].description | string | no | |
sample_data | object | no | Sample values used for previews and tests. |
{
"name": "update_template_draft",
"arguments": {
"template_id": "account-welcome",
"revision": 3,
"name": "Account welcome",
"subject_template": "Welcome, {{first_name}}",
"text_template": "Hi {{first_name}}",
"variables": [
{
"key": "first_name",
"type": "text",
"required": true
}
],
"sample_data": {
"first_name": "Asha"
}
}
}create_template_draftStart a new draft from the published release
Create a new editable draft copied from the current published release (409 if a draft already exists or nothing is published).
| Parameter | Type | Required | Description |
|---|---|---|---|
template_id | string | yes | Template ID or key. (max 128 chars) |
{
"name": "create_template_draft",
"arguments": {
"template_id": "account-welcome"
}
}render_templateRender a template preview
Render the exact server output (subject, html, text) for the draft, the published release, or a specific version with the given data. Does not send. Returns 422 with findings when data violates the variable contract.
| Parameter | Type | Required | Description |
|---|---|---|---|
template_id | string | yes | Template ID or key. (max 128 chars) |
version_id | string | no | Optional version ID; defaults to the draft, then the published release. |
data | object | no | Variable values; defaults to the version's sample data. |
{
"name": "render_template",
"arguments": {
"template_id": "account-welcome",
"data": {
"first_name": "Asha"
}
}
}send_template_testSend a template test email
SENDS REAL EMAIL. Send a [Test]-prefixed snapshot of the draft (or a given version) to the given recipients. Counts against usage; trial workspaces can only send to the account email or a mailbox-simulator test address.
| Parameter | Type | Required | Description |
|---|---|---|---|
template_id | string | yes | Template ID or key. (max 128 chars) |
to | string[] | yes | Test recipients. (1–100 items) |
from | string | no | Sender on a verified domain; defaults to the template's From. |
version_id | string | no | Optional version ID. |
data | object | no | Variable values; defaults to sample data. |
{
"name": "send_template_test",
"arguments": {
"template_id": "account-welcome",
"to": [
"owner@example.com"
]
}
}publish_templatePublish a template release
Publish the current draft as an immutable release that send_email with template.key will use. Fails with 422 findings on validation errors, or 409 if it would break the live variable contract of a template already used in production.
| Parameter | Type | Required | Description |
|---|---|---|---|
template_id | string | yes | Template ID or key. (max 128 chars) |
{
"name": "publish_template",
"arguments": {
"template_id": "account-welcome"
}
}archive_templateArchive a template
Stop new sends that use this template (history is kept; reversible with restore_template). Any integration sending this key will start failing with 404.
| Parameter | Type | Required | Description |
|---|---|---|---|
template_id | string | yes | Template ID or key. (max 128 chars) |
{
"name": "archive_template",
"arguments": {
"template_id": "account-welcome"
}
}restore_templateRestore an archived template
Make an archived template active again.
| Parameter | Type | Required | Description |
|---|---|---|---|
template_id | string | yes | Template ID or key. (max 128 chars) |
{
"name": "restore_template",
"arguments": {
"template_id": "account-welcome"
}
}Domains and DNS
list_domainsList domains
List sending domains with aggregate setup_status (verified | checking | pending), per-record DNS state, and inbound status. Can be slow: unverified domains are re-checked live. Paginated: the result includes pagination {offset, limit, returned, total?, has_more, next_offset}.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Page size. Defaults to 50. (default 50; 1–200) |
offset | integer | no | Number of records to skip. Use pagination.next_offset from the previous page. (default 0; 0–…) |
{
"name": "list_domains",
"arguments": {}
}get_domainGet domain setup details
Retrieve one domain with the exact DNS records to publish (type, name, value), each record's live state from two public resolvers, dns_issues with fixes, and inbound status.
| Parameter | Type | Required | Description |
|---|---|---|---|
domain_id | string | yes | Domain ID (starts with dom_), as returned by a list or create tool. (max 128 chars) |
{
"name": "get_domain",
"arguments": {
"domain_id": "dom_123"
}
}add_domainAdd a sending domain
Register a domain you control for sending. Returns the DNS records (Easy DKIM CNAMEs) the owner must publish. Does not change DNS itself. Counts against the plan's domain limit.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | Bare domain name, e.g. example.com or mail.example.com. (max 253 chars) |
default_from | string | no | Optional default sender address on this domain. |
{
"name": "add_domain",
"arguments": {
"name": "example.com"
}
}verify_domainVerify a domain
Run a live DNS and DKIM verification check now. Safe to repeat; poll every 30–60 s after DNS changes (propagation can take minutes to hours). Sending is allowed once status is verified.
| Parameter | Type | Required | Description |
|---|---|---|---|
domain_id | string | yes | Domain ID (starts with dom_), as returned by a list or create tool. (max 128 chars) |
{
"name": "verify_domain",
"arguments": {
"domain_id": "dom_123"
}
}delete_domainDelete a domain
DESTRUCTIVE: remove the domain from the workspace, including its inbound receiving route. Sends from it fail immediately afterwards. It does not delete DNS records at your DNS provider.
| Parameter | Type | Required | Description |
|---|---|---|---|
domain_id | string | yes | Domain ID (starts with dom_), as returned by a list or create tool. (max 128 chars) |
{
"name": "delete_domain",
"arguments": {
"domain_id": "dom_123"
}
}get_dns_providerDetect DNS provider and record hosts
Detect the domain's authoritative DNS provider and return the relative host to type into that provider for each record, the recommended DMARC record, inbound MX guidance, and whether one-click setup (Domain Connect) is available.
| Parameter | Type | Required | Description |
|---|---|---|---|
domain_id | string | yes | Domain ID (starts with dom_), as returned by a list or create tool. (max 128 chars) |
{
"name": "get_dns_provider",
"arguments": {
"domain_id": "dom_123"
}
}get_domain_connect_linkGet a one-click DNS setup link
When get_dns_provider reports providers.domainConnect.available, create a signed consent URL. Give it to the human: they open it and approve the DNS change at their provider. Nothing changes until they approve. 409 if unsupported.
| Parameter | Type | Required | Description |
|---|---|---|---|
domain_id | string | yes | Domain ID (starts with dom_), as returned by a list or create tool. (max 128 chars) |
{
"name": "get_domain_connect_link",
"arguments": {
"domain_id": "dom_123"
}
}Inbound email
setup_inboundEnable inbound receiving for a domain
Provision inbound receiving for a verified domain. Uses the root domain when it has no conflicting MX, otherwise inbound.<domain>. Returns the MX record the owner must publish; it does not edit DNS.
| Parameter | Type | Required | Description |
|---|---|---|---|
domain_id | string | yes | Domain ID (starts with dom_), as returned by a list or create tool. (max 128 chars) |
{
"name": "setup_inbound",
"arguments": {
"domain_id": "dom_123"
}
}verify_inboundVerify inbound MX
Re-check the inbound MX record. Status becomes ready when both public resolvers see it.
| Parameter | Type | Required | Description |
|---|---|---|---|
domain_id | string | yes | Domain ID (starts with dom_), as returned by a list or create tool. (max 128 chars) |
{
"name": "verify_inbound",
"arguments": {
"domain_id": "dom_123"
}
}list_inboxesList inbound addresses
List receiving addresses, optionally for one domain. Paginated: the result includes pagination {offset, limit, returned, total?, has_more, next_offset}.
| Parameter | Type | Required | Description |
|---|---|---|---|
domain_id | string | no | Optional domain ID filter. |
limit | integer | no | Page size. Defaults to 50. (default 50; 1–200) |
offset | integer | no | Number of records to skip. Use pagination.next_offset from the previous page. (default 0; 0–…) |
{
"name": "list_inboxes",
"arguments": {
"domain_id": "dom_123"
}
}get_inboxGet an inbox
Retrieve one inbound address.
| Parameter | Type | Required | Description |
|---|---|---|---|
inbox_id | string | yes | Inbox ID (starts with inb_), as returned by a list or create tool. (max 128 chars) |
{
"name": "get_inbox",
"arguments": {
"inbox_id": "inb_123"
}
}create_inboxCreate an inbound address
Create an address such as support@<receiving domain> on a domain whose inbound status is ready (run setup_inbound and verify_inbound first). Received mail appears in list_emails with direction in.
| Parameter | Type | Required | Description |
|---|---|---|---|
domain_id | string | yes | Domain ID (starts with dom_), as returned by a list or create tool. (max 128 chars) |
local_part | string | yes | Part before @, e.g. support. (max 64 chars) |
name | string | no | Optional display name. |
{
"name": "create_inbox",
"arguments": {
"domain_id": "dom_123",
"local_part": "support",
"name": "Support"
}
}update_inboxRename, enable, or disable an inbox
Rename an inbox or set its status to active / disabled.
| Parameter | Type | Required | Description |
|---|---|---|---|
inbox_id | string | yes | Inbox ID (starts with inb_), as returned by a list or create tool. (max 128 chars) |
name | string | no | New display name. |
status | string | no | New status. (one of active, disabled) |
{
"name": "update_inbox",
"arguments": {
"inbox_id": "inb_123",
"status": "disabled"
}
}set_inbox_forwardingForward an inbox to another address
SENDS REAL EMAIL when forwarding to someone other than the account owner: sets where an inbox's received mail is forwarded. The owner's own address activates immediately; any other address gets a confirmation email and forwarding stays pending until someone there confirms. Pass forward_to: null to turn forwarding off. Forwarded copies come from the inbox address with the original sender as Reply-To.
| Parameter | Type | Required | Description |
|---|---|---|---|
inbox_id | string | yes | Inbox ID (starts with inb_), as returned by a list or create tool. (max 128 chars) |
forward_to | string,null | yes | Forwarding target email address, or null to turn forwarding off. (max 254 chars) |
{
"name": "set_inbox_forwarding",
"arguments": {
"inbox_id": "inb_123",
"forward_to": "team@example.net"
}
}delete_inboxDelete an inbox
DESTRUCTIVE: delete an inbound address. Mail already received is retained; new mail to the address is no longer filed to it.
| Parameter | Type | Required | Description |
|---|---|---|---|
inbox_id | string | yes | Inbox ID (starts with inb_), as returned by a list or create tool. (max 128 chars) |
{
"name": "delete_inbox",
"arguments": {
"inbox_id": "inb_123"
}
}Deliverability, bounces, and suppressions
deliverability_statsGet 30-day delivery stats
Workspace-wide 30-day totals: sent, delivery, bounce, complaint, reject, open, click, and deliveryRate (%).
No parameters.
{
"name": "deliverability_stats",
"arguments": {}
}list_sender_reputationList sender reputation
Reputation state per exact From address: active, throttled (lower daily limit), or paused (sends return 423), with the reason and daily limit. Check this when sends fail with 423 or 429. Paginated: the result includes pagination {offset, limit, returned, total?, has_more, next_offset}.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Page size. Defaults to 50. (default 50; 1–200) |
offset | integer | no | Number of records to skip. Use pagination.next_offset from the previous page. (default 0; 0–…) |
{
"name": "list_sender_reputation",
"arguments": {}
}list_suppressionsList suppressions
Workspace suppression list: recipients blocked after a permanent bounce or a spam complaint. Sends to them fail with 422. Paginated: the result includes pagination {offset, limit, returned, total?, has_more, next_offset}.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Page size. Defaults to 50. (default 50; 1–200) |
offset | integer | no | Number of records to skip. Use pagination.next_offset from the previous page. (default 0; 0–…) |
{
"name": "list_suppressions",
"arguments": {}
}remove_suppressionRemove a bounce suppression
DESTRUCTIVE (weakens a safety block): remove a bounce suppression so the address can be mailed again. Only do this when the human confirms the address is now valid. Complaint suppressions cannot be removed (409).
| Parameter | Type | Required | Description |
|---|---|---|---|
email | string | yes | Suppressed recipient address. (max 320 chars) |
{
"name": "remove_suppression",
"arguments": {
"email": "fixed-mailbox@example.net"
}
}list_blocked_recipientsList blocked recipients
Every recipient SendHQ will refuse: bounces, complaints, and domain-scoped marketing unsubscribes, with a summary by kind. Reads up to the newest 500. Paginated: the result includes pagination {offset, limit, returned, total?, has_more, next_offset}.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Page size. Defaults to 50. (default 50; 1–200) |
offset | integer | no | Number of records to skip. Use pagination.next_offset from the previous page. (default 0; 0–…) |
{
"name": "list_blocked_recipients",
"arguments": {}
}Campaigns
list_contactsList contacts
Campaigns: Newest-first contacts with their status, consent evidence, tags, custom fields, engagement (bot-filtered, estimated opens and clicks), and the lists they are subscribed to. Filter by list, status (including dormant), email/name substring (* is an email wildcard, e.g. *@example.com), tag, source, signup date, or a saved segment. Paginated: the result includes pagination {offset, limit, returned, has_more, next_offset}.
| Parameter | Type | Required | Description |
|---|---|---|---|
list_id | string | no | Only members of this list (lst_…). |
status | string | no | Only contacts with this status. dormant: stopped opening and did not answer list cleanup, so broadcasts skip them. (one of subscribed, pending, unsubscribed, bounced, complained, dormant) |
query | string | no | Email or name contains this text; * is a wildcard on the email. (max 200 chars) |
tag | string | no | Only contacts with this tag. (max 60 chars) |
source | string | no | How the contact was added. (one of form, api, import) |
after | string | no | Signed up at or after this time. ISO 8601 date or date-time. (max 40 chars) |
before | string | no | Signed up before this time. ISO 8601 date or date-time. (max 40 chars) |
segment_id | string | no | Only contacts matching this saved segment (seg_…). |
limit | integer | no | Page size. Defaults to 50. (default 50; 1–200) |
offset | integer | no | Number of records to skip. Use pagination.next_offset from the previous page. (default 0; 0–…) |
{
"name": "list_contacts",
"arguments": {
"status": "subscribed"
}
}create_contactAdd a contact
Campaigns: Add one contact to the workspace, optionally to lists and with tags. A subscribed contact (the default) needs consent_basis: how this person agreed to receive your email. Only add people who asked for your email. Use status: unsubscribed to record an opt-out. Addresses already suppressed or unsubscribed from the workspace are stored with that status. Fails with 409 if the email already exists.
| Parameter | Type | Required | Description |
|---|---|---|---|
email | string | yes | Email address. (max 320 chars) |
name | string | no | Display name; the first word is {{first_name}}. (max 200 chars) |
fields | object | no | Custom fields (string, number, or boolean values), e.g. {"plan": "pro"}. Usable in broadcasts as {{fields.plan}} or {{plan}}. On update, a null value removes the field. |
tags | string[] | no | Tags to attach, e.g. ["VIP"]. Up to 50. (0–50 items) |
list_ids | string[] | no | Lists to add the contact to (lst_…). (0–100 items) |
status | string | no | subscribed (default, requires consent_basis) or unsubscribed. (one of subscribed, unsubscribed) |
consent_basis | string | no | How these people agreed to receive your email, recorded as consent evidence, e.g. Signed up on acme.com/newsletter (double opt-in), exported from Mailchimp. 10-500 characters. (max 500 chars) |
{
"name": "create_contact",
"arguments": {
"email": "ada@example.net",
"name": "Ada Lovelace",
"consent_basis": "Ticked the newsletter box at checkout on acme.com"
}
}get_contactGet a contact
Campaigns: One contact by ID or email: status, consent evidence (source, basis, opt-in and confirmation times), custom fields, and subscribed lists.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id | string | yes | Contact ID (starts with ct_) or the contact's email address. (max 320 chars) |
{
"name": "get_contact",
"arguments": {
"contact_id": "ct_123"
}
}update_contactUpdate, tag, or unsubscribe a contact
Campaigns: Change a contact's name, merge custom fields (null removes one), change tags (tags replaces the whole set; add_tags and remove_tags change it), or set status to unsubscribed. A contact can never be resubscribed through the API; only the recipient can, by confirming a signup form.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id | string | yes | Contact ID (starts with ct_) or the contact's email address. (max 320 chars) |
name | string | no | New display name. (max 200 chars) |
fields | object | no | Custom fields (string, number, or boolean values), e.g. {"plan": "pro"}. Usable in broadcasts as {{fields.plan}} or {{plan}}. On update, a null value removes the field. |
tags | string[] | no | Replace all of the contact's tags with these. (0–50 items) |
add_tags | string[] | no | Tags to add. (0–50 items) |
remove_tags | string[] | no | Tags to remove. (0–50 items) |
status | string | no | Only unsubscribed is accepted. (one of unsubscribed) |
{
"name": "update_contact",
"arguments": {
"contact_id": "ct_123",
"fields": {
"plan": "team"
}
}
}delete_contactDelete a contact
DESTRUCTIVE: permanently delete a contact and its list memberships. Suppressions and recorded unsubscribes are kept, so the address still cannot be mailed if it is added again. Part of Campaigns.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_id | string | yes | Contact ID (starts with ct_) or the contact's email address. (max 320 chars) |
{
"name": "delete_contact",
"arguments": {
"contact_id": "ct_123"
}
}import_contactsImport contacts from CSV
Campaigns: Import up to 5,000 CSV rows (5 MB) per call, optionally into a list. The header row needs an email column; name (or first_name/last_name), status, and tags (separated by ; or |) are recognized and other columns become custom fields. consent_basis is required and recorded on every new contact. tags are added to every accepted row. Rows whose status says unsubscribed import as unsubscribed; existing contacts keep their status. Returns counts of accepted, created, existing, skipped (duplicates), and invalid rows with line numbers.
| Parameter | Type | Required | Description |
|---|---|---|---|
csv | string | yes | The CSV file content, header row first. (max 5242880 chars) |
list_id | string | no | Add every accepted row to this list (lst_…). |
consent_basis | string | yes | How these people agreed to receive your email, recorded as consent evidence, e.g. Signed up on acme.com/newsletter (double opt-in), exported from Mailchimp. 10-500 characters. (max 500 chars) |
tags | string[] | no | Tags to add to every accepted row, e.g. ["imported-2026-11"]. (0–50 items) |
update_existing | boolean | no | Merge names and fields into contacts that already exist. Their status never changes. |
{
"name": "import_contacts",
"arguments": {
"csv": "email,name\nada@example.net,Ada Lovelace\n",
"consent_basis": "Newsletter signups exported from Mailchimp (double opt-in)"
}
}list_contact_importsList contact imports
Campaigns: Recent CSV imports with their consent basis and row counts.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Page size. Defaults to 50. (default 50; 1–200) |
offset | integer | no | Number of records to skip. Use pagination.next_offset from the previous page. (default 0; 0–…) |
{
"name": "list_contact_imports",
"arguments": {}
}bulk_update_contactsBulk update contacts
DESTRUCTIVE when action is delete (permanent) or unsubscribe (cannot be undone through the API). Apply one action to up to 10,000 contacts: the IDs in contact_ids, or everyone matching filter (the list_contacts filters). Actions: tag / untag (with tags), add_to_list / remove_from_list (with list_id), unsubscribe, delete. Adding to a list mirrors each contact's status, so nobody is resubscribed. delete needs confirm_count equal to the number of contacts it would delete: call once without it to get that count (409), confirm with the user, then repeat with it. Part of Campaigns.
| Parameter | Type | Required | Description |
|---|---|---|---|
action | string | yes | What to do. (one of tag, untag, add_to_list, remove_from_list, unsubscribe, delete) |
contact_ids | string[] | no | Contact IDs (ct_…). Use this or filter. (0–10000 items) |
filter | object | no | Everyone matching these list_contacts filters. Use this or contact_ids. |
filter.list_id | string | no | Members of this list. |
filter.status | string | no | Only contacts with this status. dormant: stopped opening and did not answer list cleanup, so broadcasts skip them. (one of subscribed, pending, unsubscribed, bounced, complained, dormant) |
filter.query | string | no | Email or name contains; * is an email wildcard. (max 200 chars) |
filter.tag | string | no | Has this tag. (max 60 chars) |
filter.source | string | no | How the contact was added. (one of form, api, import) |
filter.after | string | no | Signed up at or after. ISO 8601 date or date-time. (max 40 chars) |
filter.before | string | no | Signed up before. ISO 8601 date or date-time. (max 40 chars) |
filter.segment_id | string | no | Matches this segment. |
tags | string[] | no | For tag and untag. (0–50 items) |
list_id | string | no | For add_to_list and remove_from_list (lst_…). |
confirm_count | integer | no | For delete: the number of contacts that will be deleted, from the 409 response. (0–…) |
{
"name": "bulk_update_contacts",
"arguments": {
"action": "tag",
"tags": [
"october"
],
"filter": {
"list_id": "lst_123",
"status": "subscribed"
}
}
}list_tagsList contact tags
Campaigns: The workspace's contact tags, most used first, with how many contacts carry each (up to 500). Paginated.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Page size. Defaults to 50. (default 50; 1–200) |
offset | integer | no | Number of records to skip. Use pagination.next_offset from the previous page. (default 0; 0–…) |
{
"name": "list_tags",
"arguments": {}
}list_contact_fieldsList custom contact fields
Campaigns: Defined custom contact fields with label and type (text, number, date). Defined fields are known to check_broadcast's merge-tag check and typed in segment conditions. Contacts may also carry undefined keys. Paginated.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Page size. Defaults to 50. (default 50; 1–200) |
offset | integer | no | Number of records to skip. Use pagination.next_offset from the previous page. (default 0; 0–…) |
{
"name": "list_contact_fields",
"arguments": {}
}set_contact_fieldDefine a custom contact field
Campaigns: Create or change a custom field definition (label and type). Values live on each contact's fields. Built-in merge tags (email, name, first_name, last_name, fields, unsubscribe_url) cannot be used as keys. A workspace may define 50 fields.
| Parameter | Type | Required | Description |
|---|---|---|---|
field_key | string | yes | Field key: a lowercase letter, then up to 39 of a-z, 0-9, and _ (e.g. plan). Used in content as {{fields.plan}}. (max 40 chars) |
label | string | no | Label shown in the dashboard. Defaults to the key. (max 80 chars) |
type | string | no | text (default), number, or date. (one of text, number, date) |
{
"name": "set_contact_field",
"arguments": {
"field_key": "plan",
"label": "Plan",
"type": "text"
}
}delete_contact_fieldDelete a custom field definition
DESTRUCTIVE: delete a custom field definition. Values already stored on contacts are kept. Fails with 404 when the field is not defined. Part of Campaigns.
| Parameter | Type | Required | Description |
|---|---|---|---|
field_key | string | yes | Field key: a lowercase letter, then up to 39 of a-z, 0-9, and _ (e.g. plan). Used in content as {{fields.plan}}. (max 40 chars) |
{
"name": "delete_contact_field",
"arguments": {
"field_key": "plan"
}
}list_segmentsList segments
Campaigns: Saved segments with count (subscribed, non-dormant contacts in the workspace that match now) and fields, the condition fields and operators each accepts. Paginated.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Page size. Defaults to 50. (default 50; 1–200) |
offset | integer | no | Number of records to skip. Use pagination.next_offset from the previous page. (default 0; 0–…) |
{
"name": "list_segments",
"arguments": {}
}get_segmentGet a segment
Campaigns: One segment with its conditions and live count.
| Parameter | Type | Required | Description |
|---|---|---|---|
segment_id | string | yes | Segment ID (starts with seg_), as returned by list_segments or create_segment. (max 128 chars) |
{
"name": "get_segment",
"arguments": {
"segment_id": "seg_123"
}
}preview_segmentPreview segment conditions
Campaigns: Count and sample (up to 5) the subscribed, non-dormant contacts that match conditions without saving them. Read-only; use it to check conditions before create_segment.
| Parameter | Type | Required | Description |
|---|---|---|---|
match | string | no | all (default): every condition must match. any: at least one. (one of all, any) |
conditions | object[] | no | Up to 20 conditions. Opened and clicked use bot-filtered, estimated engagement. (0–20 items) |
conditions[].field | string | yes | tag, list, status, source, email, name, signed_up, opened, clicked, opens, sends_without_open, country, or a custom field as fields.<key> (or field together with key). (max 48 chars) |
conditions[].key | string | no | Custom field key when field is field. (max 40 chars) |
conditions[].op | string | yes | tag: has, not_has. list: in, not_in (a list ID). status: is, is_not (subscribed, pending, unsubscribed, bounced, complained, dormant). source: is, is_not (form, api, import). email: contains, not_contains, ends_with. name: is_set, not_set. signed_up: before, after (YYYY-MM-DD), in_last_days, not_in_last_days. opened / clicked: in_last_days, not_in_last_days, ever, never. opens / sends_without_open: gte, lte. country: is, is_not (two-letter code). Custom fields: equals, not_equals, contains, gt, lt, before, after, is_set, not_set. (one of has, not_has, in, not_in, is, is_not, contains, not_contains, ends_with, is_set, not_set, before, after, in_last_days, not_in_last_days, ever, never, gte, lte, equals, not_equals, gt, lt) |
conditions[].value | string,number | no | Required except for is_set, not_set, ever, and never. A number of days for in_last_days / not_in_last_days, a number for gte, lte, gt, lt, a date (YYYY-MM-DD) for before / after. |
{
"name": "preview_segment",
"arguments": {
"match": "all",
"conditions": [
{
"field": "tag",
"op": "has",
"value": "VIP"
},
{
"field": "opened",
"op": "in_last_days",
"value": 90
}
]
}
}create_segmentCreate a segment
Campaigns: Save a segment: up to 20 conditions joined by match. Use it as a broadcast's audience.segment_id, as list_contacts segment_id, or in bulk_update_contacts filters. A workspace may have 200 segments.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | Segment name. (max 120 chars) |
match | string | no | all (default): every condition must match. any: at least one. (one of all, any) |
conditions | object[] | no | Up to 20 conditions. Opened and clicked use bot-filtered, estimated engagement. (0–20 items) |
conditions[].field | string | yes | tag, list, status, source, email, name, signed_up, opened, clicked, opens, sends_without_open, country, or a custom field as fields.<key> (or field together with key). (max 48 chars) |
conditions[].key | string | no | Custom field key when field is field. (max 40 chars) |
conditions[].op | string | yes | tag: has, not_has. list: in, not_in (a list ID). status: is, is_not (subscribed, pending, unsubscribed, bounced, complained, dormant). source: is, is_not (form, api, import). email: contains, not_contains, ends_with. name: is_set, not_set. signed_up: before, after (YYYY-MM-DD), in_last_days, not_in_last_days. opened / clicked: in_last_days, not_in_last_days, ever, never. opens / sends_without_open: gte, lte. country: is, is_not (two-letter code). Custom fields: equals, not_equals, contains, gt, lt, before, after, is_set, not_set. (one of has, not_has, in, not_in, is, is_not, contains, not_contains, ends_with, is_set, not_set, before, after, in_last_days, not_in_last_days, ever, never, gte, lte, equals, not_equals, gt, lt) |
conditions[].value | string,number | no | Required except for is_set, not_set, ever, and never. A number of days for in_last_days / not_in_last_days, a number for gte, lte, gt, lt, a date (YYYY-MM-DD) for before / after. |
{
"name": "create_segment",
"arguments": {
"name": "Engaged pro customers",
"conditions": [
{
"field": "fields.plan",
"op": "equals",
"value": "pro"
},
{
"field": "opened",
"op": "in_last_days",
"value": 90
}
]
}
}update_segmentUpdate a segment
Campaigns: Rename a segment or change match or conditions (which replaces the whole list). Broadcasts using it pick up the change when they start sending.
| Parameter | Type | Required | Description |
|---|---|---|---|
segment_id | string | yes | Segment ID (starts with seg_), as returned by list_segments or create_segment. (max 128 chars) |
name | string | no | Segment name. (max 120 chars) |
match | string | no | all (default): every condition must match. any: at least one. (one of all, any) |
conditions | object[] | no | Up to 20 conditions. Opened and clicked use bot-filtered, estimated engagement. (0–20 items) |
conditions[].field | string | yes | tag, list, status, source, email, name, signed_up, opened, clicked, opens, sends_without_open, country, or a custom field as fields.<key> (or field together with key). (max 48 chars) |
conditions[].key | string | no | Custom field key when field is field. (max 40 chars) |
conditions[].op | string | yes | tag: has, not_has. list: in, not_in (a list ID). status: is, is_not (subscribed, pending, unsubscribed, bounced, complained, dormant). source: is, is_not (form, api, import). email: contains, not_contains, ends_with. name: is_set, not_set. signed_up: before, after (YYYY-MM-DD), in_last_days, not_in_last_days. opened / clicked: in_last_days, not_in_last_days, ever, never. opens / sends_without_open: gte, lte. country: is, is_not (two-letter code). Custom fields: equals, not_equals, contains, gt, lt, before, after, is_set, not_set. (one of has, not_has, in, not_in, is, is_not, contains, not_contains, ends_with, is_set, not_set, before, after, in_last_days, not_in_last_days, ever, never, gte, lte, equals, not_equals, gt, lt) |
conditions[].value | string,number | no | Required except for is_set, not_set, ever, and never. A number of days for in_last_days / not_in_last_days, a number for gte, lte, gt, lt, a date (YYYY-MM-DD) for before / after. |
{
"name": "update_segment",
"arguments": {
"segment_id": "seg_123",
"name": "Pro customers"
}
}delete_segmentDelete a segment
DESTRUCTIVE: delete a saved segment. Contacts are not affected. Fails with 409 while a sending or scheduled broadcast uses it. Part of Campaigns.
| Parameter | Type | Required | Description |
|---|---|---|---|
segment_id | string | yes | Segment ID (starts with seg_), as returned by list_segments or create_segment. (max 128 chars) |
{
"name": "delete_segment",
"arguments": {
"segment_id": "seg_123"
}
}list_listsList contact lists
Campaigns: Contact lists with subscribed, pending, and unsubscribed counts and their hosted signup form settings.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Page size. Defaults to 50. (default 50; 1–200) |
offset | integer | no | Number of records to skip. Use pagination.next_offset from the previous page. (default 0; 0–…) |
{
"name": "list_lists",
"arguments": {}
}create_listCreate a list
Campaigns: Create a contact list. Set form_enabled to publish a hosted double opt-in signup form (its URL is in form.url); form_from is the sender, on a verified domain, of the confirmation email, and redirect_url is an optional https page shown after confirming. welcome sends one welcome email when someone confirms. site publishes a public newsletter site at https://sendhq.cc/n/<slug> with the signup form, sent issues, and RSS.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | List name, shown on the signup form. (max 200 chars) |
description | string | no | Short description shown on the signup form. (max 500 chars) |
form_enabled | boolean | no | Publish the hosted signup form. |
form_from | string | no | Sender of signup confirmation emails, e.g. Acme <news@example.com>, on a verified domain. (max 320 chars) |
redirect_url | string | no | https page to open after a signup is confirmed. (max 500 chars) |
welcome | object | no | Welcome email sent once, from form_from, when someone confirms a signup. Needs subject and html or markdown when enabled. |
welcome.enabled | boolean | no | Send it. |
welcome.subject | string | no | Subject; merge tags allowed. (max 300 chars) |
welcome.html | string | no | HTML body; merge tags allowed. (max 500000 chars) |
welcome.markdown | string | no | Markdown body instead of html, rendered with the workspace design. (max 200000 chars) |
site | object | no | Public newsletter site at https://sendhq.cc/n/<slug>: a signup form, every issue sent with web.enabled, and an RSS feed at /n/<slug>/feed.xml. Needs the signup form on. A reserved or taken slug fails with 409. |
site.enabled | boolean | no | Publish the site. |
site.slug | string,null | no | Address after /n/ (3-48 characters, a-z, 0-9, -). Defaults to the list name. (max 48 chars) |
site.title | string,null | no | Site title. Defaults to the list name. (max 120 chars) |
site.description | string,null | no | Short description under the title. (max 300 chars) |
site.accent | string,null | no | Accent color, e.g. #0a7d4f. |
{
"name": "create_list",
"arguments": {
"name": "Product updates"
}
}get_listGet a list
Campaigns: One list with member counts and signup form settings.
| Parameter | Type | Required | Description |
|---|---|---|---|
list_id | string | yes | List ID (starts with lst_), as returned by a list or create tool. (max 128 chars) |
{
"name": "get_list",
"arguments": {
"list_id": "lst_123"
}
}update_listUpdate a list
Campaigns: Rename a list, change its description, or change its hosted signup form, welcome email, or newsletter site. Omitted keys inside welcome and site keep their value.
| Parameter | Type | Required | Description |
|---|---|---|---|
list_id | string | yes | List ID (starts with lst_), as returned by a list or create tool. (max 128 chars) |
name | string | no | New name. (max 200 chars) |
description | string | no | New description. (max 500 chars) |
form_enabled | boolean | no | Publish or unpublish the hosted signup form. |
form_from | string | no | Sender of signup confirmation emails, on a verified domain. (max 320 chars) |
redirect_url | string | no | https page to open after a signup is confirmed. (max 500 chars) |
welcome | object | no | Welcome email sent once, from form_from, when someone confirms a signup. Needs subject and html or markdown when enabled. |
welcome.enabled | boolean | no | Send it. |
welcome.subject | string | no | Subject; merge tags allowed. (max 300 chars) |
welcome.html | string | no | HTML body; merge tags allowed. (max 500000 chars) |
welcome.markdown | string | no | Markdown body instead of html, rendered with the workspace design. (max 200000 chars) |
site | object | no | Public newsletter site at https://sendhq.cc/n/<slug>: a signup form, every issue sent with web.enabled, and an RSS feed at /n/<slug>/feed.xml. Needs the signup form on. A reserved or taken slug fails with 409. |
site.enabled | boolean | no | Publish the site. |
site.slug | string,null | no | Address after /n/ (3-48 characters, a-z, 0-9, -). Defaults to the list name. (max 48 chars) |
site.title | string,null | no | Site title. Defaults to the list name. (max 120 chars) |
site.description | string,null | no | Short description under the title. (max 300 chars) |
site.accent | string,null | no | Accent color, e.g. #0a7d4f. |
{
"name": "update_list",
"arguments": {
"list_id": "lst_123",
"form_enabled": false
}
}delete_listDelete a list
DESTRUCTIVE: delete a list and its memberships. The contacts themselves are kept. Fails with 409 while a broadcast to the list is sending. Part of Campaigns.
| Parameter | Type | Required | Description |
|---|---|---|---|
list_id | string | yes | List ID (starts with lst_), as returned by a list or create tool. (max 128 chars) |
{
"name": "delete_list",
"arguments": {
"list_id": "lst_123"
}
}add_list_contactsAdd contacts to a list
Campaigns: Add up to 500 existing contacts to a list by ID or email. Subscribed contacts join subscribed; others join in their current state. Unknown references come back in notFound.
| Parameter | Type | Required | Description |
|---|---|---|---|
list_id | string | yes | List ID (starts with lst_), as returned by a list or create tool. (max 128 chars) |
contact_ids | string[] | no | Contact IDs (ct_…). (0–500 items) |
emails | string[] | no | Contact email addresses. (0–500 items) |
{
"name": "add_list_contacts",
"arguments": {
"list_id": "lst_123",
"emails": [
"ada@example.net"
]
}
}remove_list_contactRemove a contact from a list
DESTRUCTIVE: remove one contact from a list. The contact and its status are kept. Part of Campaigns.
| Parameter | Type | Required | Description |
|---|---|---|---|
list_id | string | yes | List ID (starts with lst_), as returned by a list or create tool. (max 128 chars) |
contact_id | string | yes | Contact ID (starts with ct_) or the contact's email address. (max 320 chars) |
{
"name": "remove_list_contact",
"arguments": {
"list_id": "lst_123",
"contact_id": "ct_123"
}
}list_broadcastsList broadcasts
Campaigns: Newest-first broadcasts with their status. Paginated: the result includes pagination.
| Parameter | Type | Required | Description |
|---|---|---|---|
status | string | no | Only broadcasts with this status. (one of draft, sending, sent, cancelled, failed) |
limit | integer | no | Page size. Defaults to 50. (default 50; 1–200) |
offset | integer | no | Number of records to skip. Use pagination.next_offset from the previous page. (default 0; 0–…) |
{
"name": "list_broadcasts",
"arguments": {}
}create_broadcastCreate a broadcast draft
Campaigns: Create a draft broadcast to one list. Content is html and/or text, markdown, a block document in content, or a published hosted template. Raw content supports merge tags: {{first_name}}, {{name}}, {{email}}, {{fields.plan}}, with fallbacks like {{first_name | there}}. audience narrows it to a segment and/or a number of people (most engaged first with order: engaged); tracking turns open and click tracking off; web publishes it on the list's newsletter site once sent. Nothing is sent until send_broadcast or schedule_broadcast; run check_broadcast and send_broadcast_test first.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | no | Internal name. Defaults to the subject. (max 200 chars) |
list_id | string | no | The list to send to (lst_…). |
from | string | no | Sender on a verified domain, e.g. Acme <news@example.com>. (max 320 chars) |
reply_to | string | no | Reply-To address. (max 320 chars) |
subject | string | no | Subject line; merge tags allowed. (max 998 chars) |
preview_text | string | no | Inbox preview text shown after the subject. (max 300 chars) |
html | string | no | HTML body; merge tags allowed and escaped. |
text | string | no | Plain-text body; merge tags allowed. |
template | object | no | A published hosted template instead of subject/html/text. Template variables are filled from the contact's name, email, and fields. |
template.id | string | no | |
template.key | string | no | |
template.version_id | string | no | |
markdown | string | no | Markdown body instead of html: converted to a block document and rendered with the workspace design (get_newsletter_settings). Merge tags allowed. Not combinable with html or content. (max 1000000 chars) |
content | object,null | no | Block document ({"type": "doc", "content": [...]}, the dashboard editor's format: paragraph, heading, bulletList, orderedList, blockquote, image, button, callout, horizontalRule, codeBlock). Rendered with the workspace design unless html is also sent. |
editor | string | no | Which editor the dashboard opens: html or blocks. Set automatically for markdown and content. (one of html, blocks) |
audience | object | no | Who gets it, within the list. Subscribed, non-dormant members only. |
audience.segment_id | string,null | no | Only list members matching this segment (seg_…), or null for the whole list. |
audience.order | string | no | With a limit: signup (default) or engaged (most engaged first). (one of signup, engaged) |
audience.limit | integer,null | no | Send to at most this many people (send_broadcast_to_more sends to more later), or null for everyone. (1–10000000) |
tracking | object | no | Open and click tracking, both on by default. Opens are estimates (bot-filtered pixel loads), never proof that someone read the email. |
tracking.opens | boolean | no | Add an open pixel. |
tracking.clicks | boolean | no | Rewrite links to signed SendHQ redirects (the unsubscribe link never is). |
web | object | no | Publish this issue on the list's newsletter site (see create_list site) once it is sent. |
web.enabled | boolean | no | Publish it. |
web.slug | string,null | no | Issue address; defaults to the subject. (max 100 chars) |
web.pinned | boolean | no | Keep it at the top of the site. |
{
"name": "create_broadcast",
"arguments": {
"list_id": "lst_123",
"from": "news@example.com",
"subject": "October update",
"markdown": "Hi {{first_name | there}}, here's what shipped.",
"audience": {
"order": "engaged",
"limit": 1000
}
}
}get_broadcastGet a broadcast
Campaigns: One broadcast with its content and, once sending, recipient totals (queued, sending, sent, held, skipped, failed). While a limit or reputation pause holds delivery, lastError explains why and nextAttemptAt says when it resumes.
| Parameter | Type | Required | Description |
|---|---|---|---|
broadcast_id | string | yes | Broadcast ID (starts with bc_), as returned by a list or create tool. (max 128 chars) |
{
"name": "get_broadcast",
"arguments": {
"broadcast_id": "bc_123"
}
}update_broadcastEdit a draft broadcast
Campaigns: Change any field of an unscheduled draft broadcast (content, audience, tracking, web). A scheduled draft must be unscheduled first (unschedule_broadcast); sent or sending broadcasts cannot be edited.
| Parameter | Type | Required | Description |
|---|---|---|---|
broadcast_id | string | yes | Broadcast ID (starts with bc_), as returned by a list or create tool. (max 128 chars) |
name | string | no | Internal name. (max 200 chars) |
list_id | string | no | The list to send to. |
from | string | no | Sender on a verified domain. (max 320 chars) |
reply_to | string | no | Reply-To address. (max 320 chars) |
subject | string | no | Subject line. (max 998 chars) |
preview_text | string | no | Inbox preview text. (max 300 chars) |
html | string | no | HTML body. |
text | string | no | Plain-text body. |
template | object,null | no | A published hosted template, or null to remove it. |
markdown | string | no | Markdown body instead of html: converted to a block document and rendered with the workspace design (get_newsletter_settings). Merge tags allowed. Not combinable with html or content. (max 1000000 chars) |
content | object,null | no | Block document ({"type": "doc", "content": [...]}, the dashboard editor's format: paragraph, heading, bulletList, orderedList, blockquote, image, button, callout, horizontalRule, codeBlock). Rendered with the workspace design unless html is also sent. |
editor | string | no | Which editor the dashboard opens: html or blocks. Set automatically for markdown and content. (one of html, blocks) |
audience | object | no | Who gets it, within the list. Subscribed, non-dormant members only. |
audience.segment_id | string,null | no | Only list members matching this segment (seg_…), or null for the whole list. |
audience.order | string | no | With a limit: signup (default) or engaged (most engaged first). (one of signup, engaged) |
audience.limit | integer,null | no | Send to at most this many people (send_broadcast_to_more sends to more later), or null for everyone. (1–10000000) |
tracking | object | no | Open and click tracking, both on by default. Opens are estimates (bot-filtered pixel loads), never proof that someone read the email. |
tracking.opens | boolean | no | Add an open pixel. |
tracking.clicks | boolean | no | Rewrite links to signed SendHQ redirects (the unsubscribe link never is). |
web | object | no | Publish this issue on the list's newsletter site (see create_list site) once it is sent. |
web.enabled | boolean | no | Publish it. |
web.slug | string,null | no | Issue address; defaults to the subject. (max 100 chars) |
web.pinned | boolean | no | Keep it at the top of the site. |
{
"name": "update_broadcast",
"arguments": {
"broadcast_id": "bc_123",
"subject": "October product update"
}
}delete_broadcastDelete a draft broadcast
DESTRUCTIVE: permanently delete a draft broadcast. Broadcasts that were sent cannot be deleted. Part of Campaigns.
| Parameter | Type | Required | Description |
|---|---|---|---|
broadcast_id | string | yes | Broadcast ID (starts with bc_), as returned by a list or create tool. (max 128 chars) |
{
"name": "delete_broadcast",
"arguments": {
"broadcast_id": "bc_123"
}
}send_broadcastSend a broadcast
SENDS REAL EMAIL to the broadcast's audience: every subscribed, non-dormant member of its list, narrowed by its segment and limit. Only call it when the user asked to send this broadcast now. Each message goes through the normal checks (verified domain, marketing plan, suppressions, unsubscribes, reputation, daily and monthly limits) and carries one-click unsubscribe from this list; members that cannot be mailed are skipped. Each contact is sent at most once. Delivery continues in the background: poll get_broadcast for totals and get_broadcast_report for engagement. A scheduled broadcast fails with 409 until unscheduled. Unpaid workspaces (integration trial) can send only to the account email. Pass idempotency_key so a retry cannot start the send twice. Part of Campaigns.
| Parameter | Type | Required | Description |
|---|---|---|---|
broadcast_id | string | yes | Broadcast ID (starts with bc_), as returned by a list or create tool. (max 128 chars) |
idempotency_key | string | no | Idempotency-Key header (max 200 chars). Reuse it only to retry this exact request. (max 200 chars) |
{
"name": "send_broadcast",
"arguments": {
"broadcast_id": "bc_123",
"idempotency_key": "october-update"
}
}cancel_broadcastCancel a broadcast
DESTRUCTIVE: stop a draft or sending broadcast for good. Recipients not yet sent are marked skipped; messages already handed to the provider are not recalled. Part of Campaigns.
| Parameter | Type | Required | Description |
|---|---|---|---|
broadcast_id | string | yes | Broadcast ID (starts with bc_), as returned by a list or create tool. (max 128 chars) |
{
"name": "cancel_broadcast",
"arguments": {
"broadcast_id": "bc_123"
}
}list_broadcast_recipientsList broadcast recipients
Campaigns: Per-recipient outcomes and engagement of a broadcast: queued, sending, sent, held, skipped (with the reason), or failed (with the error), the inbox provider, delivered/opened/clicked/bounced/complained/unsubscribed times, and the email ID to inspect delivery events with list_email_events. Filter by outcome, by activity (who opened, clicked, did not open, ...), or by email. Opens are bot-filtered estimates, never proof of reading. Paginated.
| Parameter | Type | Required | Description |
|---|---|---|---|
broadcast_id | string | yes | Broadcast ID (starts with bc_), as returned by a list or create tool. (max 128 chars) |
status | string | no | Only recipients with this outcome. (one of queued, sending, sent, held, skipped, failed) |
activity | string | no | Only recipients who did this. (one of opened, clicked, not_opened, not_delivered, bounced, complained, unsubscribed) |
query | string | no | Recipient email contains this text. (max 200 chars) |
include | string | no | links adds the links each recipient clicked (implied by activity: clicked). (one of links) |
limit | integer | no | Page size. Defaults to 50. (default 50; 1–200) |
offset | integer | no | Number of records to skip. Use pagination.next_offset from the previous page. (default 0; 0–…) |
{
"name": "list_broadcast_recipients",
"arguments": {
"broadcast_id": "bc_123",
"status": "failed"
}
}check_broadcastRun a broadcast's pre-send checks
Campaigns: The review list shown before sending: sender verification, subject, preview text, content, audience size, size against Gmail's 102 KB clipping limit, postal address, merge tags without data or fallback, empty or shortened links, image descriptions, placeholder text, spam-prone wording, the trial's own-address rule, and whether a test was sent. ready is false while any check has level block; schedule_broadcast refuses then. Fix warn items or tell the user about them.
| Parameter | Type | Required | Description |
|---|---|---|---|
broadcast_id | string | yes | Broadcast ID (starts with bc_), as returned by a list or create tool. (max 128 chars) |
{
"name": "check_broadcast",
"arguments": {
"broadcast_id": "bc_123"
}
}send_broadcast_testSend a broadcast test
SENDS REAL EMAIL: a copy of the draft marked [Test] to up to 5 addresses (default: the saved test recipients, or the account email), with merge tags filled from contact_id or a sample contact. Never goes to the list. Only send to addresses the user gave or owns. Limited to 100 test sends a day. Template broadcasts are tested with send_template_test. Part of Campaigns.
| Parameter | Type | Required | Description |
|---|---|---|---|
broadcast_id | string | yes | Broadcast ID (starts with bc_), as returned by a list or create tool. (max 128 chars) |
to | string[] | no | Test recipients (up to 5). (1–5 items) |
contact_id | string | no | Fill merge tags from this contact (ct_…). (max 320 chars) |
remember | boolean | no | Save to as the default test recipients. |
{
"name": "send_broadcast_test",
"arguments": {
"broadcast_id": "bc_123",
"to": [
"owner@example.com"
]
}
}schedule_broadcastSchedule a broadcast
SENDS REAL EMAIL at scheduled_at to the broadcast's audience, with the same checks as send_broadcast. Only call it when the user asked to send this broadcast at that time. The pre-send checks run now (422 with checks if one blocks). It starts within about a minute of the time; if it can no longer send then, it returns to a draft with the reason in lastError. A scheduled draft cannot be edited until unschedule_broadcast. Part of Campaigns.
| Parameter | Type | Required | Description |
|---|---|---|---|
broadcast_id | string | yes | Broadcast ID (starts with bc_), as returned by a list or create tool. (max 128 chars) |
scheduled_at | string | yes | When to send: ISO 8601 date-time with a timezone, at least a minute and at most a year ahead, e.g. 2026-11-10T09:00:00Z. (max 40 chars) |
{
"name": "schedule_broadcast",
"arguments": {
"broadcast_id": "bc_123",
"scheduled_at": "2026-11-10T09:00:00Z"
}
}unschedule_broadcastUnschedule a broadcast
Campaigns: Return a scheduled broadcast to an editable draft so it does not send. Fails with 409 when it is not scheduled.
| Parameter | Type | Required | Description |
|---|---|---|---|
broadcast_id | string | yes | Broadcast ID (starts with bc_), as returned by a list or create tool. (max 128 chars) |
{
"name": "unschedule_broadcast",
"arguments": {
"broadcast_id": "bc_123"
}
}duplicate_broadcastDuplicate a broadcast
Campaigns: Copy any broadcast (draft, sent, or cancelled) into a new draft named <name> (copy) with the same list, sender, content, audience, and tracking settings. Nothing is sent.
| Parameter | Type | Required | Description |
|---|---|---|---|
broadcast_id | string | yes | Broadcast ID (starts with bc_), as returned by a list or create tool. (max 128 chars) |
{
"name": "duplicate_broadcast",
"arguments": {
"broadcast_id": "bc_123"
}
}send_broadcast_to_moreSend a broadcast to more people
SENDS REAL EMAIL: sends a sent or sending broadcast to up to limit more people in its audience who have not received it (the rest of a limited audience, or people who joined since), in its audience order. Only call it when the user asked for this. Same checks as send_broadcast; each contact still gets it at most once. Fails with 422 when everyone already got it. Pass idempotency_key so a retry cannot add people twice. Part of Campaigns.
| Parameter | Type | Required | Description |
|---|---|---|---|
broadcast_id | string | yes | Broadcast ID (starts with bc_), as returned by a list or create tool. (max 128 chars) |
limit | integer | yes | How many more people at most. (1–10000000) |
idempotency_key | string | no | Idempotency-Key header (max 200 chars). Reuse it only to retry this exact request. (max 200 chars) |
{
"name": "send_broadcast_to_more",
"arguments": {
"broadcast_id": "bc_123",
"limit": 500,
"idempotency_key": "october-update-more-1"
}
}get_broadcast_reportGet a broadcast report
Campaigns: Engagement for a sending or sent broadcast: unique and total opens and clicks, bounces, complaints, and unsubscribes with rates in percent, the same per inbox provider, clicks per link, first opens and clicks by hour, and the average of the previous five broadcasts. Opens are estimates: automated requests (scanners, link previews, privacy prefetching) are counted only in machine. Never claim that a person read the email. Fails with 409 for a draft.
| Parameter | Type | Required | Description |
|---|---|---|---|
broadcast_id | string | yes | Broadcast ID (starts with bc_), as returned by a list or create tool. (max 128 chars) |
{
"name": "get_broadcast_report",
"arguments": {
"broadcast_id": "bc_123"
}
}get_newsletter_settingsGet newsletter settings
Campaigns: The postal address added to every marketing email footer, the design (accent color, font, alignment) used for Markdown and block content, list cleanup settings, and default test recipients.
No parameters.
{
"name": "get_newsletter_settings",
"arguments": {}
}update_newsletter_settingsUpdate newsletter settings
Campaigns: Change the postal address (required in marketing email by CAN-SPAM and CASL), design, list cleanup, or default test recipients. Omitted keys keep their value. List cleanup is on by default and sends email automatically: a subscriber who got afterSends broadcasts in a row without opening or clicking and has been quiet for quietDays gets one "still want these?" email; a week later those who did not confirm become dormant (kept, but skipped by broadcasts). Confirm with the user before turning it on or changing its email.
| Parameter | Type | Required | Description |
|---|---|---|---|
postal_address | string | no | Sender's postal address for the footer. Empty string removes it. (max 300 chars) |
design | object | no | Rendering design for Markdown and block content. |
design.accentColor | string,null | no | Hex color like #0a7d4f, or null for the default. |
design.font | string | no | Font family. (one of sans, serif, mono) |
design.align | string | no | Text alignment. (one of left, center) |
cleanup | object | no | List cleanup of readers who stopped opening. |
cleanup.enabled | boolean | no | Turn list cleanup on or off. |
cleanup.afterSends | integer | no | Broadcasts in a row without an open or click (3-50). (3–50) |
cleanup.quietDays | integer | no | Days without engagement (30-730). (30–730) |
cleanup.subject | string,null | no | Subject of the cleanup email; merge tags allowed. (max 200 chars) |
cleanup.body | string,null | no | Body text of the cleanup email. (max 2000 chars) |
cleanup.from | string,null | no | Sender on a verified domain. Defaults to the last sent broadcast's sender. (max 320 chars) |
test_recipients | string[] | no | Default addresses for send_broadcast_test (up to 5). (0–5 items) |
{
"name": "update_newsletter_settings",
"arguments": {
"postal_address": "Acme Inc., 1 Main St, Springfield",
"design": {
"accentColor": "#0a7d4f"
}
}
}get_campaign_overviewGet campaign overview
Campaigns: Subscriber growth and recent results for the last days (7-365, default 30): active, dormant, and pending subscribers, joined and left, a daily series, and the six latest broadcasts with open and click rates (bot-filtered estimates) and their average.
| Parameter | Type | Required | Description |
|---|---|---|---|
days | integer | no | Window in days, 7 to 365. Defaults to 30. (7–365) |
{
"name": "get_campaign_overview",
"arguments": {
"days": 30
}
}list_uploaded_imagesList uploaded images
Campaigns: The workspace's 60 most recent editor image uploads with their public URLs (https://sendhq.cc/i/<id>), to reuse in broadcast HTML, Markdown, or image blocks. Uploading needs the dashboard or the REST API (raw image body). Paginated.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Page size. Defaults to 50. (default 50; 1–200) |
offset | integer | no | Number of records to skip. Use pagination.next_offset from the previous page. (default 0; 0–…) |
{
"name": "list_uploaded_images",
"arguments": {}
}Account, usage, analytics, and keys
get_accountGet account, usage, and billing
Account owner email, plan/access tier, current-period recipient deliveries used vs quota, domains used vs limit, attachment transfer, reputation summary, subscription state, published plans, and workspace counts. Use it to check remaining quota or who the trial can deliver to (the account email).
No parameters.
{
"name": "get_account",
"arguments": {}
}get_account_diagnosticsDiagnose sending and receiving
One read that explains what is wrong with sending or receiving in this workspace: an issues list (unverified domains and missing DKIM records, inbound MX not ready, paused or throttled senders, quota or daily limit reached, trial restrictions, high bounce or complaint rates, failing webhooks), plan and usage against limits, each domain with its DKIM and inbound state, sending identities, 30-day deliverability and sender reputation, suppressions and unsubscribes, and the last 30 days of messages that were not sent (with reasons) plus bounce, complaint and reject events. Start here when a user asks why mail is not arriving. Never returns message bodies or secrets.
No parameters.
{
"name": "get_account_diagnostics",
"arguments": {}
}get_analyticsGet sending analytics
Dashboard analytics for the last 7, 30, or 90 days: sent/received/delivered/bounced/blocked/opened/clicked/complaint totals, a daily timeline, top sending domains, and top subjects.
| Parameter | Type | Required | Description |
|---|---|---|---|
days | integer | no | Window in days: 7, 30 (default), or 90. (one of 7, 30, 90) |
{
"name": "get_analytics",
"arguments": {
"days": 30
}
}list_api_keysList API key metadata
List API key names, non-secret prefixes, and last-used times. Read-only: this MCP server cannot create, rotate, or revoke keys; a human does that in the dashboard. Paginated: the result includes pagination {offset, limit, returned, total?, has_more, next_offset}.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | no | Page size. Defaults to 50. (default 50; 1–200) |
offset | integer | no | Number of records to skip. Use pagination.next_offset from the previous page. (default 0; 0–…) |
{
"name": "list_api_keys",
"arguments": {}
}get_service_healthCheck SendHQ service health
Check that the SendHQ API is up and which mail provider is active. Does not need a valid API key.
No parameters.
{
"name": "get_service_health",
"arguments": {}
}API coverage inventory
Every operation in the public API and the tool that covers it. Everything a user can do in the dashboard that has an API is covered; the exclusions below are deliberate.
| Endpoint | Tool | Notes |
|---|---|---|
| POST /emails | send_email | Send one email |
| POST /emails/batch | send_batch | Send up to 100 individualized messages |
| GET /emails | list_emails | List sent and received email |
| GET /emails/:id | get_email | Retrieve an email and its attachments |
| PATCH /emails/:id | mark_email | Update read, archive, spam, category, or importance |
| POST /emails/:id/labels | label_email | Add or remove labels on an email |
| DELETE /emails/:id | delete_email | Delete a retained email |
| GET /emails/:id/events | list_email_events | List delivery events for an email |
| GET /threads/:id | get_thread | Retrieve a conversation chronologically |
| GET /labels | list_labels | List labels with message counts and filing rules |
| POST /labels | create_label | Create a label, optionally with auto-filing rules |
| GET /labels/:id | get_label | Retrieve a label by ID or name |
| PATCH /labels/:id | update_label | Rename, recolor, or turn a label into a bucket |
| DELETE /labels/:id | delete_label | Delete a label without deleting its email |
| POST /labels/:id/rules | create_label_rule | Add an auto-filing rule to a label |
| DELETE /labels/:id/rules/:rule_id | delete_label_rule | Delete an auto-filing rule |
| POST /drafts | create_draft | Create a composer draft |
| GET /drafts | list_drafts | List composer drafts |
| GET /drafts/:id | get_draft | Retrieve a draft and attachments |
| PUT /drafts/:id | update_draft | Replace draft content |
| DELETE /drafts/:id | delete_draft | Discard a draft |
| POST /drafts/:id/attachments | upload_attachment | Upload an attachment to a draft |
| GET /attachments/:id | download_attachment | Download a private attachment |
| DELETE /attachments/:id | delete_attachment | Delete a private attachment |
| GET /sending-identities | list_sending_identities | List verified sender identities |
| GET /templates | list_templates | List hosted templates |
| POST /templates | create_template | Create a hosted template |
| GET /templates/:id | get_template | Retrieve drafts, releases, and usage |
| PUT /templates/:id/draft | update_template_draft | Autosave a template draft |
| POST /templates/:id/draft | create_template_draft | Create a new draft from the published release |
| POST /templates/:id/render | render_template | Render exact server output |
| POST /templates/:id/test | send_template_test | Send a test snapshot |
| POST /templates/:id/publish | publish_template | Publish an immutable template release |
| POST /templates/:id/archive | archive_template | Archive a template |
| POST /templates/:id/restore | restore_template | Restore an archived template |
| POST /domains | add_domain | Add a sending domain |
| GET /domains | list_domains | List domains and cached DNS state |
| GET /domains/:id | get_domain | Retrieve domain setup details |
| POST /domains/:id/verify | verify_domain | Refresh domain and DNS verification |
| POST /domains/:id/inbound/setup | setup_inbound | Provision inbound receiving |
| POST /domains/:id/inbound/verify | verify_inbound | Verify inbound MX routing |
| DELETE /domains/:id | delete_domain | Delete a domain |
| GET /dns/provider | get_dns_provider | Detect the authoritative DNS provider and relative record hosts |
| GET /dns/domain-connect/connect | get_domain_connect_link | Create a Domain Connect consent link for one-click DNS setup |
| POST /inboxes | create_inbox | Create an inbound address |
| GET /inboxes | list_inboxes | List inbound addresses |
| GET /inboxes/:id | get_inbox | Retrieve an inbound address |
| PATCH /inboxes/:id | update_inbox | Rename, enable, or disable an inbox |
| PUT /inboxes/:id/forwarding | set_inbox_forwarding | Forward an inbox's received mail to another address |
| DELETE /inboxes/:id | delete_inbox | Delete an inbox while retaining messages |
| GET /deliverability/stats | deliverability_stats | Retrieve 30-day delivery statistics |
| GET /deliverability/reputation | list_sender_reputation | List reputation state by exact sender identity |
| GET /suppressions | list_suppressions | List workspace suppressions |
| DELETE /suppressions/:email | remove_suppression | Remove an eligible bounce suppression |
| GET /blocked-recipients | list_blocked_recipients | List bounces, complaints, and unsubscribes |
| GET /webhooks | not exposed | Webhook endpoints are configured once by a human, in the dashboard or the REST API, and their responses carry signing secrets an agent should not hold. An agent reads the same outcomes with list_email_events, list_emails, and list_blocked_recipients. |
| POST /webhooks | not exposed | Webhook endpoints are configured once by a human, in the dashboard or the REST API, and their responses carry signing secrets an agent should not hold. An agent reads the same outcomes with list_email_events, list_emails, and list_blocked_recipients. |
| GET /webhooks/:id | not exposed | Webhook endpoints are configured once by a human, in the dashboard or the REST API, and their responses carry signing secrets an agent should not hold. An agent reads the same outcomes with list_email_events, list_emails, and list_blocked_recipients. |
| PATCH /webhooks/:id | not exposed | Webhook endpoints are configured once by a human, in the dashboard or the REST API, and their responses carry signing secrets an agent should not hold. An agent reads the same outcomes with list_email_events, list_emails, and list_blocked_recipients. |
| DELETE /webhooks/:id | not exposed | Webhook endpoints are configured once by a human, in the dashboard or the REST API, and their responses carry signing secrets an agent should not hold. An agent reads the same outcomes with list_email_events, list_emails, and list_blocked_recipients. |
| POST /webhooks/:id/rotate-secret | not exposed | Webhook endpoints are configured once by a human, in the dashboard or the REST API, and their responses carry signing secrets an agent should not hold. An agent reads the same outcomes with list_email_events, list_emails, and list_blocked_recipients. |
| POST /webhooks/:id/test | not exposed | Webhook endpoints are configured once by a human, in the dashboard or the REST API, and their responses carry signing secrets an agent should not hold. An agent reads the same outcomes with list_email_events, list_emails, and list_blocked_recipients. |
| GET /webhooks/:id/deliveries | not exposed | Webhook endpoints are configured once by a human, in the dashboard or the REST API, and their responses carry signing secrets an agent should not hold. An agent reads the same outcomes with list_email_events, list_emails, and list_blocked_recipients. |
| GET /webhooks/:id/deliveries/:deliveryId | not exposed | Webhook endpoints are configured once by a human, in the dashboard or the REST API, and their responses carry signing secrets an agent should not hold. An agent reads the same outcomes with list_email_events, list_emails, and list_blocked_recipients. |
| POST /webhooks/:id/deliveries/:deliveryId/retry | not exposed | Webhook endpoints are configured once by a human, in the dashboard or the REST API, and their responses carry signing secrets an agent should not hold. An agent reads the same outcomes with list_email_events, list_emails, and list_blocked_recipients. |
| GET /contacts | list_contacts | List contacts |
| POST /contacts | create_contact | Add a contact with its consent basis |
| POST /contacts/bulk | bulk_update_contacts | Tag, move, unsubscribe, or delete many contacts at once |
| GET /contacts/export | not exposed | Image uploads take a raw binary body and the export returns a CSV file meant for a person to download. Agents reuse uploaded images with list_uploaded_images and read the same contacts, filters, tags, and fields as JSON with list_contacts (paginated). |
| GET /contacts/:id | get_contact | Retrieve a contact by ID or email |
| PATCH /contacts/:id | update_contact | Update a contact's name, fields, or tags, or unsubscribe it |
| DELETE /contacts/:id | delete_contact | Delete a contact and its list memberships |
| POST /contact-imports | import_contacts | Import contacts from CSV |
| GET /contact-imports | list_contact_imports | List recent contact imports |
| GET /tags | list_tags | List contact tags with how many contacts carry each |
| GET /contact-fields | list_contact_fields | List custom contact field definitions |
| PUT /contact-fields/:key | set_contact_field | Define or change a custom contact field |
| DELETE /contact-fields/:key | delete_contact_field | Delete a custom field definition |
| GET /segments | list_segments | List saved segments with live counts |
| POST /segments | create_segment | Save a segment |
| POST /segments/preview | preview_segment | Count and sample the contacts that match unsaved conditions |
| GET /segments/:id | get_segment | Retrieve a segment and its live count |
| PATCH /segments/:id | update_segment | Rename a segment or change its conditions |
| DELETE /segments/:id | delete_segment | Delete a segment |
| GET /lists | list_lists | List contact lists with member counts |
| POST /lists | create_list | Create a list with an optional signup form, welcome email, and newsletter site |
| GET /lists/:id | get_list | Retrieve a list |
| PATCH /lists/:id | update_list | Rename a list or change its signup form, welcome email, or site |
| DELETE /lists/:id | delete_list | Delete a list without deleting its contacts |
| POST /lists/:id/contacts | add_list_contacts | Add existing contacts to a list |
| DELETE /lists/:id/contacts/:contact_id | remove_list_contact | Remove a contact from a list |
| GET /broadcasts | list_broadcasts | List broadcasts |
| POST /broadcasts | create_broadcast | Create a broadcast draft |
| GET /broadcasts/:id | get_broadcast | Retrieve a broadcast and its delivery totals |
| PATCH /broadcasts/:id | update_broadcast | Edit a draft broadcast |
| DELETE /broadcasts/:id | delete_broadcast | Delete a draft broadcast |
| GET /broadcasts/:id/checks | check_broadcast | Run the pre-send checks for a broadcast |
| POST /broadcasts/:id/test | send_broadcast_test | Send a test copy of a draft |
| POST /broadcasts/:id/schedule | schedule_broadcast | Schedule a draft to send later |
| POST /broadcasts/:id/unschedule | unschedule_broadcast | Unschedule a draft |
| POST /broadcasts/:id/duplicate | duplicate_broadcast | Copy a broadcast into a new draft |
| POST /broadcasts/:id/send | send_broadcast | Send a broadcast to its audience |
| POST /broadcasts/:id/send-more | send_broadcast_to_more | Send a sent broadcast to more of its audience |
| POST /broadcasts/:id/cancel | cancel_broadcast | Cancel a draft or sending broadcast |
| GET /broadcasts/:id/recipients | list_broadcast_recipients | List a broadcast's recipients, outcomes, and engagement |
| GET /broadcasts/:id/report | get_broadcast_report | Retrieve a broadcast's engagement report |
| GET /campaigns/overview | get_campaign_overview | Retrieve subscriber growth and recent broadcast performance |
| GET /newsletter-settings | get_newsletter_settings | Retrieve newsletter settings |
| PATCH /newsletter-settings | update_newsletter_settings | Change newsletter settings |
| POST /uploads/images | not exposed | Image uploads take a raw binary body and the export returns a CSV file meant for a person to download. Agents reuse uploaded images with list_uploaded_images and read the same contacts, filters, tags, and fields as JSON with list_contacts (paginated). |
| GET /uploads/images | list_uploaded_images | List recently uploaded images |
| GET /account | get_account | Retrieve account, usage, billing state, and workspace counts with an API key |
| GET /account/diagnostics | get_account_diagnostics | Diagnose sending and receiving problems in the workspace |
| GET /analytics | get_analytics | Retrieve dashboard sending analytics for 7, 30, or 90 days |
| GET /profile | get_account | Session-only twin of GET /account; the MCP server reads the API-key route. |
| POST /billing/checkout | not exposed | Billing changes are session-only by design and require the account owner in the dashboard. Billing state is readable with get_account. |
| POST /billing/cancel | not exposed | Billing changes are session-only by design and require the account owner in the dashboard. Billing state is readable with get_account. |
| POST /keys | not exposed | Deliberately excluded: an agent must not mint or destroy credentials. Keys are managed by a human in the dashboard. |
| GET /keys | list_api_keys | List API-key metadata |
| DELETE /keys/:id | not exposed | Deliberately excluded: an agent must not mint or destroy credentials. Keys are managed by a human in the dashboard. |
Deliberately not available
| Capability | Endpoints | Reason |
|---|---|---|
| Create, rotate, revoke, or delete API keys | POST /keys, DELETE /keys/:id | Deliberately excluded: an agent must not mint or destroy credentials. Keys are managed by a human in the dashboard. |
| Start a checkout or cancel a subscription | POST /billing/checkout, POST /billing/cancel | Billing changes are session-only by design and require the account owner in the dashboard. Billing state is readable with get_account. |
| Cloudflare one-click DNS (OAuth) | GET /api/dns/cloudflare/connect | Requires an interactive browser session and Cloudflare OAuth consent. Use get_domain records, get_dns_provider hosts, or get_domain_connect_link instead. |
| Sign up, log in, log out, Google account linking | /api/auth/* | Human browser authentication; the MCP server authenticates with an API key. |
| Contact support form | POST /api/contact | Public marketing-site form for humans, not a workspace operation. |
| Webhook endpoints and deliveries | GET /webhooks, POST /webhooks, GET /webhooks/:id, PATCH /webhooks/:id, DELETE /webhooks/:id, POST /webhooks/:id/rotate-secret, POST /webhooks/:id/test, GET /webhooks/:id/deliveries, GET /webhooks/:id/deliveries/:deliveryId, POST /webhooks/:id/deliveries/:deliveryId/retry | Webhook endpoints are configured once by a human, in the dashboard or the REST API, and their responses carry signing secrets an agent should not hold. An agent reads the same outcomes with list_email_events, list_emails, and list_blocked_recipients. |
| Upload editor images and export contacts as CSV | POST /uploads/images, GET /contacts/export | Image uploads take a raw binary body and the export returns a CSV file meant for a person to download. Agents reuse uploaded images with list_uploaded_images and read the same contacts, filters, tags, and fields as JSON with list_contacts (paginated). |
Machine-readable catalog: /docs/mcp/tools.json (schemas, annotations, endpoint mapping, exclusions). Markdown version of this page: /docs/mcp.md. With the CLI installed, sendhq commands --format json prints the same catalog.