@notifkit/mcp
An MCP server for notifkit. Point a terminal agent at your notifkit deployment and it can send notifications, manage templates and users, build workflows, and read the delivery log — without you writing a script for each one.
Speaks stdio, so it works with Claude Code, Claude Desktop, and any other MCP client.
Install
npm install -g @notifkit/mcp
Or skip the install and let your client run it with npx -y @notifkit/mcp.
Configure
Three environment variables, all of them required:
| Variable | Required | Notes |
|---|---|---|
NOTIFKIT_API_KEY |
yes | Your ADMIN_API_KEY, or a project API key. The server exits without it |
NOTIFKIT_URL |
yes | Base URL of your notifkit API. Falls back to http://localhost:3000, so set it unless the API is there |
NOTIFKIT_PROJECT_ID |
yes | Which project the admin key acts on, sent as x-project-id. Without it an admin key gets 400 on everything except /v1/projects |
The examples below use your ADMIN_API_KEY, which carries no project of its own — it needs NOTIFKIT_PROJECT_ID to know which project to act on. A project-scoped key already carries its project, so if you swap one in, drop the NOTIFKIT_PROJECT_ID line. Prefer that where you can: it is scoped to a single project and can be revoked without rotating the admin key. Mint one with create_project_key or POST /v1/projects/:id/keys.
Claude Code
claude mcp add notifkit \
--env NOTIFKIT_URL=http://localhost:3000 \
--env NOTIFKIT_API_KEY=your_admin_api_key \
--env NOTIFKIT_PROJECT_ID=6f1c9c1e-3b7a-4d2e-9f04-2c8a5b1d7e33 \
-- npx -y @notifkit/mcp
Claude Desktop
In claude_desktop_config.json:
{
"mcpServers": {
"notifkit": {
"command": "npx",
"args": ["-y", "@notifkit/mcp"],
"env": {
"NOTIFKIT_URL": "http://localhost:3000",
"NOTIFKIT_API_KEY": "your_admin_api_key",
"NOTIFKIT_PROJECT_ID": "6f1c9c1e-3b7a-4d2e-9f04-2c8a5b1d7e33"
}
}
}
}
The two-sentence workflow
The thing this exists for:
You: "Send the spring-sale template to these 200 addresses." (paste) Agent: →
send_campaign→ "Queued 200 asspring-sale-aug-13."(next day) You: "How did the spring sale do?" Agent: →
list_campaigns→get_campaign_stats→ "194 delivered, 4 bounced, 2 suppressed. 71 opened (37%), 12 clicked (6%)."
send_campaign takes raw email addresses, phone numbers, or push tokens — no user records needed up front. It creates one per recipient, keyed off the address itself, so re-sending to the same list updates those people rather than duplicating them. Duplicates and case differences within a list are collapsed before sending.
The raw_email Pattern (One-off Sends from Terminal)
Instead of hardcoding a new template in backend code for every custom message, register a single generic pass-through template once:
{
"id": "raw_email",
"channel": "email",
"content": {
"subject": "{{subject}}",
"html": "{{{body}}}"
}
}
Because triple braces {{{body}}} preserve unescaped HTML, your agent in Claude Code or Cursor can dispatch arbitrary one-off formatted emails directly from terminal prompts:
You: "Send an email to alex@acme.corp saying their invoice is ready at https://app.acme.corp/inv/9481" Agent: →
send_campaign({ template: "raw_email", emails: ["alex@acme.corp"], data: { subject: "Invoice #9481 Ready", body: "<p>Download: <a href='...'>Invoice #9481</a></p>" } })
Tools
Campaigns & Sending
| Tool | What it does |
|---|---|
send_campaign |
Send a template to a list of recipients across email, sms, push, webhook, telegram, discord, whatsapp, or slack with campaign tagging |
send_notification |
Send to existing users, a segment, or a topic with sendAt scheduling and priority lanes |
list_campaigns |
Recent campaign labels with size and last activity (supports search, channel, date range) |
get_campaign_stats |
Sent, delivered, failed, opened, clicked, bounced, complained, unsubscribed, with rates |
list_segments |
Every segment tag in use — the valid audiences to broadcast to |
list_scheduled |
Sends queued for the future, including quiet-hours deferrals |
get_delivery_logs |
Per-message detail with filters for template, channel, status, campaign, or user search |
get_notification |
Status and delivery attempts for a specific task |
cancel_notification |
Cancel a pending scheduled or quiet-hours deferred notification |
Templates — list_templates, get_template, upsert_template, delete_template, preview_template, render_template
Users — list_users (with search, segment, language, timezone, channel filters), get_user, upsert_user, update_user, delete_user, get_user_contacts, add_user_contact, delete_user_contact, get_user_preferences, update_user_preferences
Workflows — create_workflow, list_workflows (with search), trigger_workflow, get_workflow_run, cancel_workflow_run, ingest_event
Operations — get_system_health, get_system_metrics, get_dead_letters, replay_dead_letter, delete_dead_letter, list_projects, create_project, update_project, delete_project, list_project_keys, create_project_key, delete_project_key
Reading the numbers
Audience size is not delivery count. Every send passes the same gates as any other notification — suppressions, opt-outs, quiet hours, throttling — so a list of 200 delivers to fewer than 200, by design. send_campaign reports what was queued; get_campaign_stats reports what actually happened.
An untracked open rate is not a zero open rate. Opens and clicks arrive from provider webhooks. On SMS and push there is nothing to report; on email, nothing arrives unless the webhook is configured. get_campaign_stats returns a warnings array saying which of those applies, and the tool description tells the agent to pass it on rather than reporting a flat 0%.
Watch the complaint rate. Above roughly 0.3%, mailbox providers start throttling or junking your mail — and that hits your transactional email too, since it is the same sending domain.
Sequences
Multi-step sends — onboarding, drips, reminders — are workflows rather than campaigns: create_workflow defines the steps, trigger_workflow enrols each user.
"Make an onboarding drip: welcome email, wait 3 days, then the tips email unless they've already sent an
activatedevent."
Safety
send_campaign, send_notification, trigger_workflow, and replay_dead_letter reach real people and cannot be recalled. Read-only tools are annotated as such so clients can auto-approve them and prompt on the rest; cancel_workflow_run and unsuppress_address are marked destructive.
Suppressions deserve particular care. A suppressed address is usually someone who asked not to be contacted, or an address that no longer exists. Removing a suppression to improve a delivery figure is how a sending domain gets blocked — the tool description tells the agent to confirm first, and you should treat an unprompted suggestion to do it as a red flag.
Point the server at a staging deployment while you get a feel for it.
Development
npm run build # tsc
npm run typecheck
The server holds no state and imports nothing from the notifkit core package — it is a thin, self-contained HTTP client, so running it costs you a Node process and nothing else.