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/*:
- API key —
Authorization: Bearer ibx_…. Created by an operator at onboarding or by a tenant admin (API keys). Keys are shown once and stored hashed. - Mailbox login (OAuth) — the access token a user gets from the mail server's OAuth flow (used by the web client). Accepted only for mailboxes flagged
is_admin. Discovery:GET /auth/config(public).
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
| Method | Path | What |
|---|---|---|
| GET | /v1/domains | List domains |
| POST | /v1/domains | Add a domain: {"name":"acme.com"} → 201 with DNS records |
| GET | /v1/domains/:id | Domain with its DNS records |
| POST | /v1/domains/:id/verify | Check MX, DKIM, SPF, DMARC over DNS; sets status to verified or pending |
| DELETE | /v1/domains/:id | Remove 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
| Method | Path | What |
|---|---|---|
| GET | /v1/mailboxes | List |
| 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/:id | One mailbox |
| PATCH | /v1/mailboxes/:id | {"is_admin":true} — let this user manage the tenant from the web client |
| POST | /v1/mailboxes/:id/password | Set ({"password"}) or generate a new password |
| DELETE | /v1/mailboxes/:id | Delete the mailbox and its mail |
| GET | /v1/mailboxes/:id/members | Members 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_id | Remove 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": "…" }
frommust be an active user mailbox of the tenant (groups cannot send). Up to 50 recipients across to/cc/bcc.textand/orhtmlrequired. OnlyX-*custom headers.- Limits: per tenant and per mailbox per rolling 24 h (see
/v1/me). Over the limit →429 daily_limit_reached. GET /v1/messages,GET /v1/messages/:id— the send log (no bodies).
Webhooks
| Method | Path | What |
|---|---|---|
| GET | /v1/webhooks | List |
| POST | /v1/webhooks | {"url":"https://…","events":["message.received","message.bounced"]} → 201 with secret (shown once). https only. |
| DELETE | /v1/webhooks/:id | Delete |
| GET | /v1/webhooks/:id/deliveries | Recent 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", // recipient address the event is about (may be an alias or a catch-all address)
"mailbox": "support@acme.com", // message.received only — the mailbox it was delivered to
"via": "mailbox", // message.received only — mailbox | alias | catch_all
"folder": "acme-site.com", // message.received only — shared mailbox: the per-domain folder the message was filed into (alias/catch-all mail)
"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
| Type | Meaning |
|---|---|
| message.received | Delivered into one of your mailboxes (inbox or spam — see data.spam) |
| message.delivered | Outbound message accepted by the remote server |
| message.bounced | Permanent failure |
| message.deferred | Temporary failure, will retry |
| message.rejected | Rejected at SMTP time (unknown recipient, policy) |
| auth.failed | Failed 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.
| Method | Path | What |
|---|---|---|
| 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/:id | One report with its per-source records and auth results |
API keys
| Method | Path | What |
|---|---|---|
| GET | /v1/api-keys | List (prefix, label, last used) |
| POST | /v1/api-keys | {"label"} → 201 with token (shown once). Max 20 active. |
| DELETE | /v1/api-keys/:id | Revoke |
Mail protocols
| Protocol | Host | Port | Notes |
|---|---|---|---|
| IMAP | mx1.inboxar.com | 993 | SSL/TLS, login = full address |
| SMTP submission | mx1.inboxar.com | 587 | STARTTLS, same login |
| JMAP | https://mx1.inboxar.com/.well-known/jmap | 443 | Basic or OAuth bearer; full RFC 8620/8621 |
| Autoconfig | autoconfig.<your-domain> | 443 | Thunderbird/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. Under Account & security each person can turn on two-factor authentication (TOTP) and create app passwords for mail apps; with two-factor on, IMAP/SMTP/JMAP accept only app passwords.
Web app, push notifications, diagnostics
app.inboxar.com is an installable web app (PWA): on iPhone use Share → Add to Home Screen, on Android/desktop Chrome use the install prompt in the address bar. Push notifications for new mail are per device and per mailbox; turn them on under Notifications & diagnostics in the sidebar (iPhone: only from the installed app). Shared mailboxes notify every member; spam never pushes.
| Method | Path | What |
|---|---|---|
| GET | /public/push/config | Public VAPID key (applicationServerKey) |
| POST | /v1/team/push/subscribe | Mailbox token: {"subscription": PushSubscription.toJSON(), "ua"} → device id. One browser endpoint belongs to one mailbox. |
| GET | /v1/team/push/devices | Devices of the signed-in mailbox (last push, last error) |
| POST | /v1/team/push/test | Test push to every device of the caller — returns the push service's verbatim answer per device |
| DELETE | /v1/team/push/devices/:id | Unsubscribe a device |
| POST | /public/diag/log | Diagnostic log from the app (what happened and how fast, no message content). Sent by the person from the Diagnostics screen or automatically after an error; answers with a short id to quote to support. Kept 30 days. |
Workspaces (owner sign-in)
A workspace is created at app.inboxar.com/signup with any email — a one-time link confirms it, no password. One email can own several workspaces; switch between them in the Admin sidebar or create another one there without a new confirmation. New workspaces are approved by hand during the beta.
| Method | Path | What |
|---|---|---|
| POST | /public/signup | {"name", "email"} → confirmation link by email |
| POST | /public/login | {"email"} → sign-in link (lands in the last used workspace) |
| POST | /public/verify | {"token"} → owner session own_… (30 days), usable as Bearer on /v1/* with admin rights |
| GET | /public/workspaces | Owner session: all workspaces of this email, current one marked |
| POST | /public/workspaces | {"name"} → another workspace for the same email (up to 3 awaiting approval, 10 total) |
| POST | /public/switch | {"tenant_id"} → new session for that workspace; the old one is revoked |
| POST | /public/logout | Revoke the session |
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.