# dev.primitive/email (remote · www.primitive.dev)

Email infrastructure for AI agents — send, receive, search, and reply to email over MCP.

- Trust score: 84/100 (high trust)
- Change this week: +8
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-08-03

## Components

- remote · `www.primitive.dev`: 84/100 (this document), [markdown](https://verifymcp.io/servers/dev-primitive-email/www.md), [page](https://verifymcp.io/servers/dev-primitive-email/www)

## Channel facts

- Endpoint: `https://www.primitive.dev/mcp`
- Transports: `streamable-http`
- Auth: `none`
- Version: `0.1.0`

## Trust breakdown

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. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-08-03.

- **Endpoint Security**: 97/100
  - The endpoint's TLS certificate is valid, in date, and uses a strong key.
  - 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.
  - HTTPS is enforced; there's no plaintext access path.
  - The HSTS (Strict-Transport-Security) header is present.
  - DNSSEC is configured correctly; the domain's records validate against the full chain to the root.
  - The authorisation server offers only Dynamic Client Registration (RFC 7591), which MCP 2026-07-28 deprecated in favour of Client ID Metadata Documents.
- **Transport & Reachability**: 100/100
  - Verified streamable-http transport via a live MCP handshake.
- **Schema Quality & AI Usability**: 84/100
  - 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).
  - AI-judged instruction clarity (good).
  - Tool/resource definitions use about 2978 tokens (~99/item across 30 items; 29 tools + 1 resources), lean.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 27/100
  - Stability observed for 8 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 97/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 91% of tool parameters carry a description.
  - Structured output schemas are declared (100% of tools); any adoption earns full credit.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.
  - Supports UI / widget rendering.

## Install

### Claude

```bash
claude mcp add --transport http dev-primitive-email https://www.primitive.dev/mcp
```

### Codex

```toml
[mcp_servers.dev-primitive-email]
url = "https://www.primitive.dev/mcp"
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "dev-primitive-email": {
      "type": "remote",
      "url": "https://www.primitive.dev/mcp",
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add dev-primitive-email --url https://www.primitive.dev/mcp --transport streamable-http
```

### Hermes

```yaml
mcp_servers:
  dev-primitive-email:
    url: "https://www.primitive.dev/mcp"
```

### Other

```json
{
  "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.

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-08-02 (score 84, +1)

No change was recorded against any check on this day. Stability & Change Management went from 20 to 23. That category is still filling its 30-day observation window: 6 days of observed history at the previous scan, 7 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-08-01 (score 83, +5)

- [functional] We updated how we score, so this day's move reflects our rubric, not a change to the server

### 2026-07-31 (score 78, 0)

- [functional] We updated how we score, so this day's move reflects our rubric, not a change to the server

### 2026-07-29 (score 78, +1)

No change was recorded against any check on this day. Stability & Change Management went from 7 to 10. That category is still filling its 30-day observation window: 2 days of observed history at the previous scan, 3 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-07-28 (score 77, +1)

No change was recorded against any check on this day. Stability & Change Management went from 3 to 7. That category is still filling its 30-day observation window: 1 days of observed history at the previous scan, 2 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-07-27 (score 76, 0)

- [functional] We updated how we score, so this day's move reflects our rubric, not a change to the server

### 2026-07-26 (score 76)

First indexed and scored.

## MCP tools (29)

### `listDomains` (~45 tokens)

List domains

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.

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `addDomain` (~72 tokens)

Add domain

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.

Input parameters:

- `requestBody` (object, required)

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `verifyDomain` (~81 tokens)

Verify domain

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.

Input parameters:

- `id` (string, required): Domain ID returned by addDomain or listDomains.

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `downloadDomainZoneFile` (~104 tokens)

Download DNS zone file

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.

Input parameters:

- `id` (string, required): 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.

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `getAccount` (~33 tokens)

Get account

Use this when you need the authenticated Primitive account summary, including email, plan, onboarding state, and webhook secret rotation time.

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `getInboxStatus` (~37 tokens)

Get inbox status

Use this when the user asks whether inbound email is ready or needs setup. Returns domains, routes, deployed Functions, and recent inbound activity.

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `getOutboundStatus` (~76 tokens)

Get outbound status

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.

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `listEmails` (~300 tokens)

List inbound emails

Use this when you need to browse inbound emails received at verified domains with cursor pagination, status filters, date filters, or sender/recipient search.

Input parameters:

- `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…

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `searchEmails` (~383 tokens)

Search inbound emails

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.

Input parameters:

