BOX OXDocs

Build

Build with the Box Ox API

Send application mail, manage templates, and verify events from your own installation.

On this page

openapi.json is the OpenAPI 3.1 spec for /dev/v1/*. A running install also serves it at GET /dev/v1/openapi.json. This endpoint needs no key.

The inbox install on Cloudflare Workers Free supports incoming-mail access and webhooks. Outbound requests return feature_unavailable.

Direct SDK and CLI downloads#

Box Ox self-publishes packages at https://boxox.dev/downloads/#developer-tools. Use the downloaded packages directly. Download boxox-mail-0.2.0.tgz, verify it against the published SHA256SUMS.txt, then run npm install ./boxox-mail-0.2.0.tgz (or the equivalent in your package manager).

The installed package name is @boxox/mail. The imports in this guide stay the same. The CLI package boxox-cli-0.2.0.tgz bundles the SDK and WebSocket client. Install the local file or run the standalone boxox-cli-0.2.0.mjs with Node.js 22+.

Successful API sends appear in Sent and Developer logs. A received reply brings the conversation into Inbox. Mailbox copies have separate storage. Deleting one does not remove the API log.

A copy binds to an existing matching sender mailbox and follows its read grants. Without that mailbox, only the installation owner can read the copy.

Keys#

Send Authorization: Bearer <key>.

  • A domain key (bxk_…) acts on exactly one domain, within its permissions: send, read_log, read_received, templates, webhooks.
  • A management key (bxm_…) can only use /dev/v1/keys. It mints, lists, rotates and revokes domain keys on its own domains.

Errors and pagination#

Every error has the form {"error": {"code", "message", "param"?}}. A rejected batch also returns errors: [{index, code, message, param?}].

Lists return {data, nextCursor}. To get the next page, pass nextCursor back as ?cursor=. nextCursor is null on the last page.

SDK#

ts
import { BoxOx } from "@boxox/mail";

const mail = new BoxOx({ key: process.env.BOXOX_KEY, url: process.env.BOXOX_URL });

const { id } = await mail.messages.send(
  { from: "noreply@app1.com", to: "user@example.com", template: "welcome", variables: { name: "Ada" } },
  { idempotencyKey: "signup-123" },
);

for await (const m of mail.paginate(mail.messages.list, { status: "bounced" })) console.log(m.id);

mail.request(operationId, {path, query, body}) calls any operation in the spec by its operationId.

Webhooks#

Box Ox signs deliveries in the Standard Webhooks format. Each delivery carries three headers: webhook-id, webhook-timestamp and webhook-signature. The signature is v1,<base64 HMAC-SHA256 of "id.timestamp.body">. During a secret rotation the header holds two space-separated signatures, one per secret.

To verify a delivery, pass the raw body, before any JSON parsing:

ts
import { verifyWebhookRequest, WebhookVerificationError } from "@boxox/mail/webhooks";

export default {
  async fetch(request: Request, env: { BOXOX_WEBHOOK_SECRET: string }) {
    try {
      const event = await verifyWebhookRequest(request, env.BOXOX_WEBHOOK_SECRET);
      if (event.type === "message.received") {
        // event.data is a ReceivedEmail
      }
      return new Response(null, { status: 204 });
    } catch (err) {
      if (err instanceof WebhookVerificationError) return new Response("bad signature", { status: 401 });
      throw err;
    }
  },
};

By default, the verifier rejects a timestamp more than 5 minutes from now. Change the limit with toleranceSeconds.

Several deliveries can carry the same webhook-id, for example a replay. Use it to deduplicate.

Template design#

Template agents must complete the email design review before publishing. The current email skill includes the review and an HTML/plain-text invoice example.

For contributors: maintain the API contract

Maintain the API contract#

  1. The routes in apps/worker/src/dev/*-routes.ts validate with Zod.
  2. apps/worker/src/dev/openapi.ts builds the spec from those same schemas, plus the response shapes.
  3. pnpm --filter @boxox/mail generate writes docs/api/openapi.json and the SDK types in packages/sdk/src/schema.ts.
  4. Run pnpm --filter @boxox/mail run check-openapi to check freshness, route coverage, and SDK types.

The check fails if either generated file is out of date. It also fails for a missing mounted /dev/v1 route or incompatible SDK types.

Do not edit openapi.json or schema.ts by hand. Change the route or openapi.ts, then run generate.

API endpoints#

Expand an operation for parameters and response codes. These 37 operations come from the same OpenAPI contract as the SDK.

messages

POST /dev/v1/messagesSend or schedule one message (needs `send`)
Request parameters
NameLocationRequiredDescription
Idempotency-KeyheaderOptionalReplays within 24 h return the original response; a different body gets 409.

Response codes: 202, 400, 401, 403, 404, 409, 422, 429.

Use operation sendMessage with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

GET /dev/v1/messagesMessage log, newest first (needs `read_log`)
Request parameters
NameLocationRequiredDescription
statusqueryOptional
tagqueryOptional
recipientqueryOptional
cursorqueryOptional
limitqueryOptional

Response codes: 200, 400, 401, 403, 404.

Use operation listMessages with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

POST /dev/v1/messages/batchSend up to 100 messages, all-or-nothing or partial (needs `send`)
Request parameters
NameLocationRequiredDescription
Idempotency-KeyheaderOptionalReplays within 24 h return the original response; a different body gets 409.

Response codes: 202, 400, 401, 403, 404, 409, 422, 429.

Use operation sendBatch with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

GET /dev/v1/messages/{id}One message with its status timeline and rendered body (needs `read_log`)
Request parameters
NameLocationRequiredDescription
idpathRequired

Response codes: 200, 400, 401, 403, 404.

Use operation getMessage with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

PATCH /dev/v1/messages/{id}Move a scheduled message's send time (needs `send`)
Request parameters
NameLocationRequiredDescription
idpathRequired

Response codes: 200, 400, 401, 403, 404, 409.

Use operation rescheduleMessage with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

POST /dev/v1/messages/{id}/cancelCancel a scheduled message (needs `send`)
Request parameters
NameLocationRequiredDescription
idpathRequired

Response codes: 200, 400, 401, 403, 404, 409.

Use operation cancelMessage with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

keys

GET /dev/v1/keysDomain keys on the management key's domains

Response codes: 200, 400, 401, 403, 404.

Use operation listKeys with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

POST /dev/v1/keysCreate a domain key (management keys only)

Response codes: 201, 400, 401, 403, 404.

Use operation createKey with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

POST /dev/v1/keys/{id}/rotateIssue a new secret; the old one works for the overlap window
Request parameters
NameLocationRequiredDescription
idpathRequired

Response codes: 200, 400, 401, 403, 404.

Use operation rotateKey with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

POST /dev/v1/keys/{id}/revokeRevoke a domain key at once
Request parameters
NameLocationRequiredDescription
idpathRequired

Response codes: 200, 400, 401, 403, 404.

Use operation revokeKey with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

templates

GET /dev/v1/templatesLatest version of each template (needs `templates`)
Request parameters
NameLocationRequiredDescription
limitqueryOptional
cursorqueryOptional

Response codes: 200, 400, 401, 403, 404.

Use operation listTemplates with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

POST /dev/v1/templatesCreate a template at version 1

Response codes: 201, 400, 401, 403, 404, 409.

Use operation createTemplate with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

GET /dev/v1/templates/{name}The latest version, or ?version=
Request parameters
NameLocationRequiredDescription
namepathRequired
versionqueryOptional

Response codes: 200, 400, 401, 403, 404.

Use operation getTemplate with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

PUT /dev/v1/templates/{name}Save a new version
Request parameters
NameLocationRequiredDescription
namepathRequired

Response codes: 200, 400, 401, 403, 404.

Use operation updateTemplate with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

DELETE /dev/v1/templates/{name}Delete every version
Request parameters
NameLocationRequiredDescription
namepathRequired

Response codes: 200, 400, 401, 403, 404.

Use operation deleteTemplate with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

GET /dev/v1/templates/{name}/versionsEvery version, newest first
Request parameters
NameLocationRequiredDescription
namepathRequired

Response codes: 200, 400, 401, 403, 404.

Use operation listTemplateVersions with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

POST /dev/v1/templates/{name}/previewRender with sample variables
Request parameters
NameLocationRequiredDescription
namepathRequired

Response codes: 200, 400, 401, 403, 404.

Use operation previewTemplate with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

webhooks

POST /dev/v1/webhooksAdd an HTTPS endpoint (needs `webhooks`)

Response codes: 201, 400, 401, 403, 404, 422.

Use operation createWebhook with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

GET /dev/v1/webhooksEndpoints on the key's domain

Response codes: 200, 400, 401, 403, 404.

Use operation listWebhooks with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

GET /dev/v1/webhooks/listenLive events on the key's domain over a WebSocket, for local development

Response codes: 101, 400, 401, 403, 404, 426.

Use operation listenWebhooks with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

GET /dev/v1/webhooks/{id}One endpoint
Request parameters
NameLocationRequiredDescription
idpathRequired

Response codes: 200, 400, 401, 403, 404.

Use operation getWebhook with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

PATCH /dev/v1/webhooks/{id}Change the URL or events, or enable/disable
Request parameters
NameLocationRequiredDescription
idpathRequired

Response codes: 200, 400, 401, 403, 404, 422.

Use operation updateWebhook with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

DELETE /dev/v1/webhooks/{id}Delete an endpoint
Request parameters
NameLocationRequiredDescription
idpathRequired

Response codes: 200, 400, 401, 403, 404.

Use operation deleteWebhook with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

POST /dev/v1/webhooks/{id}/rotateNew signing secret; both sign during the overlap
Request parameters
NameLocationRequiredDescription
idpathRequired

Response codes: 200, 400, 401, 403, 404.

Use operation rotateWebhookSecret with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

POST /dev/v1/webhooks/{id}/testSend a webhook.test event
Request parameters
NameLocationRequiredDescription
idpathRequired

Response codes: 202, 400, 401, 403, 404, 409.

Use operation testWebhook with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

GET /dev/v1/webhooks/{id}/deliveriesDelivery log, newest first
Request parameters
NameLocationRequiredDescription
idpathRequired
statusqueryOptional
cursorqueryOptional
limitqueryOptional

Response codes: 200, 400, 401, 403, 404.

Use operation listWebhookDeliveries with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

GET /dev/v1/webhooks/{id}/deliveries/{deliveryId}One delivery with its attempt log and payload
Request parameters
NameLocationRequiredDescription
idpathRequired
deliveryIdpathRequired

Response codes: 200, 400, 401, 403, 404.

Use operation getWebhookDelivery with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

POST /dev/v1/webhooks/{id}/deliveries/{deliveryId}/replaySend a finished delivery again
Request parameters
NameLocationRequiredDescription
idpathRequired
deliveryIdpathRequired

Response codes: 202, 400, 401, 403, 404, 409.

Use operation replayWebhookDelivery with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

received

GET /dev/v1/receivedMail received on the domain's app inboxes (needs `read_received`)
Request parameters
NameLocationRequiredDescription
toqueryOptional
cursorqueryOptional
limitqueryOptional

Response codes: 200, 400, 401, 403, 404.

Use operation listReceived with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

GET /dev/v1/received/{id}Parsed fields, authentication, bodies and attachments
Request parameters
NameLocationRequiredDescription
idpathRequired

Response codes: 200, 400, 401, 403, 404.

Use operation getReceived with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

GET /dev/v1/received/{id}/rawA signed, expiring link to the original .eml
Request parameters
NameLocationRequiredDescription
idpathRequired
expiresInqueryOptional

Response codes: 200, 400, 401, 403, 404, 409.

Use operation getReceivedRawUrl with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

GET /dev/v1/received/{id}/attachments/{attachmentId}Download one attachment
Request parameters
NameLocationRequiredDescription
idpathRequired
attachmentIdpathRequired

Response codes: 200, 400, 401, 403, 404.

Use operation getReceivedAttachment with the SDK’s mail.download(). Full request and response schemas are in the downloadable OpenAPI contract.

GET /dev/v1/received/raw/{token}The signed .eml link itself; no API key, the token is the credential
Request parameters
NameLocationRequiredDescription
tokenpathRequired

Response codes: 200, 404.

Use operation downloadReceivedRaw with the SDK’s mail.download(). Full request and response schemas are in the downloadable OpenAPI contract.

suppressions

GET /dev/v1/suppressionsThe domain's suppression list (needs `read_log`)
Request parameters
NameLocationRequiredDescription
reasonqueryOptional
cursorqueryOptional
limitqueryOptional

Response codes: 200, 400, 401, 403, 404.

Use operation listSuppressions with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

POST /dev/v1/suppressionsSuppress an address (needs `send`)

Response codes: 201, 400, 401, 403, 404.

Use operation addSuppression with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

GET /dev/v1/suppressions/{email}Whether one address is suppressed
Request parameters
NameLocationRequiredDescription
emailpathRequired

Response codes: 200, 400, 401, 403, 404.

Use operation getSuppression with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

DELETE /dev/v1/suppressions/{email}Remove an address from our list (Cloudflare's still applies)
Request parameters
NameLocationRequiredDescription
emailpathRequired

Response codes: 200, 400, 401, 403, 404.

Use operation removeSuppression with the SDK’s mail.request(). Full request and response schemas are in the downloadable OpenAPI contract.

Download the API contract#

Use the machine-readable specification to generate a client or import the Postman collection for your own installation.

Download OpenAPI JSON Download Postman JSON