mailrelay-mcp-server

An unofficial Model Context Protocol (MCP) server for the Mailrelay.com email marketing API. It lets AI assistants like Claude manage groups, subscribers, tags, senders, custom fields, and campaigns in your own Mailrelay account.

Not affiliated with Mailrelay. This is an independent, community-built integration against Mailrelay's public REST API. You need your own Mailrelay account and API key to use it.

Features

173 tools covering the whole of Mailrelay's REST API (172 operations), in eight toolsets so a connection only carries what it needs (see Available tools).

Requirements

Installation

Option A: npx (no install)

Run it directly without installing, pointing your MCP client at:

npx mailrelay-mcp-server

Option B: from source

git clone https://github.com/YOUR_GITHUB_USERNAME/mailrelay-mcp-server.git
cd mailrelay-mcp-server
npm install

Configuration

The server reads these environment variables:

Variable Description
MAILRELAY_BASE_URL Your account's base URL, e.g. https://youraccount.ipzmarketing.com
MAILRELAY_API_KEY Your Mailrelay API key
MAILRELAY_TOOLSETS Optional. Extra toolsets to switch on besides core, e.g. sms,whatsapp, or all

If running from source, copy .env.example to .env and fill in your values:

cp .env.example .env

Usage with Claude Code

Register the server with the Claude Code CLI:

claude mcp add mailrelay -e MAILRELAY_BASE_URL=https://youraccount.ipzmarketing.com -e MAILRELAY_API_KEY=your-api-key -- npx mailrelay-mcp-server

Or add it manually to your MCP configuration (.mcp.json, or claude_desktop_config.json for Claude Desktop):

{
  "mcpServers": {
    "mailrelay": {
      "command": "npx",
      "args": ["mailrelay-mcp-server"],
      "env": {
        "MAILRELAY_BASE_URL": "https://youraccount.ipzmarketing.com",
        "MAILRELAY_API_KEY": "your-api-key"
      }
    }
  }
}

If you installed from source, replace the command/args with "node" / ["/absolute/path/to/mailrelay-mcp-server/src/index.js"].

Available tools

The server has 173 tools. They are grouped into toolsets because the name and description of every tool is sent to the model at the start of each conversation: all 173 together cost about 34,000 tokens of context, and a very long tool list makes a model choose worse. So core is always on (79 tools, about 14,000 tokens) and the rest are opt-in.

Toolset Tools What it covers
core (always on) 79 Subscribers, groups, tags, custom fields, senders, campaigns, A/B tests, imports, folders; reports and send control for sent campaigns; unsubscribes, bounces, account stats
transactional 4 Send individual emails through the SMTP API; review the ones sent
media 12 The media library: files and folders
rss 6 Campaigns that email new entries of an RSS feed on a schedule
automation 9 Signup forms, event subscriptions (webhooks / email alerts), API batches
sms 16 Send SMS, SMS campaigns and their delivery results
whatsapp 24 WhatsApp templates, phone numbers, campaigns and messages
ecommerce 23 Stores, products, product categories and carts

Enabling more toolsets

npm run list-tools prints every tool by toolset with its safety level and context cost (-- --markdown for tables). It is built from the server itself, so it can't go stale.

What core contains

Filters, sorting and paging

Every list tool returns { pagination, items } — the pagination block carries total_records and total_pages, so a client can tell whether it has seen everything.

The tools built from the API definition take page, per_page, sort (for example created_at desc) and a filters object whose keys are Mailrelay's own filter names, such as { "email_cont": "gmail", "created_at_gteq": "2026-01-01" }. The tool description lists the names each endpoint accepts, and an unknown name is rejected instead of being sent: Mailrelay's API silently ignores a filter it doesn't know and returns everything, which looks exactly like success.

Every tool is strict in the same way: an argument it doesn't define is an error, never quietly dropped.

Usage instructions and safety annotations

