nerfmail

reference

API Reference

Complete HTTP API reference for all Nerfmail endpoints.

Base URL: https://api.nerfmail.com

All endpoints return JSON. Errors use the format { "error": { "code": "...", "message": "...", "details": {} } }.

Authentication

Admin Endpoints

Authorization: Bearer {ADMIN_TOKEN}

Mailbox Endpoints

Authorization: Bearer nrfm_{keyId}.{secret}

API keys are scoped to a single mailbox. The token format is nrfm_ prefix + key ID + . + base64url secret.


Account Management

Create Account

POST /v1/accounts

Creates an account with a primary mailbox.

Field Type Required Description
slug string Yes URL-safe identifier (lowercase, alphanumeric, hyphens)
displayName string Yes Human-readable account name

Headers: Idempotency-Key (recommended)

Response (201):

json
{
  "account": { "id", "slug", "display_name", "status", "created_at" },
  "primary_mailbox": { "id", "slug", "kind", "email_address", "protocol_host", "status", "created_at" }
}

Get Account

GET /v1/accounts/{accountId}

Response (200):

json
{ "account": { "id", "slug", "display_name", "status", "created_at" } }

List Mailboxes

GET /v1/accounts/{accountId}/mailboxes

Response (200):

json
{
  "account": { "id", "slug", "display_name", "status" },
  "mailboxes": [{ "id", "slug", "kind", "email_address", "protocol_host", "status", "created_at" }]
}

Create Mailbox

POST /v1/accounts/{accountId}/mailboxes
Field Type Required Description
slug string Yes Mailbox slug (cannot be main or primary)

Creates an agent mailbox with a default send_message action.

Response (201):

json
{ "mailbox": { "id", "slug", "kind", "email_address", "protocol_host", "status", "created_at" } }

Get Mailbox

GET /v1/mailboxes/{mailboxId}

Response (200):

json
{ "mailbox": { "id", "email_address", "protocol_host", "kind", "status", "created_at" } }

Disable / Enable Mailbox

POST /v1/mailboxes/{mailboxId}/disable
POST /v1/mailboxes/{mailboxId}/enable

Response (200):

json
{ "mailbox": { "id", "status": "disabled" } }

API Keys

Issue API Key

POST /v1/mailboxes/{mailboxId}/api-keys
Field Type Required Description
name string Yes Human-readable key name
scope string Yes Must be "mailbox"

Response (201):

json
{
  "api_key": { "id", "name", "scope", "status" },
  "token": "nrfm_keyId.base64urlSecret"
}

The token is shown once.


Revoke API Key

POST /v1/mailboxes/{mailboxId}/api-keys/{keyId}/revoke

Response (200):

json
{ "api_key": { "id", "name", "scope", "status": "revoked" } }

Messages

List Messages

GET /v1/mailboxes/{mailboxId}/messages
Parameter Type Default Description
page integer 1 Page number
limit integer 50 Items per page (1–100)
query string Search subject text
status string Filter by message status
direction string inbound or outbound
transport string smtp or agent-json
triage_label string Filter by triage label
risk_level string Filter by risk level

Response (200):

json
{
  "items": [{ "id", "thread_id", "transport", "direction", "from_address", "to_address", "subject", "snippet", "status", "risk_level", "triage_label", "created_at" }],
  "page": 1,
  "limit": 50,
  "total": 100,
  "total_pages": 2
}

Get Message

GET /v1/messages/{messageId}

Returns the full message including body, headers, attachments, and protocol metadata.

Response (200):

json
{
  "message": {
    "id", "mailbox_id", "thread_id", "transport", "direction",
    "from_address", "to_address", "subject", "snippet", "status",
    "risk_level", "triage_label", "message_id_header", "created_at",
    "text_body": "...",
    "html_body": "...",
    "headers": [{ "key": "From", "value": "..." }],
    "suspicious_signals": ["Unknown sender"],
    "attachments": [{ "id", "filename", "mime_type", "size" }]
  }
}

Send Email

