# XMails API and MCP reference Everything a XMails customer does in the dashboard works through the REST API and the MCP server. Both run the same actions as the dashboard, with the same plan limits, sender rules, pre-send checks, paused accounts and first-send steps. ## Authentication Create an API key on the dashboard's API keys page and send it as `Authorization: Bearer ` to https://xmails.si/api/v1 or to the MCP server at https://xmails.si/mcp. - Full access keys can do everything below, including creating more keys. Keep them on a server or in your AI assistant's settings. - Signups only keys can only add contacts (`POST /api/v1/contacts`, MCP tool `add_contact`), for signup forms in website code. That endpoint also answers browsers (CORS). - Every key gets 300 requests a minute across REST and MCP, and some actions have their own limit, for each key and for the account: its keys, connected apps and dashboard share the account's, so more keys don't raise it. Over a limit, the answer is 429 rate_limited. - Keys are stored hashed and shown once. Revoked keys answer 401 unauthorized. An account has up to 20 active keys. - The MCP server also takes OAuth access tokens, for apps such as ChatGPT, Claude and Grok Bot: the user signs in to XMails and allows the app, which then has full access. Discovery starts at https://xmails.si/.well-known/oauth-protected-resource/mcp. ## Errors Every error answers `{ "error": { "code": "…", "message": "…" } }`. MCP tools answer the same object in an error result. Codes never change meaning: - `unauthorized` (401): The API key is missing, malformed, unknown or revoked. - `forbidden` (403): The key's level doesn't allow this action: a signups only key can only add contacts. - `not_found` (404): The newsletter, campaign, contact, domain or key isn't in this account. - `validation_failed` (400): The input doesn't match the schema or breaks a rule. The message names the field. - `conflict` (409): The resource's state doesn't allow it, e.g. sending a campaign that is already sending or sent. Retrying won't help. - `rate_limited` (429): Too many requests with this key, or too many of one action for the account, whose keys, apps and dashboard share its limits. Wait and retry. - `subscription_required` (402): The account has no active plan. - `billing_off` (404): The server is self-hosted without billing: every account has every feature, without plan limits. - `plan_limit_emails` (402): The send needs more emails than the plan has left this month. - `plan_limit_subscribers` (402): The account has more active subscribers than its plan allows. - `account_blocked` (403): The account is blocked after spam signals (bounce or complaint rates, Gmail's spam rate in Google Postmaster Tools, a spam trap, the email provider) or by an admin. Nothing is sent, the tracked links in its emails stop redirecting, and only reads, pausing, unscheduling, revoking access and billing work until an admin unblocks it. - `sender_not_allowed` (422): The from address is neither the newsletter's shared address nor on a verified domain of this account. - `send_check_failed` (422): A pre-send check failed: no postal address, subject or content, or the email is over 100 KB. - `no_recipients` (422): Nobody is left to send to. - `sending_disabled` (503): Sending is turned off on this server. - `unavailable` (503): A service the action needs isn't configured or didn't answer. Retry later. - `internal_error` (500): Something failed on XMails’ side. Sending a campaign that is already sending or sent answers `conflict`, so retrying a send never sends twice. ## MCP The MCP server speaks Streamable HTTP at https://xmails.si/mcp, without sessions. Each endpoint below is one tool with the same name, input and output; its annotations tell clients whether it's read-only, can't be undone, is safe to repeat or reaches outside XMails. The send tools can't be undone: ask the user before calling them. ### ChatGPT 1. Go to Plugins (chatgpt.com/plugins), click + and pick Add custom MCP server. 2. Name it XMails, enter https://xmails.si/mcp as the server URL and pick OAuth, with no client ID or secret. If ChatGPT asks how to register, pick Client ID Metadata Document. 3. Check I understand and want to continue, then click Create as a plugin. 4. Install the plugin, sign in to XMails when ChatGPT asks and click Allow. 5. In a chat, type @ and pick XMails. Custom MCP servers are on ChatGPT on the web, for Business, Enterprise and Edu workspaces: on Business, admins and owners add them; on Enterprise and Edu, so can the members an admin allows. On Pro, ChatGPT only uses the read-only tools for now. ### Claude 1. Go to Customize › Connectors and click Add custom connector. 2. Name it XMails and enter https://xmails.si/mcp. If Claude asks how it identifies itself, keep Use Claude's published identity. Click Add. 3. Click Connect, sign in to XMails and click Allow. 4. The connector works in claude.ai, Claude Desktop and the mobile apps. Turn it on or off in a chat from + › Connectors. On Team and Enterprise plans, an owner adds it once in Organization settings › Connectors › Add › Custom › Web, then each member clicks Connect under Customize › Connectors. The Free plan has one custom connector. ### Grok Bot 1. In a chat, ask Grok Bot to add the custom MCP server https://xmails.si/mcp. 2. Sign in to XMails when it asks, and click Allow. ### Claude Code Terminal: ``` claude mcp add --transport http --scope user xmails https://xmails.si/mcp \ --header "Authorization: Bearer $MAILCHEAP_API_KEY" ``` The shell puts the key in Claude Code's settings when the command runs. --scope user makes the server available in every project; run /mcp in Claude Code to check it's connected. ### Codex ~/.codex/config.toml: ``` [mcp_servers.xmails] url = "https://xmails.si/mcp" bearer_token_env_var = "MAILCHEAP_API_KEY" ``` Codex reads the key from MAILCHEAP_API_KEY each time it starts, so export it in your shell profile. The same server from the terminal: codex mcp add xmails --url https://xmails.si/mcp --bearer-token-env-var MAILCHEAP_API_KEY ### Cursor ~/.cursor/mcp.json: ``` { "mcpServers": { "xmails": { "url": "https://xmails.si/mcp", "headers": { "Authorization": "Bearer ${env:MAILCHEAP_API_KEY}" } } } } ``` Cursor reads MAILCHEAP_API_KEY from the environment it was opened in. If it isn't set there, put the key itself after Bearer, and keep the file out of version control. ## Endpoints ### Overview Subscribers, email results and sends per hour, as on the dashboard's first page. #### Get the overview `GET /api/v1/overview` · MCP tool `get_overview` · Full access · read-only, safe to repeat The dashboard's overview for one newsletter: subscribers each day of the last 30 days, email results of the last 30 days against the 30 before, emails sent per hour today and yesterday, and the plan's usage this month, which the account's newsletters share. - `newsletter` (query, string): The newsletter's ID or slug. Default: the account's first newsletter, the one it has had longest. - `timeZone` (query, string): IANA time zone for days and hours, e.g. Europe/Paris. Default: UTC. ```bash curl "https://xmails.si/api/v1/overview?timeZone=Europe%2FParis" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` ### Analytics Date-filtered account analytics, engagement, audience, campaigns and delivery health. #### Get newsletter analytics `GET /api/v1/analytics` · MCP tool `get_analytics` · Full access · read-only, safe to repeat Reports account-owned email performance, audience, campaigns, inbox providers, recorded links and operational inventory. Dates are inclusive UTC calendar days with an equal previous-period comparison. Email outcomes belong to their send cohort, as observed now; audience and inventory are current snapshots. Test emails are excluded by default. - `newsletter` (query, string): Only this newsletter's: its ID or slug. Default: every newsletter. - `days` (query, integer): Calendar days ending today, 30 by default; ignored when both start and end are supplied - `start` (query, string): Inclusive UTC start date, YYYY-MM-DD; provide end too, at most 366 days - `end` (query, string): Inclusive UTC end date, YYYY-MM-DD, no later than today - `includeTests` (query, boolean): Include test emails in message metrics; false by default ```bash curl "https://xmails.si/api/v1/analytics?days=30" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` ### Newsletters Sender, postal address, welcome and confirmation emails, list cleanup and archive. #### List newsletters `GET /api/v1/newsletters` · MCP tool `list_newsletters` · Full access · read-only, safe to repeat Lists the account's newsletters, 3 at most, first created first, with their settings and active contacts. Actions that take a newsletter use the first one when it's left out. No parameters. ```bash curl "https://xmails.si/api/v1/newsletters" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Get a newsletter `GET /api/v1/newsletters/{id}` · MCP tool `get_newsletter` · Full access · read-only, safe to repeat Gets a newsletter's settings: sender, postal address, welcome and confirmation emails, list cleanup and archive. - `id` (path, string, required): The newsletter's ID or slug ```bash curl "https://xmails.si/api/v1/newsletters/6650f1c2e9b1d4a012345678" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Create a newsletter `POST /api/v1/newsletters` · MCP tool `create_newsletter` · Full access Creates a newsletter. An account has up to 3, sharing its plan's emails and subscribers; one more answers conflict. Without a fromEmail it sends from the shared address hello@., which XMails sets up in a few minutes. List cleanup is on unless cleanup.enabled is false. Without a postalAddress, its emails print the server's default if it has one: XMails’ address on XMails Cloud. - `name` (body, string, required): The newsletter's name, shown in emails and on the archive - `slug` (body, string): Lowercase letters, digits and dashes. Default: made from the name, numbered when another newsletter has it. - `imageUrl` (body, string): Square logo, 128×128 or larger: an https:// link - `fromName` (body, string): Name in the From field - `fromEmail` (body, string): The shared address (hello@., set automatically) or an address on a domain added to this account - `replyTo` (body, string): Where replies go. Empty: replies to the shared address go to the account owner. - `postalAddress` (body, string): Printed in the footer of every email. Never set, it's the server's default (XMails’ address on XMails Cloud, none on a self-hosted server); saved empty, there's none and sending waits for one. - `archiveEnabled` (body, boolean): A public page for each sent issue, with a signup form. On for new newsletters: the website goes live after the first issue. - `description` (body, string): One sentence under the name on the archive, 200 characters at most - `author` (body, object): The archive's author page; the author's name is fromName - `author.bio` (body, string): Under the author's name on the archive, 160 characters at most - `author.imageUrl` (body, string): Author photo: an https:// link. The newsletter image otherwise. - `socialLinks` (body, string[]): https:// links shown on the archive, 8 at most - `requireConfirmation` (body, boolean): Confirmed opt-in: new signups get an email with a button and only join once they click it. On for new newsletters. - `cleanup` (body, object): List cleanup at each send: contacts who ignored several issues get a last-chance email instead, and become dormant if they don't answer - `cleanup.enabled` (body, boolean): On for new newsletters - `cleanup.issues` (body, number | null): Ignored issues in a row before the last-chance email, 2 to 100. Default 6. - `cleanup.days` (body, number | null): Days without an open, click or signup, 30 to 3650. Default 180. - `welcome` (body, object): Email sent to each new subscriber. Not sent while signups need confirming. - `welcome.enabled` (body, boolean) - `welcome.subject` (body, string) - `welcome.previewText` (body, string) - `welcome.markdown` (body, string): Markdown - `confirmation` (body, object): The confirmation email for confirmed opt-in. Sent back unchanged, the built-in one stays built-in and follows the name and description. - `confirmation.subject` (body, string) - `confirmation.previewText` (body, string) - `confirmation.markdown` (body, string): Markdown that keeps the button: an HTML link like Confirm ```bash curl -X POST "https://xmails.si/api/v1/newsletters" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Field notes","postalAddress":"Field Notes Ltd, 1 Main St, Paris, France"}' ``` #### Update a newsletter `PATCH /api/v1/newsletters/{id}` · MCP tool `update_newsletter` · Full access · safe to repeat Changes a newsletter's settings. Send only the fields to change; nested groups (welcome, confirmation, cleanup, author) keep the fields you leave out. An account changes slugs 3 times a day at most: one more answers rate_limited. - `id` (path, string, required): The newsletter's ID or slug - `name` (body, string): The newsletter's name, shown in emails and on the archive - `slug` (body, string): Lowercase letters, digits and dashes, not a name XMails keeps like www, api or mail. The shared from address (hello@.) and the archive subdomain follow it. - `imageUrl` (body, string): Square logo, 128×128 or larger: an https:// link - `fromName` (body, string): Name in the From field - `fromEmail` (body, string): The shared address (hello@., set automatically) or an address on a domain added to this account - `replyTo` (body, string): Where replies go. Empty: replies to the shared address go to the account owner. - `postalAddress` (body, string): Printed in the footer of every email. Never set, it's the server's default (XMails’ address on XMails Cloud, none on a self-hosted server); saved empty, there's none and sending waits for one. - `archiveEnabled` (body, boolean): A public page for each sent issue, with a signup form. On for new newsletters: the website goes live after the first issue. - `description` (body, string): One sentence under the name on the archive, 200 characters at most - `author` (body, object): The archive's author page; the author's name is fromName - `author.bio` (body, string): Under the author's name on the archive, 160 characters at most - `author.imageUrl` (body, string): Author photo: an https:// link. The newsletter image otherwise. - `socialLinks` (body, string[]): https:// links shown on the archive, 8 at most - `requireConfirmation` (body, boolean): Confirmed opt-in: new signups get an email with a button and only join once they click it. On for new newsletters. - `cleanup` (body, object): List cleanup at each send: contacts who ignored several issues get a last-chance email instead, and become dormant if they don't answer - `cleanup.enabled` (body, boolean): On for new newsletters - `cleanup.issues` (body, number | null): Ignored issues in a row before the last-chance email, 2 to 100. Default 6. - `cleanup.days` (body, number | null): Days without an open, click or signup, 30 to 3650. Default 180. - `welcome` (body, object): Email sent to each new subscriber. Not sent while signups need confirming. - `welcome.enabled` (body, boolean) - `welcome.subject` (body, string) - `welcome.previewText` (body, string) - `welcome.markdown` (body, string): Markdown - `confirmation` (body, object): The confirmation email for confirmed opt-in. Sent back unchanged, the built-in one stays built-in and follows the name and description. - `confirmation.subject` (body, string) - `confirmation.previewText` (body, string) - `confirmation.markdown` (body, string): Markdown that keeps the button: an HTML link like Confirm ```bash curl -X PATCH "https://xmails.si/api/v1/newsletters/6650f1c2e9b1d4a012345678" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"replyTo":"jane@example.com","requireConfirmation":true}' ``` #### Check a slug `GET /api/v1/newsletters/slugs/{slug}` · MCP tool `check_newsletter_slug` · Full access · read-only, safe to repeat Says whether a newsletter can take a slug: available, or taken by another newsletter, reserved by XMails or invalid, with the message saving it would answer. Checking doesn't count toward the 3 slug changes a day. - `slug` (path, string, required): The slug to check - `id` (query, string): The newsletter that would take it, by ID or slug: its own slug is available to it ```bash curl "https://xmails.si/api/v1/newsletters/slugs/6650f1c2e9b1d4a012345678?slug=field-notes" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Send a test of the welcome email `POST /api/v1/newsletters/{id}/welcome-test` · MCP tool `send_welcome_test_email` · Full access · reaches outside XMails Sends the saved welcome email to the account owner's address, switched on or not. It counts toward the plan's emails. - `id` (path, string, required): The newsletter's ID or slug ```bash curl -X POST "https://xmails.si/api/v1/newsletters/6650f1c2e9b1d4a012345678/welcome-test" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` ### Catalog Account-owned products and services, with reusable facts, prices, links and images. #### Get a product or service `GET /api/v1/catalog/{id}` · MCP tool `get_catalog_item` · Full access · read-only, safe to repeat Returns an account-owned product or service, including archived items, with its editable catalog details. - `id` (path, string, required): The catalog item's ID ```bash curl "https://xmails.si/api/v1/catalog/6650f1c2e9b1d4a012345678" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Delete a product or service `DELETE /api/v1/catalog/{id}` · MCP tool `delete_catalog_item` · Full access · can't be undone, safe to repeat Permanently deletes an account-owned product or service. Existing template snapshots, campaign drafts and image library assets are preserved. This cannot be undone. - `id` (path, string, required): The catalog item's ID ```bash curl -X DELETE "https://xmails.si/api/v1/catalog/6650f1c2e9b1d4a012345678" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### List products and services `GET /api/v1/catalog` · MCP tool `list_catalog_items` · Full access · read-only, safe to repeat Lists this account's products and services, newest first. Archived items are hidden unless requested. Pass nextCursor back as cursor for the next page. - `status` (query, "active" | "archived"): Item status, active by default - `limit` (query, integer): Items per page, 50 by default - `cursor` (query, string): nextCursor from the previous page ```bash curl "https://xmails.si/api/v1/catalog" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Add a product or service `POST /api/v1/catalog` · MCP tool `create_catalog_item` · Full access Adds a product or service to this account's catalog. Its facts, price, links and pictures can be reused in newsletter templates. - `kind` (body, "product" | "service", required): A product or a service - `name` (body, string, required): The product or service name - `description` (body, string, required): Facts and benefits to use in templates - `price` (body, string): Display price including currency and billing period, e.g. USD 49/month - `url` (body, string): The product, service, or booking page - `imageUrl` (body, string): An existing product image's public HTTPS URL ```bash curl -X POST "https://xmails.si/api/v1/catalog" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"kind":"service","name":"Design consultation","description":"A one-hour consultation to plan your next project.","price":"USD 99","url":"https://example.com/book"}' ``` #### Update a product or service `PATCH /api/v1/catalog/{id}` · MCP tool `update_catalog_item` · Full access · safe to repeat Updates an account-owned catalog item or archives it. Templates keep a snapshot so changing an item never rewrites an existing template or campaign. - `id` (path, string, required): The catalog item's ID - `kind` (body, "product" | "service"): A product or a service - `name` (body, string): The product or service name - `description` (body, string): Facts and benefits to use in templates - `price` (body, string): Display price including currency and billing period, e.g. USD 49/month - `url` (body, string): The product, service, or booking page - `imageUrl` (body, string): An existing product image's public HTTPS URL - `status` (body, "active" | "archived"): Archive or restore an item ```bash curl -X PATCH "https://xmails.si/api/v1/catalog/6650f1c2e9b1d4a012345678" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` ### Templates DIY email designs, catalog-grounded AI copy and consistent Nano Banana artwork. #### Delete a template `DELETE /api/v1/templates/{id}` · MCP tool `delete_template` · Full access · can't be undone, safe to repeat Permanently deletes an account-owned template. An active generation worker must finish or its lease expire first. Queued work skips deleted templates. Existing campaigns, guide revisions and image library assets are preserved. This cannot be undone. - `id` (path, string, required): The template's ID ```bash curl -X DELETE "https://xmails.si/api/v1/templates/6650f1c2e9b1d4a012345678" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Get generation guide `GET /api/v1/generation-guide` · MCP tool `get_generation_guide` · Full access · read-only, safe to repeat Returns this account's current Markdown generation guide and its last ten revisions. Recommendations reflect human feedback, not a measured quality score. No parameters. ```bash curl "https://xmails.si/api/v1/generation-guide" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Recommend template improvements `POST /api/v1/templates/{id}/feedback` · MCP tool `submit_template_feedback` · Full access · safe to repeat Saves one review of a ready AI template and rebuilds the account's versioned Markdown guide from its latest twenty reviews. Repeat the same review to recover an interrupted guide update. Existing templates stay unchanged; recommendations guide future generations. - `id` (path, string, required): The template's ID - `target` (body, "text" | "image" | "both", required): Whether the recommendation concerns copy, artwork or both - `rating` (body, "good" | "needs_work", required): Preserve a successful approach or request an improvement - `recommendation` (body, string, required): Concrete preferences to preserve or change in future generations ```bash curl -X POST "https://xmails.si/api/v1/templates/6650f1c2e9b1d4a012345678/feedback" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"target":"…","rating":"…","recommendation":"…"}' ``` #### Generate an improved version `POST /api/v1/templates/{id}/improve` · MCP tool `improve_template` · Full access · reaches outside XMails Creates a new AI template using the current account guide after reviewing a ready template. Keeps the original, links the new version to its parent, and snapshots the guide for reproducible retries. Uses the original brief and current catalog facts; nothing is sent. - `id` (path, string, required): The template's ID ```bash curl -X POST "https://xmails.si/api/v1/templates/6650f1c2e9b1d4a012345678/improve" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Create a DIY template `POST /api/v1/templates` · MCP tool `create_template` · Full access Creates an editable drag-and-drop email design with validated blocks and brand settings. Requires no catalog or AI service. HTML is generated on the server; nothing is sent. - `name` (body, string, required): A name for the reusable template - `design` (body, object, required): Version 1 email design with brand settings and up to 80 editable blocks - `design.version` (body, number, required) - `design.theme` (body, object, required) - `design.blocks` (body, object[], required) ```bash curl -X POST "https://xmails.si/api/v1/templates" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"…","design":"…"}' ``` #### List templates `GET /api/v1/templates` · MCP tool `list_templates` · Full access · read-only, safe to repeat Lists this account's reusable templates and AI generation progress, newest first. A ready template can become a campaign draft using create_campaign. - `limit` (query, integer): Templates per page, 50 by default - `cursor` (query, string): nextCursor from the previous page ```bash curl "https://xmails.si/api/v1/templates" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Get a template `GET /api/v1/templates/{id}` · MCP tool `get_template` · Full access · read-only, safe to repeat Returns an account-owned template, its editable email content, catalog snapshot and generation status. Poll this action while AI generation runs. - `id` (path, string, required): The template's ID ```bash curl "https://xmails.si/api/v1/templates/6650f1c2e9b1d4a012345678" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Generate an AI template `POST /api/v1/templates/generate` · MCP tool `generate_template` · Full access · reaches outside XMails Queues an AI template using account-owned catalog items and a brief. Gemini writes editable copy; optional Nano Banana artwork is stored in the account's image library. Returns immediately; poll get_template. Nothing is sent. - `name` (body, string, required): A name for the reusable template - `itemIds` (body, string[], required): One to twelve active catalog item IDs from this account, in display order - `style` (body, "editorial" | "minimal" | "bold"): An editorial, minimal, or bold layout - `brief` (body, string, required): Audience, goal, tone, language, and creative direction - `generateImage` (body, boolean): Generate a Nano Banana hero image; false by default ```bash curl -X POST "https://xmails.si/api/v1/templates/generate" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"…","itemIds":"…","brief":"…"}' ``` #### Retry template generation `POST /api/v1/templates/{id}/retry` · MCP tool `retry_template_generation` · Full access · reaches outside XMails Retries a failed AI template or a worker whose six-minute lease expired. Reuses completed copy and artwork stages rather than regenerating them. Nothing is sent. - `id` (path, string, required): The template's ID ```bash curl -X POST "https://xmails.si/api/v1/templates/6650f1c2e9b1d4a012345678/retry" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Update a template `PATCH /api/v1/templates/{id}` · MCP tool `update_template` · Full access · safe to repeat Saves a ready template's name, subject, preview text or content. DIY templates use structured design blocks and server-rendered HTML; other templates use sanitized rich content. Existing campaign drafts are independent. - `id` (path, string, required): The template's ID - `name` (body, string): A name for the reusable template - `subject` (body, string): The email subject - `previewText` (body, string): Inbox preview text - `design` (body, object): The editable design for a DIY template; cannot be combined with content - `design.version` (body, number, required) - `design.theme` (body, object, required) - `design.blocks` (body, object[], required) - `content` (body, string): Editable rich HTML content for templates without a structured design ```bash curl -X PATCH "https://xmails.si/api/v1/templates/6650f1c2e9b1d4a012345678" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` ### Campaigns Drafts, test emails, scheduling, sending, sending in steps and reports. #### List campaigns `GET /api/v1/campaigns` · MCP tool `list_campaigns` · Full access · read-only, safe to repeat Lists campaigns, newest activity first: sent issues by send date, scheduled ones by send time, drafts by last edit. Content is left out; get_campaign has it. - `newsletter` (query, string): Only this newsletter's: its ID or slug. Default: every newsletter. - `status` (query, "draft" | "scheduled" | "sending" | "paused" | "sent"): Only campaigns with this status - `limit` (query, integer): How many, 50 by default, 200 at most ```bash curl "https://xmails.si/api/v1/campaigns?status=sent&limit=10" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Get a campaign `GET /api/v1/campaigns/{id}` · MCP tool `get_campaign` · Full access · read-only, safe to repeat Gets a campaign with its content, stats and, for drafts, what would block sending it now. - `id` (path, string, required): The campaign's ID ```bash curl "https://xmails.si/api/v1/campaigns/6650f1c2e9b1d4a012345678" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Get a campaign's report `GET /api/v1/campaigns/{id}/report` · MCP tool `get_campaign_report` · Full access · read-only, safe to repeat Delivery, open, click, bounce, complaint and unsubscribe rates of a sent campaign, its list cleanup emails, results by inbox provider, and how many contacts a capped send hasn't reached yet. - `id` (path, string, required): The campaign's ID ```bash curl "https://xmails.si/api/v1/campaigns/6650f1c2e9b1d4a012345678/report" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Create a draft campaign `POST /api/v1/campaigns` · MCP tool `create_campaign` · Full access Creates a draft. Nothing is sent: review it in the dashboard or with get_campaign, then send_test_email, schedule_campaign or send_campaign. - `newsletter` (body, string): The newsletter's ID or slug. Default: the account's first newsletter, the one it has had longest. - `template` (body, string): A ready template ID from this account; its content, subject and preview text become an independent draft - `subject` (body, string): Subject line, 200 characters at most - `previewText` (body, string): Text inbox apps show after the subject, 300 characters at most - `format` (body, "markdown" | "rich" | "html"): markdown (the default when content is given), rich or html, as in update_campaign - `content` (body, string): The body in that format, 500 KB at most - `archiveSlug` (body, string): The issue's page path once sent. Default: made from the subject; a taken slug gets a number. ```bash curl -X POST "https://xmails.si/api/v1/campaigns" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"subject":"This week","previewText":"Three links worth your time","content":"# This week\n\nThree links worth your time."}' ``` #### Update a draft campaign `PATCH /api/v1/campaigns/{id}` · MCP tool `update_campaign` · Full access · safe to repeat Changes a draft or scheduled campaign's subject, preview text or content. Send only the fields to change; format goes with content. Sent campaigns can't change. - `id` (path, string, required): The campaign's ID - `subject` (body, string): Subject line, 200 characters at most - `previewText` (body, string): Text inbox apps show after the subject, 300 characters at most - `format` (body, "markdown" | "rich" | "html"): With content: the format it's in. Default: the campaign's current format. - `content` (body, string): The body in that format, 500 KB at most ```bash curl -X PATCH "https://xmails.si/api/v1/campaigns/6650f1c2e9b1d4a012345678" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"subject":"This week, and one more link"}' ``` #### Delete a campaign `DELETE /api/v1/campaigns/{id}` · MCP tool `delete_campaign` · Full access · can't be undone, safe to repeat Deletes a draft, scheduled, paused or sent campaign from lists and the public archive. Pause actively sending campaigns first. Delivery records remain for analytics, billing and suppression; sent emails cannot be recalled. - `id` (path, string, required): The campaign's ID ```bash curl -X DELETE "https://xmails.si/api/v1/campaigns/6650f1c2e9b1d4a012345678" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Duplicate a campaign `POST /api/v1/campaigns/{id}/duplicate` · MCP tool `duplicate_campaign` · Full access Copies a campaign's subject, preview text and content into a new draft. - `id` (path, string, required): The campaign's ID ```bash curl -X POST "https://xmails.si/api/v1/campaigns/6650f1c2e9b1d4a012345678/duplicate" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Send a test email `POST /api/v1/campaigns/{id}/test` · MCP tool `send_test_email` · Full access · reaches outside XMails Sends the campaign as saved to the account owner's address, subject prefixed with [Test]. Nobody else gets it. It counts toward the plan's emails. - `id` (path, string, required): The campaign's ID ```bash curl -X POST "https://xmails.si/api/v1/campaigns/6650f1c2e9b1d4a012345678/test" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Schedule a campaign `POST /api/v1/campaigns/{id}/schedule` · MCP tool `schedule_campaign` · Full access · can't be undone, safe to repeat, reaches outside XMails Schedules a draft to send by itself at a time within a year. It goes out without asking again, unless unschedule_campaign runs first. Sending can't be undone: emails can't be recalled once they go out. Ask the user to confirm first. Plan limits and pre-send checks run now and again when it goes out. XMails Cloud paces a send by the volume its account has proven: its largest campaign of the last 60 days whose bounces and complaints stayed under the pause level, counted a day after it went out. A send, with what the account's other sends still have to send or sent in the last 24 hours, goes at full speed up to 200 emails, or that volume times 2 on the first day of the account's 30-day warm-up, rising to 5 by its last day and after it. A bigger send goes out over 4 hours plus 4 for each time it doubles that, 72 at most (spreadHours, sendingUntil); a resume keeps the pace, each step of send_campaign_to_more gets its own. Contacts never mailed before go in steps: a slice first, the rest 3 hours after it if the slice's bounces and complaints stay under the pause level. Past 2.00% hard bounces (at least 5) or 0.08% complaints (at least 2), the campaign pauses with pauseCode high_rates, or first_send_slice for a slice, and resume_campaign sends the rest. Past 5.00% (at least 10) or 0.30% (at least 3), the account is blocked. - `id` (path, string, required): The campaign's ID - `scheduledAt` (body, string, required): ISO 8601 date and time, e.g. 2026-10-20T09:00:00Z, at least a minute ahead - `audienceLimit` (body, integer): Send to this many contacts at most, the most engaged first. The rest can follow with send_campaign_to_more. ```bash curl -X POST "https://xmails.si/api/v1/campaigns/6650f1c2e9b1d4a012345678/schedule" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"scheduledAt":"2026-10-20T09:00:00Z"}' ``` #### Cancel a scheduled send `POST /api/v1/campaigns/{id}/unschedule` · MCP tool `unschedule_campaign` · Full access · safe to repeat Turns a scheduled campaign back into a draft, so it doesn't go out. - `id` (path, string, required): The campaign's ID ```bash curl -X POST "https://xmails.si/api/v1/campaigns/6650f1c2e9b1d4a012345678/unschedule" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Send a campaign now `POST /api/v1/campaigns/{id}/send` · MCP tool `send_campaign` · Full access · can't be undone, reaches outside XMails Sends a draft or scheduled campaign now to the newsletter's active contacts, most engaged first. Sending can't be undone: emails can't be recalled once they go out. Ask the user to confirm first. A campaign that is already sending or sent answers conflict, so a retry never sends twice. Plan limits, the from address, the postal address and the email size are checked first. XMails Cloud paces a send by the volume its account has proven: its largest campaign of the last 60 days whose bounces and complaints stayed under the pause level, counted a day after it went out. A send, with what the account's other sends still have to send or sent in the last 24 hours, goes at full speed up to 200 emails, or that volume times 2 on the first day of the account's 30-day warm-up, rising to 5 by its last day and after it. A bigger send goes out over 4 hours plus 4 for each time it doubles that, 72 at most (spreadHours, sendingUntil); a resume keeps the pace, each step of send_campaign_to_more gets its own. Contacts never mailed before go in steps: a slice first, the rest 3 hours after it if the slice's bounces and complaints stay under the pause level. Past 2.00% hard bounces (at least 5) or 0.08% complaints (at least 2), the campaign pauses with pauseCode high_rates, or first_send_slice for a slice, and resume_campaign sends the rest. Past 5.00% (at least 10) or 0.30% (at least 3), the account is blocked. - `id` (path, string, required): The campaign's ID - `audienceLimit` (body, integer): Send to this many contacts at most, the most engaged first. The rest can follow with send_campaign_to_more. ```bash curl -X POST "https://xmails.si/api/v1/campaigns/6650f1c2e9b1d4a012345678/send" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Send a campaign to more contacts `POST /api/v1/campaigns/{id}/send-more` · MCP tool `send_campaign_to_more` · Full access · can't be undone, reaches outside XMails Sends a sent campaign to contacts who haven't received it yet, the next most engaged first: a send in steps. Sending can't be undone: emails can't be recalled once they go out. Ask the user to confirm first. Each call starts a new step, so check get_campaign_report before calling it again; while a step is still sending it answers conflict. XMails Cloud paces a send by the volume its account has proven: its largest campaign of the last 60 days whose bounces and complaints stayed under the pause level, counted a day after it went out. A send, with what the account's other sends still have to send or sent in the last 24 hours, goes at full speed up to 200 emails, or that volume times 2 on the first day of the account's 30-day warm-up, rising to 5 by its last day and after it. A bigger send goes out over 4 hours plus 4 for each time it doubles that, 72 at most (spreadHours, sendingUntil); a resume keeps the pace, each step of send_campaign_to_more gets its own. Contacts never mailed before go in steps: a slice first, the rest 3 hours after it if the slice's bounces and complaints stay under the pause level. Past 2.00% hard bounces (at least 5) or 0.08% complaints (at least 2), the campaign pauses with pauseCode high_rates, or first_send_slice for a slice, and resume_campaign sends the rest. Past 5.00% (at least 10) or 0.30% (at least 3), the account is blocked. - `id` (path, string, required): The campaign's ID - `additional` (body, integer): How many more contacts. Default: everyone who hasn't received it. ```bash curl -X POST "https://xmails.si/api/v1/campaigns/6650f1c2e9b1d4a012345678/send-more" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"additional":2000}' ``` #### Pause a campaign `POST /api/v1/campaigns/{id}/pause` · MCP tool `pause_campaign` · Full access · safe to repeat Stops a campaign that is sending. Emails already sent stay sent; the rest wait until resume_campaign. - `id` (path, string, required): The campaign's ID ```bash curl -X POST "https://xmails.si/api/v1/campaigns/6650f1c2e9b1d4a012345678/pause" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Resume a paused campaign `POST /api/v1/campaigns/{id}/resume` · MCP tool `resume_campaign` · Full access · can't be undone, reaches outside XMails Sends the rest of a paused campaign. Sending can't be undone: emails can't be recalled once they go out. Ask the user to confirm first. The plan limits and the account's state are checked again first. Resumed after a high_rates or first_send_slice pause, only the block level stops it again. - `id` (path, string, required): The campaign's ID ```bash curl -X POST "https://xmails.si/api/v1/campaigns/6650f1c2e9b1d4a012345678/resume" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Update a campaign's archive page `PATCH /api/v1/campaigns/{id}/archive` · MCP tool `update_campaign_archive` · Full access · safe to repeat Changes the path of a campaign's archive page (/p/), or pins a sent issue to the top of the archive. Works on sent campaigns too. - `id` (path, string, required): The campaign's ID - `slug` (body, string): Lowercase letters, digits and dashes. Empty removes it from a draft. - `pinned` (body, boolean): Pinned issues lead the archive ```bash curl -X PATCH "https://xmails.si/api/v1/campaigns/6650f1c2e9b1d4a012345678/archive" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"pinned":true}' ``` #### Preview the list cleanup of a send `GET /api/v1/campaigns/{id}/cleanup-preview` · MCP tool `preview_campaign_cleanup` · Full access · read-only, safe to repeat Counts what the newsletter's list cleanup would do if the campaign went out now: issues sent, last-chance emails instead, contacts still waiting to answer one, and contacts made dormant. Reads the whole list, so it can take a few seconds. - `id` (path, string, required): The campaign's ID - `audienceLimit` (query, integer): Count for a send capped at this many contacts - `includeRanks` (query, boolean): Also return, for each cold contact, how many issue recipients come before it, to recount any limit without asking again ```bash curl "https://xmails.si/api/v1/campaigns/6650f1c2e9b1d4a012345678/cleanup-preview" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` ### Contacts Contact groups, search, signups, imports, status changes, deletes and filtered exports. #### List contacts `GET /api/v1/contacts` · MCP tool `list_contacts` · Full access · read-only, safe to repeat Lists contacts, newest signups first, 50 a page. Filter by newsletter, contact group, status or the start of the email address; pass nextCursor back as cursor for the next page. counts has every status for the same filters. - `newsletter` (query, string): Only this newsletter's: its ID or slug. Default: every newsletter. - `group` (query, string): An owned contact group ID or everyone; Everyone requires newsletter - `status` (query, "active" | "pending" | "unsubscribed" | "bounced" | "complained" | "dormant"): Only contacts with this status - `search` (query, string): Contacts whose email starts with this - `limit` (query, integer): Contacts per page, 50 by default, 100 at most - `cursor` (query, string): nextCursor from the previous page ```bash curl "https://xmails.si/api/v1/contacts?status=active&search=jane" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Get a contact `GET /api/v1/contacts/{id}` · MCP tool `get_contact` · Full access · read-only, safe to repeat Gets one contact by ID or email address, with its status, dates and engagement. - `id` (path, string, required): The contact's ID or email address - `newsletter` (query, string): The newsletter's ID or slug, needed for an email address that is a contact of several newsletters ```bash curl "https://xmails.si/api/v1/contacts/6650f1c2e9b1d4a012345678" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Add a contact `POST /api/v1/contacts` · MCP tool `add_contact` · Full access or signups only · reaches outside XMails Signs someone up, like the archive's form: they get the welcome email, or the confirmation email when the newsletter asks new subscribers to confirm. Signing up again keeps the contact as it is unless resubscribe is true. Works with signups only keys, for website forms. Signups and confirmation emails keep coming in over the plan's limits; welcome emails count toward the month's emails and are skipped once they're used up, never sent later (get_billing's usage.welcomeEmailsPaused). A misspelled domain (gmial.com) answers validation_failed with the address it was likely meant to be; spam trap addresses, domains that can't receive email, and addresses XMails knows can't receive email (they hard bounced or hit a spam trap, from any sender on XMails) answer validation_failed too. - `newsletter` (body, string): The newsletter's ID or slug. Default: the account's first newsletter, the one it has had longest. - `email` (body, string, required): The subscriber's email address - `name` (body, string): The contact's display name - `source` (body, string): Where they signed up, e.g. website. Default: api. - `resubscribe` (body, boolean): Bring back a contact who unsubscribed or went dormant. Bounced and complained addresses never come back, nor does a contact deleted after it unsubscribed. - `ip` (body, string): The visitor's IP address: one visitor gets 5 signups in 10 minutes ```bash curl -X POST "https://xmails.si/api/v1/contacts" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"email":"jane@example.com","source":"website"}' ``` #### Edit contact details `PATCH /api/v1/contacts/{id}/profile` · MCP tool `update_contact` · Full access · safe to repeat Updates an account-owned contact's name, source and private notes. Email identifies the subscription and remains unchanged, as do consent, engagement, group membership and suppression status. No email is sent. - `id` (path, string, required): The contact's ID or email address - `newsletter` (body, string): The newsletter's ID or slug, needed for an email address that is a contact of several newsletters - `name` (body, string): The contact's display name; empty clears it - `source` (body, string): Where the contact came from; empty clears it - `notes` (body, string): Private notes for this contact; empty clears them ```bash curl -X PATCH "https://xmails.si/api/v1/contacts/6650f1c2e9b1d4a012345678/profile" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Unsubscribe or reactivate a contact `PATCH /api/v1/contacts/{id}` · MCP tool `update_contact_status` · Full access · can't be undone, safe to repeat unsubscribed stops all emails to the contact, for good: only the person signing up again brings them back. active brings back a dormant contact; unsubscribes, bounces and complaints are final. - `id` (path, string, required): The contact's ID or email address - `newsletter` (body, string): The newsletter's ID or slug, needed for an email address that is a contact of several newsletters - `status` (body, "active" | "unsubscribed", required): active (dormant contacts only) or unsubscribed ```bash curl -X PATCH "https://xmails.si/api/v1/contacts/6650f1c2e9b1d4a012345678" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"status":"unsubscribed"}' ``` #### Delete a contact `DELETE /api/v1/contacts/{id}` · MCP tool `delete_contact` · Full access · can't be undone, safe to repeat Deletes a contact from its newsletter, with its dates and engagement. It can't be undone. For a contact who unsubscribed, bounced or complained, XMails keeps a hash of the address so the newsletter never emails it again: import_contacts leaves it out, and add_contact or a signup form adds it back with that status. Any other contact can be added again like a new one. - `id` (path, string, required): The contact's ID or email address - `newsletter` (query, string): The newsletter's ID or slug, needed for an email address that is a contact of several newsletters ```bash curl -X DELETE "https://xmails.si/api/v1/contacts/6650f1c2e9b1d4a012345678" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Import contacts `POST /api/v1/contacts/import` · MCP tool `import_contacts` · Full access · can't be undone, safe to repeat Adds up to 1000 contacts per call, with the same checks as the dashboard's CSV import: invalid, misspelled (gmial.com…), spam trap, role (info@, support@…), throwaway, duplicate and suppressed addresses, undeliverable ones (XMails knows they can't receive email: they hard bounced or hit a spam trap, from any sender on XMails), and addresses on domains that can't receive email, are left out and counted. consent must be true: "These people agreed to receive my emails". Existing contacts keep their status unless a row gives a stricter one (unsubscribed, bounced, complained), which is final. No welcome emails go out. For more rows, send the next chunk with the importId of the first. - `newsletter` (body, string): The newsletter's ID or slug. Default: the account's first newsletter, the one it has had longest. - `consent` (body, boolean, required): Must be true: These people agreed to receive my emails. Bought or scraped lists aren't allowed. - `contacts` (body, object[], required): The rows, 1000 at most - `contacts[].email` (body, string, required): Invalid addresses are left out and counted - `contacts[].status` (body, "active" | "dormant" | "unsubscribed" | "bounced" | "complained"): active (default), dormant, unsubscribed, bounced or complained - `contacts[].subscribedAt` (body, string): When they signed up, ISO 8601 - `contacts[].emailsReceived` (body, number): Emails they got on the previous platform - `contacts[].openRate` (body, number): 0 to 1, from the previous platform: sends reach the most engaged first - `contacts[].clickRate` (body, number): 0 to 1, from the previous platform - `importId` (body, string): The importId of the first chunk, to add rows to the same import - `duplicates` (body, integer): Duplicates you removed before sending, counted in the import's report ```bash curl -X POST "https://xmails.si/api/v1/contacts/import" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"consent":true,"contacts":[{"email":"jane@example.com","subscribedAt":"2025-03-01T00:00:00Z"},{"email":"sam@example.com","status":"unsubscribed"}]}' ``` #### Export contacts `GET /api/v1/contacts/export` · MCP tool `export_contacts` · Full access · read-only, safe to repeat Makes a CSV download link for the contacts matching the filters, valid for an hour, no API key needed to open it. The columns go back in through the import as they are. - `newsletter` (query, string): Only this newsletter's: its ID or slug. Default: every newsletter. - `group` (query, string): An owned contact group ID or everyone; Everyone requires newsletter - `status` (query, "active" | "pending" | "unsubscribed" | "bounced" | "complained" | "dormant"): Only contacts with this status - `search` (query, string): Contacts whose email starts with this ```bash curl "https://xmails.si/api/v1/contacts/export?status=active" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### List contact groups `GET /api/v1/contact-groups` · MCP tool `list_contact_groups` · Full access · read-only, safe to repeat Lists this newsletter's groups and counts. Ensures its automatic Everyone group exists; Everyone includes new contacts automatically, regardless of subscription status. - `newsletter` (query, string): The newsletter's ID or slug. Default: the account's first newsletter, the one it has had longest. ```bash curl "https://xmails.si/api/v1/contact-groups" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Create a contact group `POST /api/v1/contact-groups` · MCP tool `create_contact_group` · Full access Creates a custom group inside an owned newsletter. Everyone is reserved. Contacts may belong to multiple custom groups; group membership never changes subscription or suppression status. - `newsletter` (body, string): The newsletter's ID or slug. Default: the account's first newsletter, the one it has had longest. - `name` (body, string, required): A unique group name within this newsletter, up to 80 characters ```bash curl -X POST "https://xmails.si/api/v1/contact-groups" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"…"}' ``` #### Rename a contact group `PATCH /api/v1/contact-groups/{id}` · MCP tool `rename_contact_group` · Full access Renames an owned custom group. Everyone cannot be renamed. - `id` (path, string, required): An account-owned contact group ID - `name` (body, string, required): A unique group name within this newsletter, up to 80 characters ```bash curl -X PATCH "https://xmails.si/api/v1/contact-groups/6650f1c2e9b1d4a012345678" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"…"}' ``` #### Delete a contact group `DELETE /api/v1/contact-groups/{id}` · MCP tool `delete_contact_group` · Full access · can't be undone Deletes a custom group and its memberships. Contacts and their subscription/suppression states are kept. Everyone cannot be deleted. - `id` (path, string, required): An account-owned contact group ID ```bash curl -X DELETE "https://xmails.si/api/v1/contact-groups/6650f1c2e9b1d4a012345678" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Update contact group members `PATCH /api/v1/contact-groups/{id}/members` · MCP tool `update_contact_group_members` · Full access Adds or removes up to 200 contact IDs in an owned custom group. Every contact must belong to its newsletter and account. Everyone is automatic; membership never resubscribes anyone. - `id` (path, string, required): An account-owned contact group ID - `contactIds` (body, string[], required): One to 200 contact IDs from the group’s own newsletter - `operation` (body, "add" | "remove", required): Add contacts to this group or remove their membership ```bash curl -X PATCH "https://xmails.si/api/v1/contact-groups/6650f1c2e9b1d4a012345678/members" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"contactIds":"…","operation":"…"}' ``` ### Domains Own sending domains with their DNS records, and the shared address. #### List domains `GET /api/v1/domains` · MCP tool `list_domains` · Full access · read-only, safe to repeat Lists the account's own sending domains with their DNS records and status as last checked, and each newsletter's shared address (hello@.), which works with no setup. No parameters. ```bash curl "https://xmails.si/api/v1/domains" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Add a domain `POST /api/v1/domains` · MCP tool `add_domain` · Full access · reaches outside XMails Adds a domain to send from, in the account's own tenant at the email provider: emails are signed with DKIM and bounces return to a subdomain of it (rp. on PostShiba, bounce. on Amazon SES). The answer lists the DNS records to add; check_domain reads them once they're in. Newsletters can send from it once it's verified. - `domain` (body, string, required): e.g. example.com. An address or a link works too: jane@example.com and https://www.example.com/about both add example.com. - `archiveHost` (body, string): Used when the domain's setup lists an archive step: the subdomain for the archive, tracked links and unsubscribe links. Default: news.. ```bash curl -X POST "https://xmails.si/api/v1/domains" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"domain":"example.com"}' ``` #### Check a domain's DNS records `POST /api/v1/domains/{id}/check` · MCP tool `check_domain` · Full access · safe to repeat, reaches outside XMails Asks the email provider and DNS again whether the domain's records are in, and looks up its DMARC record or its parent domain's. On PostShiba, a check within a minute of the last one answers what that one found. When its setup lists an archive step, the newsletter sending from the domain moves its archive and links to that subdomain once it points at XMails. - `id` (path, string, required): The domain's ID ```bash curl -X POST "https://xmails.si/api/v1/domains/6650f1c2e9b1d4a012345678/check" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Remove a domain `DELETE /api/v1/domains/{id}` · MCP tool `remove_domain` · Full access · can't be undone, safe to repeat, reaches outside XMails Removes a domain from the email provider, and its archive subdomain from XMails when its setup lists one. Newsletters on it can't send until they move to another address. Refused while a campaign from it is sending, paused or scheduled. Adding it again needs new DNS records. - `id` (path, string, required): The domain's ID ```bash curl -X DELETE "https://xmails.si/api/v1/domains/6650f1c2e9b1d4a012345678" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Start Cloudflare authorization `POST /api/v1/domains/{id}/cloudflare/authorize` · MCP tool `start_cloudflare_authorization` · Full access · reaches outside XMails Starts Cloudflare sign-in for an account-owned sending domain, with PKCE and a ten-minute single-use state. The browser connect endpoint sets the matching secure cookie. Authorizing grants setup permission to add only missing email-provider DNS records after provisioning SES. - `id` (path, string, required): The domain's ID ```bash curl -X POST "https://xmails.si/api/v1/domains/6650f1c2e9b1d4a012345678/cloudflare/authorize" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Finish Cloudflare authorization `POST /api/v1/domains/cloudflare/callback` · MCP tool `finish_cloudflare_authorization` · Full access · reaches outside XMails Completes Cloudflare authorization using its code and the browser's matching state cookie. Discovers the authorized zone, encrypts access and refresh tokens per account and domain, provisions missing email-provider setup, adds nonconflicting DNS records, and checks verification. Never returns credentials. A connected domain may still need DNS conflict resolution or administrator attention. - `state` (body, string, required): Single-use state returned by Cloudflare - `browserState` (body, string, required): Matching state from the secure browser cookie - `code` (body, string): Cloudflare authorization code - `error` (body, string): Authorization error returned when consent was declined ```bash curl -X POST "https://xmails.si/api/v1/domains/cloudflare/callback" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"state":"…","browserState":"…"}' ``` #### Get Cloudflare connection `GET /api/v1/domains/{id}/cloudflare` · MCP tool `get_cloudflare_connection` · Full access · read-only, safe to repeat Returns the account-owned domain's Cloudflare connection status. API tokens and encrypted credentials are never returned. - `id` (path, string, required): The domain's ID ```bash curl "https://xmails.si/api/v1/domains/6650f1c2e9b1d4a012345678/cloudflare" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Connect Cloudflare `POST /api/v1/domains/{id}/cloudflare` · MCP tool `connect_cloudflare` · Full access · reaches outside XMails Connects an owned sending domain to an active Cloudflare zone using a zone-scoped token with Zone Read and DNS Edit. Validates zone ownership and read access, encrypts the token per account and domain, and makes no DNS changes. Never returns the token. - `id` (path, string, required): The domain's ID - `zoneId` (body, string, required): Cloudflare Zone ID shown on the domain's Overview page - `apiToken` (body, string, required): Secret API token scoped to this zone with Zone Read and DNS Edit permissions ```bash curl -X POST "https://xmails.si/api/v1/domains/6650f1c2e9b1d4a012345678/cloudflare" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"zoneId":"…","apiToken":"…"}' ``` #### Preview Cloudflare DNS setup `POST /api/v1/domains/{id}/cloudflare/preview` · MCP tool `preview_cloudflare_dns` · Full access · reaches outside XMails Reads Cloudflare DNS and previews missing SES or PostShiba authentication and optional archive records. Preserves valid existing or inherited DMARC policies, reports conflicts, and makes no DNS changes. - `id` (path, string, required): The domain's ID ```bash curl -X POST "https://xmails.si/api/v1/domains/6650f1c2e9b1d4a012345678/cloudflare/preview" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Configure Cloudflare records `POST /api/v1/domains/{id}/cloudflare/apply` · MCP tool `apply_cloudflare_dns` · Full access · reaches outside XMails Rechecks and adds only missing provider-generated DNS records to the connected zone, with CNAME proxying disabled. Refuses conflicting plans, never updates or deletes existing records, serializes simultaneous applies, and checks provider verification afterward. Retry safely after partial failure; DNS propagation may take time. - `id` (path, string, required): The domain's ID ```bash curl -X POST "https://xmails.si/api/v1/domains/6650f1c2e9b1d4a012345678/cloudflare/apply" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Disconnect Cloudflare `DELETE /api/v1/domains/{id}/cloudflare` · MCP tool `disconnect_cloudflare` · Full access · safe to repeat Removes the saved encrypted Cloudflare token for this account-owned domain. Existing DNS records are left in place and the token can be revoked in Cloudflare. Wait for active DNS setup to finish first. - `id` (path, string, required): The domain's ID ```bash curl -X DELETE "https://xmails.si/api/v1/domains/6650f1c2e9b1d4a012345678/cloudflare" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` ### Images Pictures for campaign content, on XMails’ CDN, kept in the account's library to use again. #### List images `GET /api/v1/images` · MCP tool `list_images` · Full access · read-only, safe to repeat Lists the images uploaded to the account, newest first, 50 a page, so a campaign can use one again without uploading it twice. Pass nextCursor back as cursor for the next page. - `limit` (query, integer): Images per page, 50 by default, 100 at most - `cursor` (query, string): nextCursor from the previous page ```bash curl "https://xmails.si/api/v1/images?limit=20" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Upload an image `POST /api/v1/images` · MCP tool `upload_image` · Full access · reaches outside XMails Stores a PNG, JPEG, GIF or WebP image under 4 MB on XMails’ CDN and returns its URL for campaign content. Send the file as base64 (under about 3 MB, since requests stop at 4.5 MB) or an https:// link XMails downloads. The URL never changes: sent emails keep pointing at it. list_images finds images uploaded before. - `base64` (body, string): The image file, base64 encoded; a data: URL works too - `url` (body, string): An https:// link to the image - `fileName` (body, string): Used in the stored file's name - `contentType` (body, string): The file's type, checked against its first bytes ```bash curl -X POST "https://xmails.si/api/v1/images" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com/cover.png"}' ``` ### API keys Keys for the REST API and MCP, plus account-scoped image-model preferences using the shared Gemini connection. #### List API keys `GET /api/v1/api-keys` · MCP tool `list_api_keys` · Full access · read-only, safe to repeat Lists the account's API keys, revoked ones included, with their level and when they were last used. Keys themselves are never shown again. No parameters. ```bash curl "https://xmails.si/api/v1/api-keys" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Create an API key `POST /api/v1/api-keys` · MCP tool `create_api_key` · Full access Creates an API key and returns it once; only its hash is stored. full can do everything a full key can, including creating more keys; signups can only add contacts, for keys that end up in website code. An account has up to 20 active keys: one more answers conflict until one is revoked. - `name` (body, string, required): What the key is for, e.g. Website signups - `level` (body, "full" | "signups", required): full or signups ```bash curl -X POST "https://xmails.si/api/v1/api-keys" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Website signups","level":"signups"}' ``` #### Revoke an API key `DELETE /api/v1/api-keys/{id}` · MCP tool `revoke_api_key` · Full access · can't be undone, safe to repeat Revokes an API key for good: requests with it answer unauthorized from now on. A key can revoke itself. - `id` (path, string, required): The API key's ID ```bash curl -X DELETE "https://xmails.si/api/v1/api-keys/6650f1c2e9b1d4a012345678" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Get AI settings `GET /api/v1/ai-settings` · MCP tool `get_ai_settings` · Full access · read-only, safe to repeat Returns this account's Gemini models and reference-image settings, plus the shared server connection status. The server API key is never returned. No parameters. ```bash curl "https://xmails.si/api/v1/ai-settings" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Save AI settings `PATCH /api/v1/ai-settings` · MCP tool `update_ai_settings` · Full access Saves account-scoped model and visual preferences. All generation uses the shared server Gemini key; customers cannot supply keys. Changes apply to new templates; queued templates retain their models and visual brief. No provider request is made. - `textModel` (body, string): Gemini model ID used to write newsletter copy - `imageModel` (body, string): Nano Banana / Gemini image model ID used for artwork - `artDirection` (body, string): Brand colors, lighting, composition and visual identity to reuse across newsletter artwork - `aspectRatio` (body, "16:9" | "4:3" | "1:1"): Aspect ratio of generated newsletter artwork - `referenceLimit` (body, integer): Maximum selected product/service images to combine, 3 by default ```bash curl -X PATCH "https://xmails.si/api/v1/ai-settings" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### List connected apps `GET /api/v1/connected-apps` · MCP tool `list_connected_apps` · Full access · read-only, safe to repeat Lists the apps connected to the account with OAuth, such as ChatGPT, Claude and Grok Bot, with when they were connected and last used. Each one has full access, like a full access API key. No parameters. ```bash curl "https://xmails.si/api/v1/connected-apps" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Disconnect an app `DELETE /api/v1/connected-apps/{id}` · MCP tool `revoke_connected_app` · Full access · can't be undone, safe to repeat Disconnects an app connected with OAuth: its tokens stop working at once, and it has to ask for access again to come back. An app can disconnect itself. - `id` (path, string, required): The connected app's ID ```bash curl -X DELETE "https://xmails.si/api/v1/connected-apps/6650f1c2e9b1d4a012345678" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` ### Billing Plan, usage and links to Stripe to change plan. #### Get the plan and usage `GET /api/v1/billing` · MCP tool `get_billing` · Full access · read-only, safe to repeat The account's plan, subscription status and this month's usage against the plan's limits, with every plan on sale for comparison. Works without an active plan. A self-hosted server has no billing and answers billing_off. No parameters. ```bash curl "https://xmails.si/api/v1/billing" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ``` #### Change plan `POST /api/v1/billing/change-plan` · MCP tool `change_plan` · Full access · reaches outside XMails Returns a Stripe page where the account owner picks up the change: Checkout without a paid plan (on a free plan too: the paid plan takes over), the Customer Portal's confirmation page with one. Nothing changes until they confirm there. Give the URL to the user. A self-hosted server has no billing and answers billing_off. - `plan` (body, "starter" | "growth" | "pro" | "scale" | "max", required): starter, growth, pro, scale or max - `interval` (body, "month" | "year"): month (default) or year, which costs 10 months ```bash curl -X POST "https://xmails.si/api/v1/billing/change-plan" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" \ -H "Content-Type: application/json" \ -d '{"plan":"growth","interval":"year"}' ``` #### Open the billing portal `POST /api/v1/billing/portal` · MCP tool `open_billing_portal` · Full access · reaches outside XMails Returns a Stripe Customer Portal link for the account owner: payment method, invoices, plan changes and cancellation. A self-hosted server has no billing and answers billing_off. No parameters. ```bash curl -X POST "https://xmails.si/api/v1/billing/portal" \ -H "Authorization: Bearer $MAILCHEAP_API_KEY" ```