REST API

The whole surface over plain HTTPS, with the same API keys.

Everything the MCP server does is also available over plain HTTPS, for stacks without MCP support. The base is https://mcpmailer.com and every endpoint takes an Authorization: Bearer mmk_... header. Bodies and responses are JSON unless noted.

Messages

POST /v1/messages

Send. Same body as send_email.

GET /v1/messages

?unread_only=true&thread_id=&include_archived=&limit=

GET /v1/messages/:id

Full message, and marks it read.

POST /v1/messages/:id/reply-all

{ body }

POST /v1/messages/:id/forward

{ to, body?, mode? }

POST /v1/messages/:id/archive

{ archived?, unread? }

GET /v1/messages/:id/attachments/:filename

One attachment.

GET /v1/threads

Conversations, newest first.

GET /v1/threads/:id

Every message in one conversation.

Identity

GET /v1/identity

Handle, filter mode, rules.

PATCH /v1/identity

{ handle?, display_name?, description?, filter_mode? }

GET /v1/identity/rules

Filter rules.

POST /v1/identity/rules

{ match, value, action }

DELETE /v1/identity/rules/:id

Removes a rule.

Contacts

GET /v1/contacts

?q= search, ?lookup= reverse lookup. Accept: text/vcard exports all.

POST /v1/contacts

One contact, or { vcard } to bulk import.

GET /v1/contacts/duplicates

Pairs that look like the same person.

POST /v1/contacts/duplicates

{ survivor_id, loser_id } merges them.

GET /v1/contacts/:id

Full contact. Accept: text/vcard returns a vCard.

PATCH /v1/contacts/:id

Merge patch.

POST /v1/contacts/:id

{ fact } remembers something about them.

DELETE /v1/contacts/:id

Deletes the contact.

POST /v1/contacts/import

{ csv, list_name? | list_id? } pasted CSV or address lines become contacts, optionally all on one list. Or send the text as text/csv with ?list_name=.

GET /v1/contacts/lists

Every list with its member count.

POST /v1/contacts/lists

{ name, description? } creates a list.

GET /v1/contacts/lists/:id

The list and a page of members. ?limit= ?offset=, page until next_offset is null.

PATCH /v1/contacts/lists/:id

{ name?, description? }

DELETE /v1/contacts/lists/:id

Deletes the list, keeps the contacts.

POST /v1/contacts/lists/:id/members

{ contact_ids } adds contacts to the list.

DELETE /v1/contacts/lists/:id/members

{ contact_ids } removes them from the list.

Notes, knowledge, secrets, and signup

GET /v1/notes

?q= to search.

POST /v1/notes

{ title?, body }

GET /v1/notes/:id

PATCH merge patches it, DELETE removes it.

GET /v1/knowledge

The summary; ?q= returns ranked passages instead.

GET /v1/knowledge/pages/:id

One indexed page in full.

GET /v1/knowledge/sources

Every source with its status.

POST /v1/knowledge/sources

{ kind: site | page, url } or { kind: text, body }. Sites and pages index in the background.

GET /v1/knowledge/sources/:id

POST fetches it again, DELETE removes it and its pages.

GET /v1/secrets

Metadata for granted secrets. No values.

GET /v1/secrets/:id

Opens a granted secret and returns its value. No key to pass.

GET /v1/mailboxes

Every inbox here, with the plan allowance.

POST /v1/mailboxes

{ handle, domain_id? } creates one.

GET /v1/domains

Domains, with records a pending one still needs.

POST /v1/domains

{ domain } returns the DNS records to publish.

POST /v1/domains/{id}/verify

Checks the records now and reports whether the domain can send yet.

DELETE /v1/domains?id=

Removes a custom domain.

GET /v1/webhooks

Endpoints. Secrets are not listed.

POST /v1/webhooks

{ url, events, mailbox_id?, headers?, template? } returns the secret once.

PATCH /v1/webhooks?id=

Changes an endpoint in place. rotate_secret returns a new secret once.

DELETE /v1/webhooks?id=

Removes an endpoint.

GET /v1/webhooks/deliveries

What each attempt got back. webhook_id, limit, and before narrow it.

POST /v1/webhooks/deliveries

{ webhook_id } sends a test; { delivery_id } replays a stored one.

POST /v1/signup

{ handle, email? } provisions a workspace and a key.