MCP server · Mnemo Connect

Your AI agents, sending campaigns.

pulsemail exposes a Mnemo Connect MCP endpoint. Your agents see every list, every subscriber, every template, and can send campaigns on instruction. No glue code. No webhook duct-tape.

Available on the Business tier
Why this exists

Email infrastructure your agents understand natively.

Three things make MCP the right surface for campaign work — better than scripts that wrap REST and parse JSON for you.

Agents don't read documentation

They read tool schemas. MCP gives every endpoint a strict, typed JSON-Schema definition that agents can introspect and call without prompting tricks.

Stop building glue scripts

Instead of bash that wraps curl, parses fields, retries on 429, and logs to a sidecar — agents call typed tools natively. Idempotency, retries, and quotas are handled inside the MCP layer.

Memory and comms in one substrate

pulsemail MCP is registered in Mnemo Connect, alongside your other agents (mnemo, aigramm, listing.company tools). One connection, every product.

Connect in 60 seconds

Three steps. Zero glue code.

From key generation to your first agent call. Bring your own Mnemo Connect daemon — or use the bundled one.

Get a key

Go to /admin/settings#api and click Generate MCP key. Choose scopes (read / write / send). Copy the mcp_live_… string — it's shown once.

# Settings → API → Generate MCP key
mcp_live_3f8b2c9e1a4d5670

Connect

Register the endpoint with your local Mnemo Connect daemon. The handshake exchanges capabilities and caches the tool schema.

mnemo connect mcp:wss://send.blun.ai/mcp \
  --key mcp_live_3f8b2c9e1a4d5670 \
  --alias pulsemail

Use

Call any tool by name. Your agent gets the same view it would have in the dashboard — but as typed structured data.

mnemo call pulsemail.list_lists
# → {"lists": [{"id": "lst_8a7b…", "name": "Newsletter", "subscribers": 12480}]}
Tool reference

Twelve tools, one endpoint.

Every tool has a typed argument schema and a typed return shape. Click any row to expand the JSON-Schema details and a working call example.

Reading

read scope
list_lists Return all lists in the workspace, paginated.
Arguments
FieldTypeDescription
limitintegerPage size, 1–200. Default 50.
cursorstringOpaque pagination cursor returned by the previous call.
Returns
FieldTypeDescription
listsList[]Array of list objects with id, name, subscribers, created_at.
next_cursorstring|nullPass to the next call, or null if last page.
Example
mnemo call pulsemail.list_lists --args '{"limit": 20}'

// → response
{
  "lists": [
    { "id": "lst_8a7b3c", "name": "Newsletter", "subscribers": 12480 },
    { "id": "lst_4f9e1d", "name": "VIP buyers", "subscribers": 312 }
  ],
  "next_cursor": "eyJpZCI6Imxz..."
}
list_subscribers Return subscribers in a list, optionally filtered by status.
Arguments
FieldTypeDescription
list_id*stringRequired. Target list identifier.
statusenumOne of subscribed, unsubscribed, bounced, pending.
limitintegerPage size, 1–200. Default 50.
cursorstringOpaque pagination cursor.
Returns
FieldTypeDescription
subscribersSubscriber[]Each entry has id, email, status, fields, tags, consent_at.
next_cursorstring|nullPass to the next call.
Example
mnemo call pulsemail.list_subscribers --args '{
  "list_id": "lst_8a7b3c",
  "status": "subscribed",
  "limit": 100
}'
list_campaigns Return campaigns filtered by status and time window.
Arguments
FieldTypeDescription
statusenumdraft | scheduled | sending | sent | cancelled.
sinceiso-8601Only return campaigns created after this timestamp.
limitintegerPage size, 1–200. Default 50.
cursorstringOpaque pagination cursor.
Returns
FieldTypeDescription
campaignsCampaign[]Each has id, subject, status, list_id, sent_at, recipients.
next_cursorstring|nullPagination continuation token.
Example
mnemo call pulsemail.list_campaigns --args '{
  "status": "sent",
  "since": "2026-04-01T00:00:00Z"
}'
get_campaign_analytics Return the full analytics object for a sent campaign.
Arguments
FieldTypeDescription
campaign_id*stringRequired. Campaign to inspect.
Returns
FieldTypeDescription
sentintegerTotal messages dispatched.
opensintegerUnique open count.
clicksintegerUnique click count.
ctrfloatClick-through rate, 0.0–1.0.
geographicGeoBucket[]Per-country opens with ISO-3166 codes.
devicesDeviceBucket[]Desktop / mobile / tablet split.
heatmapHourCell[24×7]2D grid of opens by hour of week.
Example
mnemo call pulsemail.get_campaign_analytics --args '{
  "campaign_id": "cmp_61a8e9d3"
}'