Beyond the tools themselves, the server sends the client two things that shape how a model uses it.

Server instructions, delivered at initialize and usually placed in the model's context, document the parts of Mailrelay's API that are easy to get wrong: that sending is irreversible, that target has no "everyone" value (pass groups with every group id), that status: 'active' skips double opt-in and can't be changed later, that drafts and sent campaigns are separate endpoints, how to supply an import's CSV (base64 when remote), which toolsets are enabled, and that a send that went out by mistake can be paused.

Tool annotations tell the client how risky each tool is, so it can decide what to confirm. Across all 173 tools:

Class Count Examples
readOnlyHint 76 every list_* and get_*, plus ping, stats and reports
destructiveHint 38 every delete_*; every cancel; send_campaign; anything that sends SMS, WhatsApp or transactional email; bulk changes and API batches
neither 59 create_*, update_*, sync_*, pause_*, resume_*, send_test_campaign, start_import

"Destructive" here means irreversible, not only "deletes data": the A/B test tools and the send tools are marked so because they cause real sends. RSS campaign tools are marked too, because an enabled RSS campaign emails its audience on a schedule with no further call. Tools that send real messages also say so in their description (THIS SENDS REAL MESSAGES). A test checks all of this, and registration throws at startup if a hand-written tool has no annotation, so a new tool can't ship unlabelled.

Prompts

Five workflows are exposed as MCP prompts, so they appear in the client's prompt menu with the ordering and the safety rules already built in:

Prompt What it does
audit_list_health Read-only review of a group or the whole account: status breakdown, bounces, spam reports, bans, and what to do about them.
campaign_report Interprets a send's statistics with rates computed against the right denominators, compared to previous sends, and drills into clicks, opens and unsubscribes.
stop_sending Pauses a send that went out by mistake and reports how far it got. Pauses; never cancels unless the user asks.
draft_campaign Turns a brief into a draft and a test send. Explicitly forbidden from calling mailrelay_send_campaign.
reengage_inactive Finds subscribers who never completed double opt-in, excludes bounced and banned addresses, and asks before sending reminders.

Manual testing

npm start

The server speaks MCP over stdio — it isn't meant to be run standalone in an interactive terminal, but launched by an MCP client (Claude Code, Claude Desktop, etc.).

Running it remotely (HTTP mode)

Besides stdio, the server can run as an HTTP service that clients connect to by URL. In this mode it is multi-tenant: it holds no Mailrelay credentials of its own, and every user connects with their own account.

Connecting runs a standard OAuth 2.1 handshake. Mailrelay's own API offers no OAuth — its only scheme is an X-AUTH-TOKEN API key — so there is nothing to delegate to. Instead the authorize step shows a consent screen asking for the user's Mailrelay account URL and API key. Those are verified against Mailrelay before anything is granted, and the access token issued back is an opaque, server-generated value bound to that one account. Every subsequent request talks to the account that authorized it.

Sessions are encrypted at rest. Because they carry users' API keys, they're stored with AES-256-GCM so that restarts and deployments don't disconnect everyone. Be clear about what that buys: it protects the data if a disk image, backup or snapshot leaves the machine, and against anyone who can read the file but not the key. It does not protect against a root compromise of the host, since the key lives there too. Leave SESSION_ENCRYPTION_KEY unset and the server keeps sessions in memory instead, forcing everyone to reconnect after each restart.

Configuration for HTTP mode:

PORT=3939
PUBLIC_URL=https://your-domain.example
SESSION_ENCRYPTION_KEY=<openssl rand -hex 32>

PUBLIC_URL is the OAuth issuer identifier and must match exactly what clients connect to.