- `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.

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `getEmail` (~54 tokens)

Get email

Use this when you need full details for one inbound email ID, including parsed bodies, threading metadata, SMTP envelope, webhook state, and replies.

Input parameters:

- `id` (string, required): Inbound email ID from listEmails or searchEmails.

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `replyToEmail` (~65 tokens)

Reply to email

Use this when the user has selected a specific inbound email and confirmed a reply. Sends real outbound email with threading handled server-side.

Input parameters:

- `id` (string, required): Inbound email ID to reply to. Threading headers are set server-side.
- `requestBody` (object, required)

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `sendEmail` (~90 tokens)

Send email

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.

Input parameters:

- `Idempotency-Key` (string): Client-supplied idempotency key. Retrying with the same key returns the original result without re-sending.
- `requestBody` (object, required)

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `sendEmailDemo` (~136 tokens)

Try a send (no signup)

Send a SIMULATED email with no account required. Validates the body against the exact same schema as sendEmail (including cc/bcc, reply_to, tags, attachments, and scheduled_at), then returns a realistic synthetic success envelope (demo: true); it never actually sends, queues, or stores anything. Demo requests are capped at 16KB total body, so large attachments are rejected even though the schema allows them. Use this to let someone try Primitive and see the response shape before they sign up. To send for real, sign up for an API key and use sendEmail.

Input parameters:

- `requestBody` (object, required)

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `listSentEmails` (~229 tokens)

List sent emails

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.

Input parameters:

- `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.

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `getSentEmail` (~81 tokens)

Get sent email

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.

Input parameters:

- `id` (string, required): Sent email ID from listSentEmails or a /send-mail response.

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `cancelScheduledSend` (~93 tokens)

Cancel scheduled send

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.

Input parameters:

- `id` (string, required): Sent email ID of the scheduled send, from a sendEmail response or listSentEmails.

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `awaitReply` (~183 tokens)

Await reply

Get the threaded reply to a sent email — the canonical 'did they reply yet?' call. Pass the sent email's id (from a sendEmail response). With wait=true it long-polls up to wait_timeout_ms for the reply to arrive (synchronous agent-to-agent chat); with wait=false (default) it returns immediately with the reply if one has arrived, else reply=null. Matches on the reply's threading, not a from/subject guess.

Input parameters:

- `id` (string, required): The sent email id to fetch the reply for (from a sendEmail/replyToEmail response).
- `wait` (string): When "true", long-poll until a reply arrives or wait_timeout_ms elapses. Default no-wait.
- `wait_timeout_ms` (number): How long to wait when wait=true (1000–30000 ms, default 10000).

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `getConversation` (~103 tokens)

Get conversation

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.

Input parameters:

- `id` (string, required): ID of any inbound email in the conversation.

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `getThread` (~100 tokens)

Get thread

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.

Input parameters:

- `id` (string, required): Thread ID from the thread_id field on any email or sent-email.

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `downloadEmailAttachments` (~94 tokens)

Download email attachments

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.

Input parameters:

- `id` (string, required): Inbound email ID.
- `token` (string): Signed download token from a webhook payload. Optional — the bearer token is used when this is omitted.

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `listEndpoints` (~31 tokens)

List webhook endpoints

List all active webhook endpoints for the organization. Each endpoint shows its URL, enabled state, and optional domain restriction.

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `createEndpoint` (~61 tokens)

Create webhook endpoint

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.

Input parameters:

- `requestBody` (object, required)

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `deleteEndpoint` (~37 tokens)

Delete webhook endpoint

Soft-delete a webhook endpoint. The endpoint will no longer receive deliveries.

Input parameters:

- `id` (string, required): Endpoint ID from listEndpoints or createEndpoint.

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `testEndpoint` (~60 tokens)

Test webhook endpoint

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.

Input parameters:

- `id` (string, required): Endpoint ID from listEndpoints or createEndpoint.

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `listFilters` (~20 tokens)

List filters

List all whitelist and blocklist filter rules for the organization.

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `createFilter` (~38 tokens)

Create filter

Create a whitelist or blocklist filter rule. Patterns are stored lowercase. Per-domain filters require a Pro plan.

Input parameters:

- `requestBody` (object, required)

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `deleteFilter` (~29 tokens)

Delete filter

Delete a filter rule.

Input parameters:

- `id` (string, required): Filter rule ID from listFilters or createFilter.

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `listWebhookDeliveries` (~155 tokens)

List webhook deliveries

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.

Input parameters:

- `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.

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

### `replayWebhookDelivery` (~60 tokens)

Replay webhook delivery

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).

Input parameters:

- `id` (string, required): Delivery ID (numeric string from listWebhookDeliveries).

Output parameters:

- `data`
- `status` (number)
- `statusText` (string)
- `tool` (string)

## Diagnostics

Captured diagnostic sections: TLS, DNSSEC, Authorisation, Transports. The full working is on the page: https://verifymcp.io/servers/dev-primitive-email/www#diagnostics

## Score history

- 2026-08-03: 84
- 2026-08-02: 84
- 2026-08-01: 83
- 2026-07-31: 78
- 2026-07-30: 78
- 2026-07-29: 78
- 2026-07-28: 77
- 2026-07-27: 76
- 2026-07-26: 76

## Links

- Remote endpoint: https://www.primitive.dev/mcp
- Repository: https://github.com/primitivedotdev/primitive-mono-repo
- Website: https://www.primitive.dev/
- Changelog RSS feed: https://verifymcp.io/servers/dev-primitive-email/www/changelog.xml
- Changelog JSON feed: https://verifymcp.io/servers/dev-primitive-email/www/changelog.json
- HTML version of this page: https://verifymcp.io/servers/dev-primitive-email/www