POST /v1/mailboxes/{mailboxId}/send
Field Type Required Description
to string[] Yes Recipient addresses
cc string[] No CC addresses
bcc string[] No BCC addresses
subject string Yes Email subject
textBody string No Plain text body
htmlBody string No HTML body

At least one of textBody or htmlBody should be provided.

Response (201):

json
{
  "message": { "id", "status": "sent", "..." },
  "provider_message_id": "...",
  "reply_to": "acme+reply-token@nerfmail.com"
}

Reply to Message

POST /v1/messages/{messageId}/reply
Field Type Required Description
textBody string No Plain text body
htmlBody string No HTML body
cc string[] No CC addresses
bcc string[] No BCC addresses

Response (201): Same format as Send Email.


Archive / Review Message

POST /v1/messages/{messageId}/archive
POST /v1/messages/{messageId}/review

Response (200):

json
{ "message_id": "uuid", "status": "archived" }

Delivery Events

GET /v1/messages/{messageId}/delivery

Response (200):

json
{ "events": [{ "id", "status", "detail", "created_at" }] }

Raw Email Download

GET /v1/messages/{messageId}/raw

Returns the original .eml file with Content-Type: message/rfc822.


Attachment Download

GET /v1/messages/{messageId}/attachments/{attachmentId}

Returns the attachment with its original Content-Type.


Threads

List Threads

GET /v1/mailboxes/{mailboxId}/threads
Parameter Type Default Description
page integer 1 Page number
limit integer 50 Items per page (1–100)
query string Search thread subject
status string open or archived

Response (200):

json
{
  "items": [{ "id", "subject", "status", "last_message_at", "created_at" }],
  "page": 1,
  "limit": 50,
  "total": 10,
  "total_pages": 1
}

Drafts

Create Draft

POST /v1/mailboxes/{mailboxId}/drafts
Field Type Required Description
to string[] No Recipient addresses
cc string[] No CC addresses
bcc string[] No BCC addresses
subject string No Email subject
textBody string No Plain text body
htmlBody string No HTML body

Response (201):

json
{ "draft": { "id", "to", "cc", "bcc", "subject", "text_body", "html_body", "created_at" } }

List Drafts

GET /v1/mailboxes/{mailboxId}/drafts

Get / Update / Delete Draft

GET /v1/drafts/{draftId}
PUT /v1/drafts/{draftId}
DELETE /v1/drafts/{draftId}

Send Draft

POST /v1/drafts/{draftId}/send

Labels

Create Label

POST /v1/mailboxes/{mailboxId}/labels
Field Type Required Description
name string Yes Label name
color string No Display colour

List Labels

GET /v1/mailboxes/{mailboxId}/labels

Delete Label

DELETE /v1/labels/{labelId}

Apply / Remove Labels

POST /v1/messages/{messageId}/labels/{labelId}
DELETE /v1/messages/{messageId}/labels/{labelId}
POST /v1/threads/{threadId}/labels/{labelId}
DELETE /v1/threads/{threadId}/labels/{labelId}

Search & Intelligence

Search Messages

POST /v1/mailboxes/{mailboxId}/search
Field Type Required Description
query string Yes Search query
limit integer No Max results

Message Summary

GET /v1/messages/{messageId}/summary
POST /v1/messages/{messageId}/summary

GET returns the cached summary. POST generates one.


Data Extraction

GET /v1/messages/{messageId}/extractions
POST /v1/messages/{messageId}/extractions

Thread Digest

GET /v1/threads/{threadId}/digest
POST /v1/threads/{threadId}/digest

Sender Intelligence

GET /v1/sender-profiles/{profileId}/intelligence
POST /v1/sender-profiles/{profileId}/intelligence

Autopilot Policy

GET /v1/mailboxes/{mailboxId}/autopilot
PUT /v1/mailboxes/{mailboxId}/autopilot
Field Type Required Description
mode string No observe or enforce
rules array No Array of rule objects

Mailbox Policy

