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).
- Subscribers — list, get, create, update, delete, upsert, ban/unban, restore, bulk changes
- Groups, tags, custom fields, senders — full management
- Campaigns — create drafts, send test emails, send to a real audience, delete drafts
- Sending control — pause, resume or cancel a send that is under way
- Reports — delivery stats of past sends, down to who clicked which link and who unsubscribed
- A/B tests, campaign folders, imports, unsubscribes, bounces, account stats
- Opt-in: transactional email (SMTP), media library, RSS campaigns, signup forms and webhooks, SMS, WhatsApp and ecommerce
- Five prompts for common jobs, including stopping a send that went out by mistake
Requirements
- Node.js 18 or later
- A Mailrelay account and its API key (Mailrelay → Settings → API)
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
- Over stdio, list them in
MAILRELAY_TOOLSETS(comma-separated, orall) and restart. A name that doesn't exist stops the server with a clear message rather than being quietly ignored. - Over HTTP, the consent screen shown when you connect has an Optional tools section: tick
what you want. The choice is stored with your session. To change it, disconnect and reconnect.
An operator can pre-tick toolsets for everyone with
MAILRELAY_TOOLSETSon the server. - Ask the assistant, which can call
mailrelay_list_toolsetsto see what exists, what is enabled and how to enable the rest.
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
- Account —
mailrelay_ping,mailrelay_get_account_package(plan and limits),mailrelay_get_stats(sending summary for a date range, up to a month),mailrelay_list_toolsets - Subscribers — list (filter by group or email, newest first), get, create (
status: 'active'skips double opt-in and is only settable here), update, delete,resend_confirmation_email,sync_subscriber(create-or-update by email),ban/unban,restore,list_deleted_subscribers,bulk_update_subscribers - Groups — list, get, create, update, delete
- Tags — list, get, create, update, delete; a subscriber's tags: list, add, remove, replace
- Custom fields and senders — list, get, create, update, delete as the API allows; a new
sender is verified with
send_sender_confirmation_email - Campaigns (drafts and scheduled) — list, get, create, update, delete,
send_test_campaign, andsend_campaign, which is irreversible: it delivers real emails to the audience - Sent campaigns — list and get with delivery stats;
pause,resumeandcancela send under way; and the detail behind the numbers:list_sent_campaign_clicks,_impressions(opens),_emailsand_unsubscribes - Campaign folders and A/B tests — full management; an A/B test needs at least 10 subscribers in the audience
- Imports — list, get,
get_import_data(line-by-line results), cancel, andstart_import, which takes the CSV base64-encoded (Mailrelay's API takes the file inline as JSON, not as a multipart upload). In local stdio mode it can also read a file from your machine viafile_path; the remote server never offers that, since it has no access to your files and must not read its own - Unsubscribes and bounces — list, get, create and delete unsubscribe events;
delete_bouncesclears addresses from the bounce list
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.