MCP Server
Connect any MCP-compatible agent runtime to the Molted Email API with the @molted/mcp server package.
The @molted/mcp package exposes the Molted Email API as discoverable tools for any runtime that supports the Model Context Protocol. Claude Desktop, Cursor, VS Code Copilot, Windsurf, and any other MCP client can use it to send email, manage threads, classify inbound messages, and more.
Installation
npx -y @molted/mcpOr install globally:
npm install -g @molted/mcpEnvironment variables
| Variable | Required | Default | Description |
|---|---|---|---|
MOLTED_API_KEY | Yes | - | Your Molted API key (mm_live_* or mm_test_*) |
MOLTED_API_URL | No | https://api.molted.email | API base URL |
Setup
Claude Desktop
Add to your claude_desktop_config.json:
{
"mcpServers": {
"molted": {
"command": "npx",
"args": ["-y", "@molted/mcp"],
"env": {
"MOLTED_API_KEY": "mm_live_..."
}
}
}
}Cursor
Add to .cursor/mcp.json in your project:
{
"mcpServers": {
"molted": {
"command": "npx",
"args": ["-y", "@molted/mcp"],
"env": {
"MOLTED_API_KEY": "mm_live_..."
}
}
}
}VS Code Copilot
Add to .vscode/mcp.json in your project:
{
"servers": {
"molted": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@molted/mcp"],
"env": {
"MOLTED_API_KEY": "mm_live_..."
}
}
}
}Windsurf
Add to ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"molted": {
"command": "npx",
"args": ["-y", "@molted/mcp"],
"env": {
"MOLTED_API_KEY": "mm_live_..."
}
}
}
}HTTP transport
For cloud agents or server-side use, run in HTTP mode:
MOLTED_API_KEY=mm_live_... molted-mcp --transport http --port 3100The HTTP transport is stateless -- each request creates a fresh MCP session. This works well for serverless deployments and load-balanced environments.
Tools
The MCP server exposes 25 tools across five groups, plus 8 resources and 5 prompts.
Budget
check_budget
Check remaining send capacity including daily/hourly limits and bounce/complaint budget. Call this before planning a campaign or batch send to verify you have capacity.
Parameters: None
Returns: { daily: { used, remaining }, hourly: { used, remaining }, negativeBudget: { bounces, complaints } }
Inbox and threads
read_inbox
List threads in a mailbox with optional filtering.
| Parameter | Type | Required | Description |
|---|---|---|---|
mailbox_id | string | No | Filter by mailbox UUID |
status | string | No | Filter by status: open, waiting, resolved, escalated |
limit | number | No | Max results (1-50, default 25) |
Returns: { items: [{ id, contactEmail, subject, status, messageCount, lastMessageAt }], total }
get_thread
Get a full thread with all messages, intent classification, and SLA status.
| Parameter | Type | Required | Description |
|---|---|---|---|
thread_id | string | Yes | Thread UUID |
Returns: { thread: { id, status, contactEmail, ... }, envelopedMessages: [...] }
reply
Reply to a thread. Supports two modes:
- Shorthand: Provide
body(plain text) orhtml-- uses the_defaulttemplate with an auto-generated dedupe key. - Template: Provide
template_id,payload, anddedupe_keyfor custom templates.
| Parameter | Type | Required | Description |
|---|---|---|---|
thread_id | string | Yes | Thread UUID to reply to |
body | string | No | Plain text reply body (shorthand mode) |
html | string | No | HTML reply body (shorthand mode) |
subject | string | No | Override reply subject |
template_id | string | No | Template ID (template mode) |
payload | object | No | Template payload (template mode) |
dedupe_key | string | No | Deduplication key |
send_reason | string | No | Reason for sending (audit trail) |
Returns: { status: "queued" | "sent", requestId }
archive
Archive a resolved thread, moving it out of the active inbox.
| Parameter | Type | Required | Description |
|---|---|---|---|
thread_id | string | Yes | Thread UUID |
Returns: { archived: 1 }
update_thread
Update a thread's status, metadata, or assignment.
| Parameter | Type | Required | Description |
|---|---|---|---|
thread_id | string | Yes | Thread UUID |
status | string | No | New status: open, waiting, resolved, escalated |
metadata | object | No | Metadata to set |
assigned_agent_id | string | No | Agent to assign to |
Returns: The updated thread object.
approve
Approve a pending send in the approval queue (human-in-the-loop).
| Parameter | Type | Required | Description |
|---|---|---|---|
thread_id | string | Yes | Thread UUID with pending approval |
reason | string | Yes | Reason for approving (audit trail) |
Returns: Approval confirmation.
Sending
send_email
Send a policy-checked email. Supports shorthand (body/html) and template modes.
| Parameter | Type | Required | Description |
|---|---|---|---|
to | string | Yes | Recipient email |
body | string | No | Plain text body (shorthand) |
html | string | No | HTML body (shorthand) |
subject | string | No | Subject line (shorthand) |
template_id | string | No | Template ID |
payload | object | No | Template payload |
dedupe_key | string | No | Deduplication key |
send_reason | string | No | Reason for sending |
mailbox_id | string | No | Mailbox to send from |
idempotency_key | string | No | Idempotency key for safe retries |
Returns: { status: "queued" | "blocked" | "pending_approval", requestId }
Policy blocks are returned as successful results with the block reason so your agent can reason about alternatives.
draft_email
Draft an email with AI-assisted composition and policy pre-check.
| Parameter | Type | Required | Description |
|---|---|---|---|
to | string | Yes | Recipient email |
context | object | No | Background context for draft generation |
Returns: { draftCandidates: [...], policyPreCheck: { allowed, reason }, contactContext }
dry_run
Simulate a send without dispatching. Preview the policy decision.
| Parameter | Type | Required | Description |
|---|---|---|---|
to | string | Yes | Recipient email |
template_id | string | Yes | Template to simulate |
dedupe_key | string | No | Deduplication key |
payload | object | No | Template payload |
mailbox_id | string | No | Mailbox to simulate from |
Returns: { wouldAllow: boolean, reason?, cooldownExpiresAt?, simulation: true }
batch_send
Send to multiple recipients with per-recipient policy evaluation. Max 500 recipients per batch.
| Parameter | Type | Required | Description |
|---|---|---|---|
template_id | string | Yes | Template for all recipients |
sends | array | Yes | Array of { to, dedupe_key, payload?, send_reason? } |
mailbox_id | string | No | Mailbox to send from |
Returns: { batchId, total, queued, blocked, results: [{ recipientEmail, requestId, status, reason? }] }
schedule_followup
Schedule a delayed follow-up email that auto-cancels if the recipient replies.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_email | string | Yes | Contact to follow up with |
thread_request_id | string | Yes | Request ID from the initial send |
delay_minutes | number | Yes | Delay before sending (1-43200) |
cancel_on_reply | boolean | No | Auto-cancel on reply |
trigger_conditions | object | No | Custom trigger conditions |
Returns: { followupId, status, scheduledAt }
Intelligence
classify_inbound
Classify an inbound email's intent.
| Parameter | Type | Required | Description |
|---|---|---|---|
subject | string | Yes | Email subject line |
body_text | string | Yes | Plain text body |
Returns: { intent, confidence, suggestedAction, flags?, safetyVerdict? }
Intent categories: interested, objection, not_now, support, billing, legal, security, out_of_office.
next_best_action
Get a recommendation for what to do next with a contact based on timeline, fatigue, and intent signals.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_email | string | Yes | Contact email address |
Returns: { recommendation, reasoning, contactSummary: { email, lastSendAt, isSuppressed } }
Recommendations: reply, wait, nudge, stop, escalate.
get_context
Get full contact timeline including sends, replies, journeys, engagement history, and suppression status.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_email | string | Yes | Contact email address |
Returns: { timeline: { sends, journeyRuns, inboundMessages }, suppressionStatus, activeIncidents, lastClassification }
batch_classify
Classify multiple inbound emails in one call.
| Parameter | Type | Required | Description |
|---|---|---|---|
messages | array | Yes | Array of { subject, body_text } |
Returns: { results: [{ intent, confidence, suggestedAction }] } in the same order as inputs.
batch_next_action
Get next-best-action recommendations for multiple contacts.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_emails | array | Yes | Array of email addresses |
Returns: { results: [{ contactEmail, recommendation, reasoning }] }
Governance and outcomes
add_suppression
Suppress a contact or domain so no further emails are sent to them. Provide either recipient_email (email-level) or domain (domain-level).
| Parameter | Type | Required | Description |
|---|---|---|---|
recipient_email | string | No | Email address to suppress |
domain | string | No | Domain to suppress (e.g. example.com) |
scope | string | No | Scope: global, tenant, or campaign |
reason_code | string | No | Reason code: complaint, hard_bounce, manual_dnc, legal_request, role_account, domain_suppressed, no_engagement |
source | string | No | Source of the suppression |
Returns: The created suppression record.
check_suppression
Check if an email address or domain is currently suppressed. Call this before sending to verify the recipient is not on a suppression list.
| Parameter | Type | Required | Description |
|---|---|---|---|
recipient_email | string | No | Email to check |
domain | string | No | Domain to check |
Returns: Suppression records if found, empty array if not suppressed.
remove_suppression
Remove a suppression so the contact or domain can receive emails again.
| Parameter | Type | Required | Description |
|---|---|---|---|
suppression_id | string | No | UUID of the email suppression to remove |
domain | string | No | Domain suppression to remove |
Returns: { deleted: true }
check_consent
Check consent status for a contact (GDPR/CCPA compliance).
| Parameter | Type | Required | Description |
|---|---|---|---|
recipient_email | string | Yes | Email address to check |
Returns: { hasConsent: boolean, basis: string }
record_consent
Record consent for GDPR/CCPA compliance.
| Parameter | Type | Required | Description |
|---|---|---|---|
recipient_email | string | Yes | Email address |
basis | string | Yes | Legal basis: explicit_opt_in, legitimate_interest, contractual, legal_obligation |
source | string | No | Where consent was given (e.g. signup_form) |
jurisdiction | string | No | Legal jurisdiction (e.g. GDPR, CCPA) |
granted_at | string | No | ISO 8601 timestamp when consent was granted |
revoked_at | string | No | ISO 8601 timestamp when consent was revoked |
Returns: The consent record.
record_outcome
Record a business outcome (conversion, revenue, engagement) linked to email touches. Molted automatically attributes the outcome to recent email interactions.
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_email | string | Yes | Contact email |
event_type | string | Yes | activation, trial_conversion, meeting_booked, deal_closed, upsell, or custom |
event_name | string | Yes | Event name (e.g. purchase, demo_booked) |
revenue | number | No | Revenue amount in cents |
metadata | object | No | Additional metadata |
occurred_at | string | No | ISO 8601 timestamp (defaults to now) |
Returns: The ingested outcome record with attribution data.
outcome_dashboard
Get a revenue attribution summary showing how email campaigns contributed to business outcomes.
Parameters: None
Returns: { totalRevenue, conversions, topCampaigns }
journey_impact
Analyze which journeys had the most impact on business outcomes.
| Parameter | Type | Required | Description |
|---|---|---|---|
model | string | No | Attribution model: first_touch, last_touch, or linear (default last_touch) |
Returns: Journeys ranked by outcome contribution.
Resources
Resources provide read-only context that agents can pull into their working memory without calling tools. Access them via resources/list and resources/read in any MCP client.
| URI | Description |
|---|---|
molted://mailboxes | All mailboxes with address, reputation, autonomy, and send limits |
molted://mailboxes/{id}/config | Full mailbox configuration: rules, SLA, alerts, autonomy level |
molted://templates | Available email templates with variable schemas |
molted://templates/{id} | Specific template with version history |
molted://segments | Audience segments with filter definitions |
molted://journeys | Active email journeys with step definitions |
molted://policy-rules | Current policy rules and thresholds |
molted://domains | Verified sending domains with DNS and warmup status |
Resources return JSON data from the Molted API. Template URIs ({id} parameters) auto-populate from the list callback, so MCP clients that support resource templates will show available mailboxes and templates as completions.
Prompts
Prompts are pre-built multi-step workflows that agents can invoke. Each prompt returns a message sequence that guides the agent through chaining multiple tools together.
draft-followup
Given a thread, draft an appropriate follow-up email. Reads the thread, checks budget, and composes a reply matching the conversation tone.
| Argument | Type | Required | Description |
|---|---|---|---|
thread_id | string | Yes | Thread UUID to follow up on |
mailbox_id | string | No | Mailbox UUID to send from |
triage-inbox
Process unread threads: classify intent, recommend action, and auto-archive resolved/spam/OOO threads.
| Argument | Type | Required | Description |
|---|---|---|---|
mailbox_id | string | No | Mailbox UUID to triage (omit for all) |
max_threads | string | No | Maximum threads to process (default 20) |
pre-send-check
Before sending an email, verify it is safe: check suppression status, consent, and remaining budget. Returns a GO / NO-GO recommendation.
| Argument | Type | Required | Description |
|---|---|---|---|
to_email | string | Yes | Recipient email to validate |
mailbox_id | string | No | Mailbox UUID to send from |
investigate-bounce
Investigate why an email bounced and recommend corrective action. Automatically suppresses hard bounces.
| Argument | Type | Required | Description |
|---|---|---|---|
thread_id | string | Yes | Thread UUID of the bounced email |
mailbox_id | string | No | Mailbox UUID |
campaign-readiness
Check if conditions are right to send a campaign to a segment: budget capacity, template readiness, domain health, and negative-signal budget.
| Argument | Type | Required | Description |
|---|---|---|---|
segment_id | string | Yes | Segment UUID |
mailbox_id | string | No | Mailbox UUID |
template_id | string | No | Template UUID |
Error handling
The MCP server maps API responses to structured tool results:
| HTTP Status | Behavior |
|---|---|
| 2xx | Success with parsed JSON data |
| 429 | Rate limited -- includes retry timing |
| 403 | Insufficient scope -- key lacks required permissions |
| 401 | Authentication error -- invalid or expired key |
| 404 | Resource not found |
| 5xx | Server error |
Policy blocks (e.g., suppressed contact, rate limit exceeded) are returned as successful results with the block reason. This lets your agent reason about why the send was blocked and decide on alternatives rather than treating it as an error.
Scoped keys
API keys can be scoped to specific mailboxes. When using a scoped key, all tools automatically operate within that mailbox's context. This is useful for multi-tenant setups where each agent should only access its own mailbox. See Authentication for details on key scoping.