GET /v1/mailboxes/{mailboxId}/policy
PUT /v1/mailboxes/{mailboxId}/policy
Field Type Required Description
allow_outbound boolean No Allow outbound sending
allow_auto_reply boolean No Allow agent auto-replies
max_outbound_per_day integer No Send rate limit

Mailbox Stats & Metrics

GET /v1/mailboxes/{mailboxId}/stats
GET /v1/mailboxes/{mailboxId}/metrics

Suppressions

GET /v1/accounts/{accountId}/suppressions
POST /v1/accounts/{accountId}/suppressions
DELETE /v1/accounts/{accountId}/suppressions?address=...

Sender Profiles

POST /v1/accounts/{accountId}/sender-profiles
GET /v1/accounts/{accountId}/sender-profiles
Parameter Type Description
address string Search by address
disposition string Filter: neutral, trusted, blocked

Webhooks

POST /v1/mailboxes/{mailboxId}/webhooks
GET /v1/mailboxes/{mailboxId}/webhooks
GET /v1/webhooks/{endpointId}/deliveries
POST /v1/webhooks/{endpointId}/deliveries/{deliveryId}/replay

Actions

POST /v1/mailboxes/{mailboxId}/actions
GET /v1/mailboxes/{mailboxId}/actions
PUT /v1/mailboxes/{mailboxId}/actions/{actionName}
DELETE /v1/mailboxes/{mailboxId}/actions/{actionName}

Agent Protocol Endpoints

These endpoints use the mailbox's protocol_host (e.g., assistant.acme.mail.nerfmail.com).

GET  /.well-known/agent.json          — Discovery document (public)
POST /.agent/inbox                     — Send a protocol message
GET  /.agent/inbox/{messageId}         — Check message status
POST /.agent/inbox/{messageId}/respond — Respond to a message
GET  /.agent/inbox/{messageId}/responses — Response history

OpenClaw Integration

Manage OpenClaw agent bindings and view routing event logs. All endpoints require admin auth.

Binding CRUD

POST   /v1/mailboxes/{mailboxId}/openclaw-binding  — Create or update binding
GET    /v1/mailboxes/{mailboxId}/openclaw-binding  — Get binding for mailbox
DELETE /v1/mailboxes/{mailboxId}/openclaw-binding  — Remove binding

Admin Views

GET /v1/admin/openclaw-bindings                    — List all bindings
GET /v1/admin/openclaw-events                      — Recent event log (?limit=50&mailboxId=uuid)

Create/Update Binding Body

Field Type Default Description
agentId string required OpenClaw agent identifier
openclawBaseUrl string required Base URL for OpenClaw API
permissionTier string "draft" read-only, draft, send, admin-lite, admin
policyMode string "draft-first" draft-first, auto-send, observe
personaLabel string "default" Label used in agent prompts (e.g., "support", "ops")
enabled boolean true Whether the binding is active

Utility

Health Check

GET /health

Public, no authentication required.

OpenAPI Spec

GET /openapi.json

Returns a complete OpenAPI 3.1.0 specification. Public, no authentication required.


Idempotency

Most mutating endpoints support the Idempotency-Key header:

Scenario Result
New key Request processed normally
Same key, same request Cached response returned with x-idempotent-replay: true
Same key, different request 409 Conflict

Error Codes

HTTP Code Description
400 validation_error Request body failed validation
400 invalid_json Malformed JSON
400 schema_validation_failed Protocol parameters failed action schema
400 unknown_action Action doesn't exist or is disabled
400 reply_not_supported Can't reply to this message type
400 invalid_reply_token Reply token not found or wrong mailbox
401 missing_authorization No Authorization header
401 invalid_authorization Malformed token
401 invalid_token Token doesn't match
403 forbidden Valid token, wrong mailbox
403 outbound_disabled Mailbox policy disallows sending
404 not_found Resource not found
409 address_suppressed Recipient is on suppression list
409 idempotency_key_reused Same key, different request
410 expired_reply_token Reply token past 30-day expiry
429 rate_limit_exceeded Rate limit hit
500 internal_error Server error