← Home

API reference

Base URL https://api.inboxar.com. JSON in, JSON out. All object ids are UUIDs. Invite-only beta: endpoints may still change; breaking changes will be announced to tenants.

Authentication

Two credentials work against /v1/*:

curl https://api.inboxar.com/v1/me -H "Authorization: Bearer ibx_XXXXXXXX_…"
{ "tenant": { "id": "…", "slug": "acme", "name": "Acme", "status": "active",
             "daily_send_limit": 5000, "mailbox_daily_send_limit": 500 },
  "actor": { "kind": "api_key", "api_key_id": "…" },
  "mail_host": "mx1.inboxar.com" }

Domains

MethodPathWhat
GET/v1/domainsList domains
POST/v1/domainsAdd a domain: {"name":"acme.com"} → 201 with DNS records
GET/v1/domains/:idDomain with its DNS records
POST/v1/domains/:id/verifyCheck MX, DKIM, SPF, DMARC over DNS; sets status to verified or pending
DELETE/v1/domains/:idRemove the domain and its mailboxes

Records to publish (returned by POST/GET): MX 10 mx1.inboxar.com, two DKIM TXTs (Ed25519 + RSA, rotating selectors), TXT v=spf1 mx -all (add your other senders if any), _dmarc TXT v=DMARC1; p=none; rua=mailto:dmarc@inboxar.com, plus autoconfig/autodiscover CNAMEs for mail clients. Aggregate DMARC reports land in /v1/dmarc.

Mailboxes

MethodPathWhat
GET/v1/mailboxesList
POST/v1/mailboxes{"domain_id","local_part","kind":"user"|"group","password"?}. Users get a generated password returned once when none is given; groups are shared mailboxes without a login.
GET/v1/mailboxes/:idOne mailbox
PATCH/v1/mailboxes/:id{"is_admin":true} — let this user manage the tenant from the web client
POST/v1/mailboxes/:id/passwordSet ({"password"}) or generate a new password
DELETE/v1/mailboxes/:idDelete the mailbox and its mail
GET/v1/mailboxes/:id/membersMembers of a group
POST/v1/mailboxes/:id/members{"mailbox_id"} — add a user to a shared mailbox; it appears as a second account in their client
DELETE/v1/mailboxes/:id/members/:mailbox_idRemove a member

Sending — POST /v1/messages

Sends from one of the tenant's mailboxes. The message is submitted through the mailbox itself (it shows up in its Sent folder), signed with your domain's DKIM, and queued. Delivery outcome arrives as message.delivered / message.bounced / message.deferred events.

curl https://api.inboxar.com/v1/messages \
  -H "Authorization: Bearer ibx_…" -H "Content-Type: application/json" \
  -d '{
    "from": {"email": "hello@acme.com", "name": "Acme"},
    "to": ["ann@example.org", {"email": "bob@example.org", "name": "Bob"}],
    "cc": [], "bcc": [], "reply_to": ["support@acme.com"],
    "subject": "Your account is ready",
    "text": "Welcome aboard.",
    "html": "<p>Welcome aboard.</p>",
    "in_reply_to": "<prev-message-id@example.org>",
    "headers": {"X-Campaign": "welcome"}
  }'
202 { "id": "…", "status": "queued", "message_id": "<…@acme.com>", "from": "hello@acme.com", "to": ["ann@example.org","bob@example.org"], "created_at": "…" }

Webhooks

MethodPathWhat
GET/v1/webhooksList
POST/v1/webhooks{"url":"https://…","events":["message.received","message.bounced"]} → 201 with secret (shown once). https only.
DELETE/v1/webhooks/:idDelete
GET/v1/webhooks/:id/deliveriesRecent delivery attempts and statuses

Each delivery is a POST with headers X-Inboxar-Event, X-Inboxar-Delivery and X-Inboxar-Signature: sha256=<hex> — HMAC-SHA256 of the raw body with the webhook secret. Respond 2xx within 10 s. Retries: 1 m, 5 m, 30 m, 2 h, 12 h, 24 h; then the delivery is marked failed. Deliveries are at-least-once — dedupe on id.

// Node: verify the signature
import { createHmac, timingSafeEqual } from "node:crypto";
const expected = "sha256=" + createHmac("sha256", process.env.WEBHOOK_SECRET).update(rawBody).digest("hex");
const ok = timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers["x-inboxar-signature"] ?? ""));

Payload

{
  "id": "…",                        // event id (UUID)
  "type": "message.received",
  "created_at": "2026-09-17T10:00:00.000Z",
  "data": {
    "address": "support@acme.com",  // mailbox the event is about
    "domain": "acme.com",
    "occurred_at": "…",
    "spam": false,                  // message.received only
    "message": {                    // message.received only — the parsed email
      "id": "…", "blob_id": "…", "thread_id": "…",
      "message_id": ["<…>"], "in_reply_to": [], "references": [],
      "received_at": "…", "sent_at": "…",
      "from": [{"email":"ann@example.org","name":"Ann"}], "to": [...], "cc": [...], "reply_to": [...],
      "subject": "Order #1", "text": "…", "html": "…",
      "text_truncated": false, "html_truncated": false,   // bodies capped at 512 KB
      "has_attachment": true,
      "attachments": [{"name":"inv.pdf","type":"application/pdf","size":100,"blob_id":"…","cid":null}],
      "headers": [{"name":"List-Unsubscribe","value":"…"}]
    },
    "stalwart": { … }               // raw server event, for debugging
  }
}

Attachment contents are fetched through JMAP with the mailbox's own credentials (Mail protocols) using blob_id.

Events

TypeMeaning
message.receivedDelivered into one of your mailboxes (inbox or spam — see data.spam)
message.deliveredOutbound message accepted by the remote server
message.bouncedPermanent failure
message.deferredTemporary failure, will retry
message.rejectedRejected at SMTP time (unknown recipient, policy)
auth.failedFailed login to one of your mailboxes

GET /v1/events?type=…&limit=… lists recent events regardless of webhooks.

DMARC

Every domain's DMARC record points aggregate reports at us; we parse them for you.

MethodPathWhat
GET/v1/dmarc?days=30&domain=Per-domain totals (messages, pass, fail) and top sending sources by IP
GET/v1/dmarc/reports?days=&domain=Individual reports (reporter, period, published policy)
GET/v1/dmarc/reports/:idOne report with its per-source records and auth results

API keys

MethodPathWhat
GET/v1/api-keysList (prefix, label, last used)
POST/v1/api-keys{"label"} → 201 with token (shown once). Max 20 active.
DELETE/v1/api-keys/:idRevoke

Mail protocols

ProtocolHostPortNotes
IMAPmx1.inboxar.com993SSL/TLS, login = full address
SMTP submissionmx1.inboxar.com587STARTTLS, same login
JMAPhttps://mx1.inboxar.com/.well-known/jmap443Basic or OAuth bearer; full RFC 8620/8621
Autoconfigautoconfig.<your-domain>443Thunderbird/Apple Mail/Outlook pick settings up automatically once the CNAMEs are set

Web client: app.inboxar.com — sign in with the mailbox address and password.

Errors & limits

Errors are {"error": "code", "detail": "…"} with a matching HTTP status: 400 validation, 401 bad credentials, 403 not allowed (suspended tenant, foreign mailbox), 404, 409 conflict (name taken, already member), 413 body too large, 429 daily_limit_reached, 502 mail server refused, 503 not_configured. Every response carries X-Request-Id — include it when writing to hello@inboxar.com.