The account address a user submits decides where the server sends an authenticated request, so it is validated rather than trusted: only HTTPS URLs on known Mailrelay domains are accepted, and IP literals are refused outright. Without that, the consent form could be used to make the server reach internal addresses — loopback, cloud metadata, private ranges — on the network it runs on. Set MAILRELAY_ALLOWED_DOMAINS (comma-separated) if your accounts live on a custom domain; it defaults to ipzmarketing.com,mailrelay.com. Consent submissions are also capped per IP (CONSENT_MAX_ATTEMPTS, default 10 per 10 minutes) so the form can't be used to probe API keys.

Request bodies are limited to 10 MB by default (MAX_REQUEST_BODY, e.g. 25mb). Imports travel inline as base64, so this bounds the CSV size at roughly three quarters of it. Authentication runs before the body is read, so an unauthenticated caller can't make the server buffer anything.

To connect, add <PUBLIC_URL>/mcp as a custom connector (e.g. Claude Desktop → Settings → Connectors → Add custom connector), click Connect, and enter your Mailrelay account and API key on the screen that appears. The same screen has an Optional tools section for the extra toolsets (SMS, WhatsApp, ecommerce…); the choice is stored with the session.

For a permanent deployment on your own domain, see DEPLOY.md (VPS + Caddy).

Temporary tunnel

To expose a local instance for a quick test without deploying anything, start it and put a tunnel in front of it:

npm run start:http
cloudflared tunnel --url http://127.0.0.1:3939

Set PUBLIC_URL to the URL the tunnel prints, restart the server, and share <that URL>/mcp. The server binds to 127.0.0.1 only, so it is reachable through the tunnel rather than directly.

Whoever connects gets full access to their own Mailrelay account, including mailrelay_send_campaign, which sends real and irreversible email.

Tests

npm test             # offline: no credentials, no network
npm run test:live    # needs real credentials in .env; talks to your account

npm test runs four suites against a stubbed HTTP layer:

Suite What it asserts
test-spec-conformance.mjs Every tool is called and the request it makes is checked against Mailrelay's OpenAPI definition: a real operation, only parameters it accepts, a body that satisfies its schema. It also checks that every operation of the API is reachable through some tool. After npm run update-spec this shows what an API change did.
test-toolsets.mjs Which tools each toolset switches on; that every tool is annotated and strict; that every tool which sends, deletes or cancels is destructive; how arguments become paths, filters and bodies; that unknown filters are refused; the context budget.
test-file-access.mjs The remote server never reads its own filesystem on a caller's behalf (the file_path import option exists only in stdio mode) and can't be used to probe which files exist.
test-tool-metadata.mjs Instructions, annotations, prompts, and MAILRELAY_TOOLSETS over a real stdio process.

npm run test:live runs the suites that need a real account, because the consent step verifies credentials against Mailrelay:

Suite What it asserts
test-oauth-consent.mjs Access can't be obtained without valid credentials; the account address can't be used to reach elsewhere (SSRF); a token is bound to the account that authorized it; the toolsets ticked on the consent screen reach the tool list; body limits; error responses leak nothing.
test-session-persistence.mjs A session survives a restart, its toolsets with it; the stored file holds no plaintext credentials; it can't be read with a different key.
test-pagination.mjs List tools return { pagination, items } with correct totals, and ordering works.
test-readonly-smoke.mjs Every read-only tool built from the API definition is called against the real account (GET only, so nothing changes), including a check that a filter really narrows real results.

What the tests do not cover: the write, destructive and sending tools built from the API definition are checked against its shape, never executed. Nothing in the suite sends a message or deletes anything in a real account.

Keeping up with the API

Fifty tools are hand-written in src/server.js; the other 123 are built from Mailrelay's OpenAPI definition, so adding or fixing an operation is a line in src/catalog.js rather than a new schema:

npm run update-spec   # downloads the definition and reduces it to src/openapi/operations.json
npm test              # the conformance test reports what the API change affected

A catalog entry names the operation, the toolset that carries it, and its safety level.

Contributing

Issues and pull requests are welcome. Please don't commit real API keys or account URLs — use .env (gitignored) for local testing.

License

MIT