primitive

by primitivedotdev

3 33.2k downloads Not rated yet MIT

About

Official Node.js, Python, and Go SDKs plus the primitive CLI for Primitive — email infrastructure for AI agents. Generated from the OpenAPI spec.

Details

License
MIT

Explore

- Inbound email parsing and synchronous outbound sending
- Non-custodial x402 payments on Base and Base-Sepolia
- SDKs for Node.js, Python, and Go
- Webhook verification and MIME parsing helpers
- Low-level generated APIs and OpenAPI exports
- Primitive Memories key-value store operations

Setting up with Highlight

This MCP is not yet compatible with Highlight’s one-click setup. However, you can still use it with Highlight by following these steps:

  1. Download and install Highlight from highlightai.com/download
  2. Navigate to the plugins tab and select "Add Custom Plugin"
  3. Configure the plugin with the settings below
    Plugin Name primitive
    Command (node, npx, python, etc.)

    Please refer to the README for specific instructions on how to obtain API keys or other required environment variables.

  4. Enable "Start Automatically" if you want the plugin to start when Highlight launches

From the repository

Install the SDK for your language (npm install @primitivedotdev/sdk, pip install primitivedotdev, or go get github.com/primitivedotdev/sdks/sdk-go@latest). Use receive(...) to parse an incoming email, client(...) to create an outbound client with an API key, and then send(...), reply(...), or forward(...) to act on the email.

listDomains

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.

addDomain

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.

verifyDomain

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.

downloadDomainZoneFile

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.

getAccount

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 [email protected], for use as sendEmail.requestBody.from.

getInboxStatus

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

getOutboundStatus

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.

listEmails

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

searchEmails

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.

getEmail

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

replyToEmail

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

sendEmail

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.

sendEmailDemo

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.

listSentEmails

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.

getSentEmail

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.

cancelScheduledSend

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.

awaitReply

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.

getConversation

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.

getThread

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.

downloadEmailAttachments

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.

listEndpoints

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

createEndpoint

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.

deleteEndpoint

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

testEndpoint

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.

listFilters

List all whitelist and blocklist filter rules for the organization.

createFilter

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

deleteFilter

Delete a filter rule.

listWebhookDeliveries

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.

replayWebhookDelivery

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

createEmailAddress

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 [email protected] 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.

- listDomains: 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.
- addDomain: 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.
- verifyDomain: 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.
- downloadDomainZoneFile: 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.
- getAccount: 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 [email protected], for use as sendEmail.requestBody.from.
- getInboxStatus: Use this when the user asks whether inbound email is ready or needs setup. Returns domains, routes, deployed Functions, and recent inbound activity.
- getOutboundStatus: 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.
- listEmails: Use this when you need to browse inbound emails received at verified domains with cursor pagination, status filters, date filters, or sender/recipient search.
- searchEmails: 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.
- getEmail: Use this when you need full details for one inbound email ID, including parsed bodies, threading metadata, SMTP envelope, webhook state, and replies.
- replyToEmail: Use this when the user has selected a specific inbound email and confirmed a reply. Sends real outbound email with threading handled server-side.
- sendEmail: 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.
- sendEmailDemo: 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.
- listSentEmails: 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.
- getSentEmail: 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.
- cancelScheduledSend: 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.
- awaitReply: 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.
- getConversation: 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.
- getThread: 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.
- downloadEmailAttachments: 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.
- listEndpoints: List all active webhook endpoints for the organization. Each endpoint shows its URL, enabled state, and optional domain restriction.
- createEndpoint: 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.
- deleteEndpoint: Soft-delete a webhook endpoint. The endpoint will no longer receive deliveries.
- testEndpoint: 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.
- listFilters: List all whitelist and blocklist filter rules for the organization.
- createFilter: Create a whitelist or blocklist filter rule. Patterns are stored lowercase. Per-domain filters require a Pro plan.
- deleteFilter: Delete a filter rule.
- listWebhookDeliveries: 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.
- replayWebhookDelivery: 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).
- createEmailAddress: 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 [email protected] 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.

Claude Desktop / Cursor

Paste into your MCP client config file to install this server.

{
    "mcpServers": {
        "primitive": {
            "primitive": {
                "url": "https://www.primitive.dev/mcp",
                "headers": {
                    "Authorization": "Bearer prim_YOUR_API_KEY"
                }
            }
        }
    }
}

McpServers

{
    "primitive": {
        "url": "https://www.primitive.dev/mcp",
        "headers": {
            "Authorization": "Bearer prim_YOUR_API_KEY"
        }
    }
}

SDK Checks

Monorepo for the Primitive SDKs.

Primitive is an inbound and outbound email platform. The SDKs are centered on a
small default workflow:

1. receive an inbound email
2. inspect a normalized email object
3. send, reply, or forward synchronously

SDKs

| SDK | Install target | README |
| --- | --- | --- |
| Node.js | npm install @primitivedotdev/sdk | sdk-node/README.md |
| Python | pip install primitivedotdev | sdk-python/README.md |
| Go | go get github.com/primitivedotdev/sdks/sdk-go@latest | sdk-go/README.md |

Default API shape

Across the SDKs, the default story is:

- receive inbound mail with receive(...)
- create an outbound client with client(...)
- send new mail with send(...)
- continue a thread with reply(...)
- forward a message with forward(...)

The Node.js end-state looks like this:

import primitive from "@primitivedotdev/sdk";

const client = primitive.client({
apiKey: process.env.PRIMITIVE_API_KEY!,
});

export async function POST(req: Request) {
const email = await primitive.receive(req, {
secret: process.env.PRIMITIVE_WEBHOOK_SECRET!,
});

await client.reply(email, "Thank you for your email.");

return Response.json({ ok: true });
}

x402 payments

Each SDK also ships a non-custodial x402 payments client. One agent registers a
payout address and requests a USDC payment; the paying agent signs locally with
its own key and settles. Keys never leave the caller. Networks are base and
base-sepolia, and amounts are token base units (USDC has 6 decimals, so
"10000" is 0.01 USDC). See the "x402 payments" section in each SDK README
(sdk-node, sdk-python, sdk-go) for runnable examples, and the
primitive payments command group in the CLI for the same flow from a terminal.

Advanced surfaces

The low-level and generated APIs still exist for advanced use cases:

- webhook verification/parsing helpers
- generated HTTP API packages
- Primitive Memories key-value operations
- OpenAPI exports
- contract tooling
- raw MIME parsing helpers
- CLI

The SDK refresh keeps those escape hatches available, but the primary docs story
focuses on the inbound/outbound automation flow above.

Repository layout

sdks/
  .github/workflows/
  openapi/
  json-schema/
  sdk-go/
  sdk-node/
  sdk-python/
  test-fixtures/

Development

Use the root Makefile as the main task interface:

make node-generate python-generate go-generate
make check
make build
make shared-check

The Makefile wraps each SDK's native commands. You can still run them directly
from each SDK directory when needed:

cd sdk-node && pnpm typecheck && pnpm test
cd sdk-python && uv sync --dev && uv run pytest && uv run ruff check . && uv run basedpyright
cd sdk-go && go test ./... && go test -run TestSharedCompatibilityFixtures ./...

Documentation

- docs/architecture.md gives the repository architecture and package layout
- docs/schema-generation.md documents schema/codegen flow
- docs/repo-model.md documents the monorepo task model
- RELEASE.md documents the release process

No reviews yet — be the first

Sign in to leave a review

Use Google, GitHub, or an email account so ratings stay tied to real people.

Email sign in

No reviews posted yet.