// → response (truncated)
{ "sent": 12480, "opens": 5621, "clicks": 912, "ctr": 0.073 }
list_templates Return reusable templates, optionally filtered by category.
Arguments
FieldTypeDescription
categorystringe.g. newsletter, welcome, announcement, transactional.
limitintegerPage size, 1–200.
Returns
FieldTypeDescription
templatesTemplate[]id, name, category, variables[], preview_url.
Example
mnemo call pulsemail.list_templates --args '{"category": "newsletter"}'

Writing

write scope
add_subscriber Add a contact to a list with explicit consent metadata.
Arguments
FieldTypeDescription
list_id*stringRequired. Target list.
email*stringRequired. Validated against RFC 5322 + DNS MX.
fieldsobjectCustom fields keyed by name (e.g. {"first_name": "Anna"}).
tagsstring[]Tags to apply on creation.
consent_source*stringRequired. e.g. checkout-form, concierge-flow, imported-csv.
Returns
FieldTypeDescription
subscriber_idstringNew subscriber identifier.
statusenumsubscribed | pending (if double opt-in is enabled).
Example
mnemo call pulsemail.add_subscriber --args '{
  "list_id": "lst_8a7b3c",
  "email": "anna@example.eu",
  "fields": {"first_name": "Anna"},
  "tags": ["happy-customer"],
  "consent_source": "concierge-flow"
}'
tag_subscriber Add or update tags on a subscriber. Idempotent.
Arguments
FieldTypeDescription
subscriber_id*stringRequired.
tags*string[]Required. Existing tags are preserved unless prefixed with -.
Returns
FieldTypeDescription
okbooleantrue on success.
Example
mnemo call pulsemail.tag_subscriber --args '{
  "subscriber_id": "sub_6f3e9a",
  "tags": ["vip", "-trial"]
}'
build_block Construct a single content block (hero, text, button, image…).
Arguments
FieldTypeDescription
kind*enumRequired. One of hero, text, button, image, spacer, quote, divider.
props*objectRequired. Schema depends on kind. e.g. {"headline": "...", "image_url": "..."}.
Returns
FieldTypeDescription
block_idstringReusable identifier.
htmlstringRendered MJML-compiled HTML for preview.
Example
mnemo call pulsemail.build_block --args '{
  "kind": "hero",
  "props": {
    "headline": "Welcome aboard",
    "subhead": "A short note from the team"
  }
}'
compose_campaign Assemble a draft campaign from a template or an array of blocks.
Arguments
FieldTypeDescription
list_idstringTarget list. Either this or segment_id is required.
segment_idstringTarget segment within a list.
template_idstringOptional. If set, blocks is ignored.
blocksBlock[]Inline block definitions. Mutually exclusive with template_id.
subject*stringRequired. Subject line. Personalisation tokens supported.
from*objectRequired. {"email": "...", "name": "..."} from a verified sender domain.
Returns
FieldTypeDescription
campaign_idstringNew campaign identifier.
draftbooleanAlways true; sends require schedule_send or send_campaign.
Example
mnemo call pulsemail.compose_campaign --args '{
  "list_id": "lst_8a7b3c",
  "template_id": "tpl_monthly_v3",
  "subject": "May highlights — handpicked for {{first_name}}",
  "from": {"email": "team@blun.ai", "name": "BLUN team"}
}'

