Send notifs using AI

MCP server

@notifkit/mcp exposes NotifKit over the Model Context Protocol, so an agent in your terminal can send notifications, preview and edit templates, drive multi-step workflows, manage audiences, and read the delivery log — without you writing a one-off script for each.

It speaks stdio, which every major MCP client supports: Claude Code, Claude Desktop, Cursor, Gemini, and anything else that can spawn a process. The server is a lightweight, self-contained client over the REST API — it holds no state, opens no ports, and connects to no database directly. Whatever your API key can do, it can do.

Install

npm install -g @notifkit/mcp

Or skip the install and let your client fetch it on demand with npx -y @notifkit/mcp, which is what the configuration examples below do.

note

The server package lives in the NotifKit monorepo at packages/mcp. If you are running from a local clone rather than npm, point your client at node /path/to/notifkit/packages/mcp/dist/index.js after building with npm install && npm run build.

Configure

Three environment variables, all of them required:

VariableRequiredWhat it does
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 your API really 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. Only a project-scoped key can go without, since it carries its own project.

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 limited to one project's data, and you can revoke it without rotating the admin key that every other service depends on. 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

{
  "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 server writes diagnostics to stderr and prints the URL it is talking to on startup, so a misconfigured NOTIFKIT_URL shows up in your client's MCP log rather than as silence.

The raw_email pattern: One-off sends from terminal

Most notification systems require pre-defining static templates in your backend repository before sending any message. But when working in an AI-assisted terminal (Claude Code, Cursor, terminal agents), you frequently want to send ad-hoc, one-off formatted emails without code deployments.

pro tip

Create a generic pass-through template named raw_email once. Because triple-braces {{{body}}} preserve unescaped HTML, your agent can write arbitrary rich HTML and subject lines directly from prompt instructions.

1. One-time template registration:

You: "Register a template called raw_email for email with subject {{subject}} and body {{{body}}}."
Agent:upsert_template

{
  "id": "raw_email",
  "channel": "email",
  "content": {
    "subject": "{{subject}}",
    "html": "{{{body}}}"
  }
}

2. Dispatching one-off emails from prompt commands:

You: "Send an email to alex@acme.corp saying their invoice #9481 is ready to download at https://app.acme.corp/inv/9481."
Agent:send_campaign

{
  "campaign": "invoice-link-alex-2026-08-20",
  "template": "raw_email",
  "emails": ["alex@acme.corp"],
  "data": {
    "subject": "Your invoice #9481 is ready",
    "body": "

Hi Alex,

Your invoice is available: Download Invoice #9481.

" } }

Every one-off email sent this way is fully routed, deduplicated, tracked for opens/clicks, and tagged with a campaign label for reporting.

The tools

The MCP server exposes full CRUD and operational control across NotifKit. Each tool carries rich descriptions and annotations instructing the agent when and how to reach for it.

Campaigns & Sending

ToolWhat it does
send_campaign Send a template directly to a list of destination addresses on email, sms, push, or webhook. Automatically creates/updates user records, handles deduplication, and tags every message with a campaign label for reporting.
send_notification Send to existing user(s), a segment tag, or a topic. Supports sendAt scheduling, priority lanes (critical, high, normal, low), and campaign tags.
list_campaigns List recent campaign labels with message count and activity timestamps. Supports filtering by search keyword, channel, since/until date ranges, and minMessages count.
get_campaign_stats Sent, delivered, failed, opened, clicked, bounced, complained, unsubscribed — with rates and a per-channel split. Surfaces provider tracking warnings.
list_segments List all active segment audience tags across users.
list_scheduled List sends queued for the future, including quiet-hours deferrals, with channel filter and pagination.
get_delivery_logs Query granular delivery logs. Filter by search, templateId, workflowInstanceId, channel, status, campaign, taskId.
get_notification Fetch real-time delivery status, attempt timestamps, and provider logs for a specific taskId.
cancel_notification Cancel a pending or quiet-hours deferred notification before it is dispatched to the provider.

Templates

ToolWhat it does
preview_template Dry-run render a template with sample variable data without sending. Interpolates {{variable}} and {{{raw}}} placeholders with contextual escaping, reporting resolved and missing variables.
render_template Alias for preview_template. Render template content with variable data without dispatching notifications.
list_templates List registered templates, with optional filtering by channel, topic opt-out tag, and limit.
get_template Fetch one template definition by ID, including its per-channel content, topic associations, and AI prompts.
upsert_template Create or update a template with channel-specific content (e.g. subject, HTML body) and optional per-field AI prompts.
delete_template Permanently delete a registered template by ID.

Users & Preferences

ToolWhat it does
list_users Page through users with comprehensive filtering: search (external ID or email), segment tag, language, timezone, and channel contact.
get_user Fetch full user profile, contact endpoints, assigned segments, preferences, and recent delivery logs.
upsert_user Create or overwrite a user profile, contacts, timezone, segment tags, and notification preferences in one call.
update_user Partially update an existing user (e.g. language, timezone, preferences, segments, or append new contacts).
delete_user Permanently delete a user profile and all associated contacts and preferences.
get_user_contacts List all registered destination addresses, phone numbers, push tokens, and webhook URLs for a user.
add_user_contact Add a contact target to an existing user without replacing their other channels or devices.
delete_user_contact Remove a single contact destination (e.g. an old device token or email) from a user.
get_user_preferences Fetch channel/topic opt-in preferences and quiet hours windows for a user.
update_user_preferences Update channel opt-ins, topic opt-outs, and quiet hours schedule for a user.

Workflows & Events

ToolWhat it does
create_workflow Define a named multi-step sequence (drip campaigns, onboarding flows, reminder chains) with notify, wait, and waitForEvent steps.
list_workflows List registered workflow definitions and their steps. Supports filtering by search keyword.
trigger_workflow Start a new execution instance of a workflow for a specific user with custom input data.
get_workflow_run Fetch execution state, current step, and waiting conditions for a workflow instance.
cancel_workflow_run Cancel an active or suspended workflow instance so remaining steps never fire.
ingest_event Publish an application event (e.g. order.paid, user.onboarded) to resume workflow instances parked on waitForEvent.

Suppressions

ToolWhat it does
list_suppressions List suppressed destinations with reason (unsubscribed, complained, bounced, manual), channel, and target filter.
suppress_address Manually suppress an email, phone number, or push token to prevent all future sends.
unsuppress_address Remove an address from suppression list. (Marked destructive — use only on explicit user confirmation).

Operations & Projects

ToolWhat it does
get_system_health Health report of Redis, Postgres database, and background workers (enricher, engine, scheduler, delivery, ai, workflow).
get_system_metrics Report queue depths across all Redis streams and delivery statistics for the project.
get_dead_letters List messages that exhausted retries and landed in the dead-letter queue (DLQ) with error reasons.
replay_dead_letter Re-queue a dead-lettered message for delivery after resolving the underlying issue.
delete_dead_letter Permanently dismiss/delete an entry from the dead-letter queue.
list_projects List all tenant projects configured in the deployment.
create_project Create a new project tenant and generate its initial admin API key.
update_project Update project rate limits and throttling window parameters.
delete_project Permanently delete a project and its associated data.
list_project_keys List API keys created for a specific project.
create_project_key Generate a new project API key with admin or read_only role.
delete_project_key Revoke and delete a project API key.

Real-World Business Use Cases

Connecting NotifKit to an MCP agent provides powerful leverage across engineering, operations, customer success, product, and marketing. Here is how teams use it in practice:

1. Customer Support & Delivery Triage

Who uses it: Support Engineers, Technical Account Managers.
Problem: A user opens a ticket stating they never received an urgent password reset link or invoice receipt. Support agents usually do not have direct database access.

You: "User usr_9182 (email buyer@example.com) says they didn't receive their invoice receipt. Check why."
Agent:
1. → get_user({ id: "usr_9182" }) to inspect registered contacts and quiet hours.
2. → list_suppressions({ target: "buyer@example.com" }) to check if the address bounced or was marked as spam.
3. → get_delivery_logs({ search: "usr_9182", limit: 10 }) to view dispatch attempts and provider status codes.
Agent response: "The user has quiet hours configured from 22:00 to 08:00 in America/Chicago. The notification was deferred and is scheduled to dispatch at 08:01 AM tomorrow."

2. Safe Template Development with Dry-Run Previews

Who uses it: Product Managers, Template Designers.
Problem: Before broadcasting a campaign, you want to inspect rendered output and verify that all variable placeholders resolve correctly without sending live messages.

You: "Preview the order_shipped template for user Sarah with order #8172 and FedEx tracking."
Agent:preview_template({ id: "order_shipped", data: { name: "Sarah", orderId: "8172", carrier: "FedEx", trackingNumber: "FX-817293" } })

{
  "templateId": "order_shipped",
  "channel": "email",
  "rendered": {
    "subject": "Order #8172 has shipped!",
    "body": "Hi Sarah, your package is on its way via FedEx. Tracking: FX-817293"
  },
  "resolvedVariables": ["name", "orderId", "carrier", "trackingNumber"],
  "unresolvedVariables": []
}

3. E-Commerce Flash Sales & Product Drops (Multichannel SMS & Push)

Who uses it: E-Commerce Operators, Growth Marketers.
Problem: You are launching a 4-hour flash sale. You need to blast 1,500 VIP customers over SMS and Push with a discount code, respecting quiet-hours boundaries.

You: "Send an SMS blast with template flash_sale_sms to these phone numbers with code FLASH50: +15551234567, +15559876543, +15558765432."
Agent:send_campaign({ campaign: "flash-sale-aug20", channel: "sms", template: "flash_sale_sms", recipients: ["+15551234567", "+15559876543", "+15558765432"], data: { code: "FLASH50" } })

4. SaaS Product Growth & Onboarding Automations

Who uses it: Product Managers, Growth Engineers.
Problem: You want to design and deploy an automated onboarding drip sequence for new trial users that stops automatically once they activate a core feature.

You: "Create an onboarding workflow called saas-onboarding-v2: Send welcome_email, wait 2 days, send pro_tips_email, then wait for event project.created for up to 5 days. If they don't create a project, send trial_help_reminder."
Agent:create_workflow with notify, wait, and waitForEvent steps → list_workflows({ search: "saas-onboarding" })

5. DevOps & Incident Response (Outage Alerts & Priority Lanes)

Who uses it: SRE, On-Call Engineers.
Problem: A regional database cluster is degraded. You need to alert all active users in the region-eu-central segment immediately, bypassing routine throttle queues.

You: "Notify all users in segment region-eu-central that emergency maintenance is starting in 15 minutes. Use critical priority."
Agent:send_notification({ segment: "region-eu-central", template: "incident_alert", priority: "critical", data: { eta: "15m", service: "Database Cluster A" } })
Result: Critical priority bypasses non-essential quiet-hours deferrals and dispatches immediately.

6. Marketing Performance & Executive Reporting

Who uses it: CMO, Growth Lead.
Problem: You need an aggregated delivery and engagement report for yesterday's product announcement without logging into individual provider dashboards (Resend, Twilio, Firebase).

You: "How did the v2-launch-broadcast campaign perform yesterday?"
Agent:get_campaign_stats({ campaign: "v2-launch-broadcast" })

{
  "campaign": "v2-launch-broadcast",
  "totals": {
    "sent": 12450,
    "delivered": 12250,
    "failed": 200,
    "opened": 5129,
    "clicked": 1085,
    "bounced": 140,
    "complained": 2,
    "deliveryRate": 98.4,
    "openRate": 41.2,
    "clickRate": 8.7,
    "complaintRate": 0.02
  }
}

7. GDPR / Compliance & Channel Suppressions

Who uses it: Legal/Compliance Officers, Operations.
Problem: A customer exercises their "Right to be Forgotten" or requests an immediate global opt-out across SMS, email, and push.

You: "Customer with phone +447700900123 requested to be suppressed from all future communications."
Agent:suppress_address({ channel: "sms", target: "+447700900123", reason: "manual" })list_users({ search: "+447700900123" }) to identify and suppress linked email addresses.

Reading the numbers

gotcha

Audience size is not delivery count. Every send passes the same gates as any other notification — suppressions, dedupe, opt-out, channel, quiet hours, throttle, template, contact — 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. SMS and push report nothing at all; 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 instructs the agent to repeat it rather than presenting a flat 0%.

Watch the complaint rate. Above roughly 0.3%, mailbox providers begin throttling or junking your mail — and because it is the same sending domain, that reaches your transactional email too. It is reported as its own figure for that reason.

Suppressions

An unsubscribe, a spam complaint, or a hard bounce arriving from a provider webhook adds the address to a suppression list, and the engine drops it before dispatch on every later send. This outranks every other setting, priority: "critical" included — a suppression records that somebody asked to stop hearing from you, or that the address no longer exists, and neither is overridable by an urgent send.

suppress_address covers the routes a provider cannot see: someone replying to ask for removal, or telling you over the phone. list_suppressions explains why a particular person did not receive something.

one-way by design

unsuppress_address exists, is marked destructive, and should be used only on an explicit request. Removing suppressions to improve a delivery figure is the single most reliable way to get a sending domain blocked. If an agent proposes it unprompted, that is a bug in the prompt, not a good idea.

Safety

send_campaign, send_notification, trigger_workflow, and replay_dead_letter reach real people and cannot be recalled. Every tool carries MCP annotations describing what it does — read-only tools are marked as such, and cancel_workflow_run and unsuppress_address are marked destructive — so a client can auto-approve reads and prompt on everything that writes.

Where to go next