dev.primitive/email
REMOTE · WWW.PRIMITIVE.DEV · SCANNED SEP 20
Email infrastructure for AI agents — send, receive, search, and reply to email over MCP.
Available components
How this component scores in each security and reliability category. Every signal is checked automatically against the live server, and we only credit what we can confirm. How we score → Why this is hard to score →
Endpoint Security97
- The endpoint's TLS certificate is valid, in date, and uses a strong key. View diagnostics → Pass
- Authorisation is enforced on tool calls, advertised via RFC 9728 protected-resource metadata. Discovery is public, which costs nothing: no tool can be invoked without a token. View diagnostics → Pass
- HTTPS is enforced; there's no plaintext access path. View diagnostics → Pass
- The HSTS (Strict-Transport-Security) header is present. View diagnostics → Pass
- DNSSEC is configured correctly; the domain's records validate against the full chain to the root. View diagnostics → Pass
- The authorisation server offers only Dynamic Client Registration (RFC 7591), which MCP 2026-07-28 deprecated in favour of Client ID Metadata Documents. View diagnostics → Partial
Transport & Reachability100
- Verified streamable-http transport via a live MCP handshake. View diagnostics → Pass
Schema Quality & AI Usability81
- 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).Pass
- AI-judged instruction clarity (excellent).Pass
- Context-footprint check failed: tool/resource definitions use about 4508 tokens (~145/item across 31 items; 30 tools + 1 resources), over budget; trim descriptions and params. See how to fix → Fail
- Usage-examples check failed: none of the tools include examples. See how to fix → Fail
Stability & Change Management100
- No destabilizing schema changes in the last 30 days.Pass
Tool Coverage97
- 100% of tools have a non-trivial description (not blank, and not just the tool's name).Pass
- 90% of tool parameters carry a description.Partial
- Structured output schemas are declared (100% of tools); any adoption earns full credit.Pass
Tool Safety96
- No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.Pass
- 5 of 6 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation; "sendEmailDemo" implies "send" and declares readOnlyHint instead, contradicting what its own name says it does. See how to fix → Partial
- An AI judge read all 32 captured unit(s) of tool text and found none that tries to manipulate the model reading it.Pass
Capabilities100
- Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.Pass
- Supports UI / widget rendering.Pass
How do I install the dev.primitive/email MCP server?
dev.primitive/email is a hosted endpoint at https://www.primitive.dev/mcp, so there is nothing to install locally. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.
remote · www.primitive.dev
claude mcp add --transport http dev-primitive-email 'https://www.primitive.dev/mcp'
{
"mcpServers": {
"dev-primitive-email": {
"url": "https://www.primitive.dev/mcp"
}
}
} {
"servers": {
"dev-primitive-email": {
"type": "http",
"url": "https://www.primitive.dev/mcp"
}
}
} [mcp_servers.dev-primitive-email] url = "https://www.primitive.dev/mcp"
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"dev-primitive-email": {
"type": "remote",
"url": "https://www.primitive.dev/mcp",
"enabled": true
}
}
} openclaw mcp add dev-primitive-email --url 'https://www.primitive.dev/mcp' --transport streamable-http
mcp_servers:
dev-primitive-email:
url: "https://www.primitive.dev/mcp" {
"McpServers": {
"dev-primitive-email": {
"Transport": "http",
"Url": "https://www.primitive.dev/mcp"
}
}
} assistant mcp add dev-primitive-email -t streamable-http -u 'https://www.primitive.dev/mcp'
{
"mcpServers": {
"dev-primitive-email": {
"type": "http",
"url": "https://www.primitive.dev/mcp"
}
}
} The mcpServers block is a cross-client convention. Remote transports vary, so check your client's docs.
Every change we have recorded for this component, newest first. Security-relevant changes are always shown. ▲ marks a change for the better, ▼ a change for the worse; unmarked changes are neutral.
- 17 Sept 26 0
- Stability: fail → pass ▲ security
- 26 Aug 26 +1
- We updated how we score, so this day's move reflects our rubric, not a change to the server See what changed → functional
- 24 Aug 26 +1
No change was recorded against any check on this day. Stability & Change Management went from 91 to 95.
- 18 Aug 26 0
- Stability: 0.73 → fail ▼ security
- A breaking change shipped without a version bump: still 0.1.0 ▼ security
- The server rewrote its instructions, which are the text every model session reads security
- Tool “awaitReply” rewrote its description, which is the text the model reads security
- Tool “createEmailAddress” rewrote its description, which is the text the model reads security
- Tool “getAccount” rewrote its description, which is the text the model reads security
- Tool “sendEmail” rewrote its description, which is the text the model reads security
- Schema quality: 115 → 145 ▼ functional
- “awaitReply” changed the type of “wait”: string → boolean ▼ functional
- “awaitReply” added an optional parameter “api_key” cosmetic
- “awaitReply” reworded the description of “wait” cosmetic
- “awaitReply” reworded the description of “wait_timeout_ms” cosmetic
- Tool “createEmailAddress” changed its title: Get an email address (no signup) → Get a managed email domain (no signup) cosmetic
- 14 Aug 26 0
- Tool “sendEmailDemo” rewrote its description, which is the text the model reads security
- Schema quality: pass → fail ▼ functional
- New tool “createEmailAddress” functional
- “getAccount” added an optional parameter “api_key” cosmetic
- “getEmail” added an optional parameter “api_key” cosmetic
- “getInboxStatus” added an optional parameter “api_key” cosmetic
- “listEmails” added an optional parameter “api_key” cosmetic
- “replyToEmail” added an optional parameter “api_key” cosmetic
- “sendEmail” added an optional parameter “api_key” cosmetic
- Tool “sendEmailDemo” changed its title: Try a send (no signup) → Preview a send (simulated, nothing is delivered) cosmetic
- 11 Aug 26 0
- We updated how we score, so this day's move reflects our rubric, not a change to the server See what changed → functional
- 1 Aug 26 0
- We updated how we score, so this day's move reflects our rubric, not a change to the server See what changed → functional
- 31 Jul 26 0
- We updated how we score, so this day's move reflects our rubric, not a change to the server See what changed → functional
Diagnostic detail from the automated scan of this channel: what the scanner observed at each step, so you can see exactly where a check passed or failed. It is informational only and never changes the trust score.
Captured 20 Sept 2026 · Probed https://www.primitive.dev/mcp
TLS valid
Negotiated TLS 1.3 with TLS_AES_128_GCM_SHA256 .
| Subject | Issuer | Valid from | Valid until | Key | Signature | Serial |
|---|---|---|---|---|---|---|
| CN=primitive.dev | CN=YE2,O=Let's Encrypt,C=US | 1 Aug 2026 | 30 Oct 2026 | ECDSA 256 | ECDSA-SHA384 | 6fbed9ebd59789d8bd951e2445d19856f00 |
| SANs: *.primitive.dev, primitive.dev | ||||||
| CN=YE2,O=Let's Encrypt,C=US (CA) | CN=Root YE,O=ISRG,C=US | 3 Sept 2025 | 2 Sept 2028 | ECDSA 384 | ECDSA-SHA384 | 4df3b15dd6c0784c507cd37b58e6f115 |
| CN=Root YE,O=ISRG,C=US (CA) | CN=ISRG Root X2,O=Internet Security Research Group,C=US | 13 May 2026 | 2 Sept 2032 | ECDSA 384 | ECDSA-SHA384 | 872165fc34b6e5fba8add5b3705fb53a |
| CN=ISRG Root X2,O=Internet Security Research Group,C=US (CA) | CN=ISRG Root X1,O=Internet Security Research Group,C=US | 13 May 2026 | 2 Sept 2032 | ECDSA 384 | SHA256-RSA | 6c8f1dc727c7117f7baf853ac980f9cd |
Background: What to check on a remote MCP endpoint →
DNSSEC secure
Validation of www.primitive.dev. — Secure
| Zone | DS | Keys | Algorithms | Outcome |
|---|---|---|---|---|
| . | trust_anchor | 20326, 38696 | 8, 8 | Verified |
| dev. | present | 60074 | 8 | Verified |
| primitive.dev. | present | 2371 | 13 | Verified |
| www.primitive.dev. | Verified address RRset verified with the apex keys |
Authentication Enforced and verified
The endpoint asked for a token and published valid RFC 9728 metadata describing how to get one.
| Result | Enforced and verified |
|---|---|
| Enforced | On tool calls |
| HTTP status | 200 |
WWW-Authenticate challenge Bearer realm="Primitive MCP", resource_metadata="https://www.primitive.dev/.well-known/oauth-protected-resource/mcp", error="invalid_token", error_description="Bearer token required for /mcp"
Bearer realm="Primitive MCP", resource_metadata="https://www.primitive.dev/.well-known/oauth-protected-resource/mcp", error="invalid_token", error_description="Bearer token required for /mcp" | Header | Value |
|---|---|
| strict-transport-security | max-age=63072000; includeSubDomains |
| content-security-policy | base-uri 'self'; form-action 'self'; frame-ancestors 'none'; object-src 'none' |
| x-content-type-options | nosniff |
| x-frame-options | DENY |
| referrer-policy | strict-origin-when-cross-origin |
| permissions-policy | accelerometer=(), bluetooth=(), browsing-topics=(), camera=(), geolocation=(), gyroscope=(), hid=(), magnetometer=(), microphone=(), payment=(), serial=(), usb=() |
Protected resource metadata
| Document | https://www.primitive.dev/.well-known/oauth-protected-resource/mcp |
|---|---|
| Retrieved | Yes |
| Resource | https://www.primitive.dev/mcp |
| Authorisation server | https://www.primitive.dev |
Background: How OAuth 2.1 works in the 2026 MCP spec →
Transports 2 probes
| Transport | URL | Outcome | Status | Location |
|---|---|---|---|---|
| streamable-http | https://www.primitive.dev/mcp | Verified | 200 | |
| http (plaintext) | http://www.primitive.dev/mcp | HTTPS enforced | 301 | https://www.primitive.dev/mcp |
The tools this component advertises to a client, with an estimated token cost for each. Expand a tool to see its parameters and schema. The per-tool counts are indicative and are not scored directly; the schema's total context footprint is one signal in Schema Quality & AI Usability. A tool's description is untrusted text the model reads on every call, which is what makes this list a security surface and not just an inventory: how tool poisoning works →
addDomain Add domain ~72
Claim a new domain and receive the DNS records to publish. Returns dns_records with the exact records to add. If the domain has an mx_conflict (existing mail provider), re-call with confirmed: true to proceed. After publishing DNS records, call verifyDomain to complete setup.
| Name | Type | Req | Description |
|---|---|---|---|
| requestBody | object | yes | – |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
awaitReply Await reply ~246
Wait for the threaded reply to a sent email. Call this immediately after sendEmail or replyToEmail when you expect an answer. With only the sent email's id it long-polls for up to 30 seconds by default. Do not report that nobody replied or ask the user to check later until this call returns with no reply. Set wait=false only for a deliberate immediate poll. Matching uses reply threading, not a sender or subject guess.
| Name | Type | Req | Description |
|---|---|---|---|
| api_key | string | – | Primitive API key (prim_...). Only needed when this connection has no signed-in account: pass the api_key returned by createEmailAddress, unchanged, on every call for the rest of the conversation. Om… |
| id | string | yes | The sent email id to fetch the reply for (from a sendEmail/replyToEmail response). |
| wait | boolean | – | When true, long-poll until a reply arrives or wait_timeout_ms elapses. Defaults to true. Set false only for a single immediate poll. |
| wait_timeout_ms | number | – | How long to wait when wait=true (1000 to 30000 ms). Defaults to 30000. |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
cancelScheduledSend Cancel scheduled send ~93
Cancel a still-scheduled send before it dispatches (terminal status "canceled"; the same idempotency key becomes reusable). Pass the sent email id from a sendEmail response that used scheduled_at, or from listSentEmails. Returns a conflict error once the send has already started dispatching.
| Name | Type | Req | Description |
|---|---|---|---|
| id | string | yes | Sent email ID of the scheduled send, from a sendEmail response or listSentEmails. |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
createEmailAddress Get a managed email domain (no signup) ~244
Get this user's Primitive managed inbox domain, creating the account on first use: no signup form, no password, no email verification, no browser. CALL THIS FIRST whenever you do not already have a Primitive API key and the user wants to send or receive email, instead of telling them to sign up or visit a website. On first use the REST response's address field is a domain such as pink-ram.primitive.email, not a complete mailbox. When it is non-null, this MCP result also includes sender_address such as agent@pink-ram.primitive.email for use as sendEmail.requestBody.from. It also returns an api_key. IMPORTANT: pass that api_key as the `api_key` argument on every later Primitive tool call in this conversation, exactly as returned; those calls fail without it. If this user already has a domain, the result repeats it with existing_account: true. Report it as their existing managed inbox rather than announcing a new one, and calling again will not produce a different one. The result also carries an upgrade link the user can open whenever they want to attach this domain to a full account.
| Name | Type | Req | Description |
|---|---|---|---|
| requestBody | object | yes | – |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
createEndpoint Create webhook endpoint ~61
Create a webhook endpoint to receive email.received events. If an endpoint with the same URL already exists but is deactivated, it is reactivated. After creating, call testEndpoint to confirm your signature verifier accepts the payload.
| Name | Type | Req | Description |
|---|---|---|---|
| requestBody | object | yes | – |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
createFilter Create filter ~38
Create a whitelist or blocklist filter rule. Patterns are stored lowercase. Per-domain filters require a Pro plan.
| Name | Type | Req | Description |
|---|---|---|---|
| requestBody | object | yes | – |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
deleteEndpoint Delete webhook endpoint ~37
Soft-delete a webhook endpoint. The endpoint will no longer receive deliveries.
| Name | Type | Req | Description |
|---|---|---|---|
| id | string | yes | Endpoint ID from listEndpoints or createEndpoint. |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
deleteFilter Delete filter ~29
Delete a filter rule.
| Name | Type | Req | Description |
|---|---|---|---|
| id | string | yes | Filter rule ID from listFilters or createFilter. |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
downloadDomainZoneFile Download DNS zone file ~104
Download a BIND-format DNS zone file for a domain. Useful when users want to import all required DNS records at once rather than copying them individually. Returns plain text in BIND zone file format.
| Name | Type | Req | Description |
|---|---|---|---|
| id | string | yes | Domain ID from listDomains or addDomain. |
| outbound_only | boolean | – | When true, include only outbound DNS records (SPF, DKIM, DMARC). Defaults to all records for unverified domains, outbound-only for verified. |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
downloadEmailAttachments Download email attachments ~94
Download all attachments for an inbound email as a gzip-compressed tar archive. Returns the archive as a base64-encoded string along with the attachment count and SHA-256 digest. Prefer getEmail first to check the attachment manifest before downloading.
| Name | Type | Req | Description |
|---|---|---|---|
| id | string | yes | Inbound email ID. |
| token | string | – | Signed download token from a webhook payload. Optional — the bearer token is used when this is omitted. |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
getAccount Get account ~134
Use this when you need the authenticated Primitive account summary, including plan, onboarding state, and managed inbox domain. managed_inbox_address is a domain, not a complete mailbox. When it is non-null, this MCP result also includes sender_address, such as agent@pink-ram.primitive.email, for use as sendEmail.requestBody.from.
| Name | Type | Req | Description |
|---|---|---|---|
| api_key | string | – | Primitive API key (prim_...). Only needed when this connection has no signed-in account: pass the api_key returned by createEmailAddress, unchanged, on every call for the rest of the conversation. Om… |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
getConversation Get conversation ~103
Get the full conversation an inbound email belongs to as ordered, chat-model-ready turns with bodies. Each message is oldest-first with a direction (inbound/outbound) and a derived role (inbound→user, outbound→assistant). For a brand-new message, returns just that one turn. The response includes a truncated boolean (true when the message cap was reached) and a message_count field.
| Name | Type | Req | Description |
|---|---|---|---|
| id | string | yes | ID of any inbound email in the conversation. |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
getEmail Get email ~111
Use this when you need full details for one inbound email ID, including parsed bodies, threading metadata, SMTP envelope, webhook state, and replies.
| Name | Type | Req | Description |
|---|---|---|---|
| api_key | string | – | Primitive API key (prim_...). Only needed when this connection has no signed-in account: pass the api_key returned by createEmailAddress, unchanged, on every call for the rest of the conversation. Om… |
| id | string | yes | Inbound email ID from listEmails or searchEmails. |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
getInboxStatus Get inbox status ~94
Use this when the user asks whether inbound email is ready or needs setup. Returns domains, routes, deployed Functions, and recent inbound activity.
| Name | Type | Req | Description |
|---|---|---|---|
| api_key | string | – | Primitive API key (prim_...). Only needed when this connection has no signed-in account: pass the api_key returned by createEmailAddress, unchanged, on every call for the rest of the conversation. Om… |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
getOutboundStatus Get outbound status ~76
What can I send FROM? Lists this account's verified outbound (sendable) domains plus any domains still pending DNS verification, with next actions. Call this BEFORE sendEmail to pick a valid `from` domain — the account email is not necessarily sendable. The same sendable list is echoed in a cannot_send_from_domain error.
Input schema present but exposes no named parameters.
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
getSentEmail Get sent email ~81
Get the full record for a single sent email by id, including body_text and body_html. Use to inspect delivery details for a specific send — e.g. the SMTP response on a bounced row, or the gate denial reason on a gate_denied row.
| Name | Type | Req | Description |
|---|---|---|---|
| id | string | yes | Sent email ID from listSentEmails or a /send-mail response. |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
getThread Get thread ~100
Get a conversation thread by id: metadata plus all inbound and outbound messages interleaved oldest-first. Each message has a direction (inbound/outbound) and id; fetch inbound message bodies via getEmail, or outbound bodies via getSentEmail. Discover thread_id from any email or sent-email record. Compare message_count against messages.length to detect truncation.
| Name | Type | Req | Description |
|---|---|---|---|
| id | string | yes | Thread ID from the thread_id field on any email or sent-email. |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
listDomains List domains ~45
List all inbound domains for the organization, both verified and unverified. Each domain includes its verification status and DNS records. Use before addDomain to check whether a domain is already claimed.
Input schema present but exposes no named parameters.
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
listEmails List inbound emails ~357
Use this when you need to browse inbound emails received at verified domains with cursor pagination, status filters, date filters, or sender/recipient search.
| Name | Type | Req | Description |
|---|---|---|---|
| api_key | string | – | Primitive API key (prim_...). Only needed when this connection has no signed-in account: pass the api_key returned by createEmailAddress, unchanged, on every call for the rest of the conversation. Om… |
| cursor | string | – | Backward (history) pagination cursor from a previous response's `meta.cursor` field: returns emails OLDER than this position, newest-first. Mutually exclusive with `since`. |
| date_from | string | – | Inclusive lower bound on created_at (ISO 8601). |
| date_to | string | – | Inclusive upper bound on created_at (ISO 8601). |
| domain_id | string | – | Filter to emails received on a specific verified domain. |
| limit | number | – | Maximum number of emails to return (1–100, default 50). |
| search | string | – | Free-text search across sender, recipient, and subject. |
| since | string | – | Forward-tail cursor (a `meta.cursor` value, format `{ISO datetime}|{UUID}`): returns emails strictly NEWER than this position, oldest-first, for lossless polling of just-arrived mail. Mutually exclus… |
| status | string | – | Filter to emails in this processing status. |
| wait | number | – | Long-poll up to this many seconds (0–30, default 0) holding the request for new mail instead of returning an empty page immediately. Requires `since`. Returns as soon as matching mail arrives, or an… |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
listEndpoints List webhook endpoints ~31
List all active webhook endpoints for the organization. Each endpoint shows its URL, enabled state, and optional domain restriction.
Input schema present but exposes no named parameters.
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
listFilters List filters ~20
List all whitelist and blocklist filter rules for the organization.
Input schema present but exposes no named parameters.
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
listSentEmails List sent emails ~229
List outbound emails sent by this org, with cursor pagination and filters. Bodies are omitted from list rows to keep responses small — use getSentEmail to fetch a specific row with full body. Useful for auditing delivery status, finding bounced sends, or checking gate-denied attempts.
| Name | Type | Req | Description |
|---|---|---|---|
| cursor | string | – | Pagination cursor from a previous response's `meta.cursor` field. |
| date_from | string | – | Inclusive lower bound on created_at (ISO 8601). |
| date_to | string | – | Inclusive upper bound on created_at (ISO 8601). |
| idempotency_key | string | – | Filter to rows with the given client idempotency key. |
| limit | number | – | Maximum number of sent emails to return (1–100, default 50). |
| request_id | string | – | Filter to the row matching a specific server-issued request_id from a /send-mail response. |
| status | string | – | Filter to rows in this status. Use "scheduled" to find pending scheduled sends (cancelable via cancelScheduledSend) and "canceled" for ones already canceled. |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
listWebhookDeliveries List webhook deliveries ~155
List webhook delivery attempts with pagination and filters. Each delivery includes the target endpoint and a nested email object with sender/recipient/subject. Useful for diagnosing delivery failures or confirming a specific email was delivered.
| Name | Type | Req | Description |
|---|---|---|---|
| cursor | string | – | Pagination cursor from a previous response's `meta.cursor` field. |
| date_from | string | – | Inclusive lower bound on attempted_at (ISO 8601). |
| date_to | string | – | Inclusive upper bound on attempted_at (ISO 8601). |
| email_id | string | – | Filter by inbound email ID. |
| limit | number | – | Maximum number of delivery records to return (1–100, default 50). |
| status | string | – | Filter to deliveries in this status. |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
replayWebhookDelivery Replay webhook delivery ~60
Re-send a stored webhook payload from a previous delivery attempt to its original endpoint. Rate limited per org (burst + sustained windows, shared budget with email webhook replays).
| Name | Type | Req | Description |
|---|---|---|---|
| id | string | yes | Delivery ID (numeric string from listWebhookDeliveries). |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
replyToEmail Reply to email ~122
Use this when the user has selected a specific inbound email and confirmed a reply. Sends real outbound email with threading handled server-side.
| Name | Type | Req | Description |
|---|---|---|---|
| api_key | string | – | Primitive API key (prim_...). Only needed when this connection has no signed-in account: pass the api_key returned by createEmailAddress, unchanged, on every call for the rest of the conversation. Om… |
| id | string | yes | Inbound email ID to reply to. Threading headers are set server-side. |
| requestBody | object | yes | – |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
searchEmails Search inbound emails ~383
Use this when you need to find inbound emails with structured filters or full-text matching. Use sort=received_at_asc plus date_from for new-mail polling.
| Name | Type | Req | Description |
|---|---|---|---|
| body | string | – | Filter to emails whose body contains this string. |
| cursor | string | – | Pagination cursor from a previous response's `meta.cursor` field. |
| date_from | string | – | Inclusive lower bound on received_at (ISO 8601). |
| date_to | string | – | Inclusive upper bound on received_at (ISO 8601). |
| domain_id | string | – | Filter to emails received on a specific verified domain. |
| from | string | – | Filter to emails from this sender address or domain. |
| has_attachment | string | – | Filter to emails that have ("true") or lack ("false") attachments. |
| include_facets | string | – | Include aggregated facet counts (sender, domain, status) in the response (default true). |
| limit | number | – | Maximum number of emails to return (1–100, default 50). |
| q | string | – | Full-text query matched across subject, body, sender, and recipient. |
| reply_to_sent_email_id | string | – | Filter to inbound emails that are replies to a specific sent email ID. |
| snippet | string | – | Include a short body snippet in each result (default true). |
| sort | string | – | Sort order. Use received_at_asc with date_from for polling new mail. |
| spam_score_gte | number | – | Filter to emails with a spam score at or above this value. |
| spam_score_lt | number | – | Filter to emails with a spam score strictly below this value. |
| status | string | – | Filter to emails in this processing status. |
| subject | string | – | Filter to emails whose subject contains this string. |
| to | string | – | Filter to emails addressed to this recipient. |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
sendEmail Send email ~235
Use this when the user has confirmed a new outbound email. Sends real email through Primitive's relay and can wait for the first SMTP delivery outcome, or schedule the send for a future time with scheduled_at. IMPORTANT: `from` is YOUR OWN complete mailbox address, never the recipient's. Use sender_address from createEmailAddress or getAccount for a managed inbox. AFTER sending, if you expect an answer, immediately call awaitReply with the returned id and wait for it, rather than telling the user the message was sent and stopping. Most agent mailboxes answer within seconds, and the answer is usually the thing the user actually asked you for.
| Name | Type | Req | Description |
|---|---|---|---|
| Idempotency-Key | string | – | Client-supplied idempotency key. Retrying with the same key returns the original result without re-sending. |
| api_key | string | – | Primitive API key (prim_...). Only needed when this connection has no signed-in account: pass the api_key returned by createEmailAddress, unchanged, on every call for the rest of the conversation. Om… |
| requestBody | object | yes | – |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
sendEmailDemo Preview a send (simulated, nothing is delivered) ~180
SIMULATION ONLY: nothing is delivered. NEVER call this when the user actually wants an email to arrive, and never describe its result as a sent email: no message is sent, queued, or stored, and the recipient receives nothing. To really send with no account, call createEmailAddress (instant, no signup form and no verification) and then sendEmail with the api_key it returns; that is the correct path for any genuine send request. This tool exists only to preview the response shape: it validates the body against the exact same schema as sendEmail (including cc/bcc, reply_to, tags, attachments, and scheduled_at) and returns a synthetic success envelope marked demo: true. Demo requests are capped at 16KB total body, so large attachments are rejected even though the schema allows them.
| Name | Type | Req | Description |
|---|---|---|---|
| requestBody | object | yes | – |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
testEndpoint Test webhook endpoint ~60
Send a sample email.received event to a webhook endpoint to verify your signature verifier. Rate limited to 4/min and 30/hr. Successful deliveries and verified-domain endpoints are exempt.
| Name | Type | Req | Description |
|---|---|---|---|
| id | string | yes | Endpoint ID from listEndpoints or createEndpoint. |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
verifyDomain Verify domain ~81
Check DNS records for a domain claim (MX, TXT, SPF, DKIM, DMARC). On success the domain becomes verified and starts receiving mail. On failure, returns which checks passed and which still need attention. If DNS propagation is incomplete, wait a few minutes and retry.
| Name | Type | Req | Description |
|---|---|---|---|
| id | string | yes | Domain ID returned by addDomain or listDomains. |
| Name | Type | Req | Description |
|---|---|---|---|
| data | – | yes | – |
| status | number | yes | – |
| statusText | string | – | – |
| tool | string | yes | – |
No examples provided.
What is the dev.primitive/email MCP server?
dev.primitive/email is an MCP server listed in the public MCP registry as dev.primitive/email. Email infrastructure for AI agents, send, receive, search, and reply to email over MCP. This page covers its hosted endpoint (https://www.primitive.dev/mcp).
Is the dev.primitive/email MCP server safe to use?
dev.primitive/email scores 95 out of 100 on VerifyMCP. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.
What tools does the dev.primitive/email MCP server expose?
dev.primitive/email exposes 30 tools: listDomains, addDomain, verifyDomain, downloadDomainZoneFile, getAccount, and 25 more. Their descriptions and schemas cost roughly 3,675 tokens of context every time the server is loaded.
Does the dev.primitive/email MCP server require authentication?
Yes. dev.primitive/email asked us for credentials when we connected, so you will need to authorise it in your MCP client before it can do anything.
Is the dev.primitive/email MCP server still maintained?
dev.primitive/email is still listed as active in the MCP registry. We last reached this channel on 20 September 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.