Sending

send scope
schedule_send Schedule a draft for a future ISO timestamp, or auto-optimise.
Arguments
FieldTypeDescription
campaign_id*stringRequired. Draft to schedule.
send_at_isoiso-8601UTC timestamp. Ignored when optimize: true.
optimizebooleanIf true, the engine picks the best slot from the open heatmap.
Returns
FieldTypeDescription
scheduled_atiso-8601Confirmed dispatch time.
estimated_recipientsintegerSnapshot of audience size at schedule time.
Example
mnemo call pulsemail.schedule_send --args '{
  "campaign_id": "cmp_61a8e9d3",
  "send_at_iso": "2026-05-12T07:30:00Z"
}'
send_campaign Send a draft immediately. Subject to per-key send-rate limits.
Arguments
FieldTypeDescription
campaign_id*stringRequired. Draft to send now.
Returns
FieldTypeDescription
send_idstringIdentifier for the dispatch job — used by cancel_send.
queued_recipientsintegerCount of validated, deduplicated addresses queued.
Example
mnemo call pulsemail.send_campaign --args '{
  "campaign_id": "cmp_61a8e9d3"
}'
cancel_send Cancel a scheduled or in-flight dispatch. Stops queued mail.
Arguments
FieldTypeDescription
send_id*stringRequired. Returned by send_campaign or schedule_send.
Returns
FieldTypeDescription
okbooleantrue if cancellation took effect before queued messages flushed.
Example
mnemo call pulsemail.cancel_send --args '{
  "send_id": "snd_42ab17f0"
}'
Sample agent flows

Patterns we've already seen in production.

These are full-loop examples that combine multiple tools — not toy snippets. Each one runs as a Mnemo Connect skill.

Monthly digest

Anna's monthly digest

An editorial agent runs once a month. Reads which tags performed best, drafts a newsletter from those case studies, hits the optimised slot, posts a 1-line summary back to Mnemo.

  1. list_campaigns with since: -30d
  2. get_campaign_analytics for top 3 performers
  3. compose_campaign with a curated block list
  4. schedule_send with optimize: true for Tuesday window
  5. Mnemo brief_drop with the campaign id + snapshot
Recurring agent

Re-engagement bot

Every Sunday, identifies subscribers who haven't opened in 60 days. Builds a personalised re-engagement note per segment. Schedules a Monday 10:00 wave.

  1. list_subscribers with status: subscribed
  2. Filter locally on last_open_at < 60 days
  3. tag_subscribercool or warm
  4. compose_campaign per segment
  5. schedule_send for next Monday 10:00 local
Cross-product pipeline

Concierge → newsletter pipeline

When the concierge agent closes a deal in listing.company, the buyer is added to a "happy-customer" segment in pulsemail and the welcome flow is triggered.

  1. concierge agent: deal closed event
  2. add_subscriber with consent_source: concierge-flow
  3. tag_subscriberhappy-customer
  4. compose_campaign from welcome-v3 template
  5. send_campaign immediately

It's also a Mnemo Connect agent.

The pulsemail MCP server is registered as pulsemail-65er in the Mnemo Connect tree. Mention it in your other agents' channels — for example @pulsemail-65er please send the May digest — and Mnemo will route the request via the brief queue.

This means an editorial agent can hand off to pulsemail without holding the MCP key itself, and the audit log captures both sides of the handoff.

Learn about Mnemo Connect
Authentication & security

Workspace-scoped, scoped, audited.

MCP keys carry the same trust model as REST keys. Three layers: scope, rotation, audit.

Per-key permission scopes

Choose read, write, or send per key. Sending a campaign requires the send scope explicitly.

Rotate from settings

Keys rotate from /admin/settings#api. Old key remains valid for 60 minutes during cutover, then revoked.

Full audit log

Every MCP call is recorded with timestamp, key id, tool name, args hash, and result code. Available at /admin/settings#audit.

Plug your agents in.

Generate a key. Run one mnemo connect command. Watch your agents send.