Docs · SDKs
TypeScript SDK
A zero-dependency typed client for Node 20+.
npm install agentboxdThe TypeScript client is the npm package agentboxd. It has no dependencies: it uses the global fetch and node:crypto, and needs Node 20 or newer. It ships ES module and CommonJS builds with type declarations. With no options, the client reads AGENTBOXD_API_KEY and AGENTBOXD_BASE_URL (default https://api.agentboxd.com) from the environment.
import { Agentboxd } from 'agentboxd';
const mr = new Agentboxd(); // or new Agentboxd({ apiKey: 'mr_...', baseUrl: 'https://api.agentboxd.com' })#Methods
| Call | Endpoint |
|---|---|
mr.inboxes.create({ username?, display_name?, client_id? }) | POST /v1/inboxes |
mr.inboxes.list() · get(id) · delete(id) | /v1/inboxes |
mr.inboxes.createTemporary({ ttlSeconds }) | POST /v1/inboxes with ttl_seconds |
mr.inboxes.pause(id, { reason? }) · resume(id) | POST /v1/inboxes/:id/pause · /resume (the kill switch) |
mr.emergencyStop({ reason? }) | POST /v1/emergency-stop (emergency stop of the whole workspace) |
mr.escalation.get() · update({ contacts?, triggers?, … }) | GET · PUT /v1/escalation (human on call) |
mr.escalation.inbox.get(id) · mr.escalation.inbox.update(id, { override?, contacts?, triggers? }) | GET · PUT /v1/inboxes/:id/escalation |
mr.domains.create/list/get/verify/update/delete | /v1/domains |
mr.domains.rotateDkim(id) · activateDkim(id) | POST /v1/domains/:id/dkim/rotate · /activate |
mr.domains.zoneFile(id) | GET /v1/domains/:id/zone-file |
mr.messages.send(inboxId, { to, subject, text?, html?, attachments?, data?, type? }, { idempotencyKey? }) | POST …/messages/send (data/type: agent messaging; send<T> types data) |
mr.messages.reply(inboxId, messageId, { text?, html?, reply_all?, data?, type? }) | POST …/reply |
mr.messages.forward(inboxId, messageId, { to, cc?, bcc?, text?, include_attachments? }) | POST …/forward |
mr.messages.batchGet(ids) · batchUpdate(ids, { add_labels?, remove_labels?, is_read? }) | POST /v1/messages/batch-get · /batch-update |
mr.messages.list(inboxId, { labels?, is_read?, direction?, channel?, type? }) | GET …/messages |
mr.messages.wait(inboxId, { timeout?, since?, from?, subject?, channel?, type? }) | GET …/messages/wait |
mr.messages.waitForVerification(inboxId, { timeout?, since?, from?, include_unauthenticated? }) | GET …/verification |
mr.messages.get(id) · update(id, { add_labels?, remove_labels?, is_read? }) | /v1/messages/:id |
mr.messages.claim(inboxId, { limit?, lease_seconds?, consumer?, wait?, enriched?, analysis?, since? }) | POST …/messages/claim (claim/ack queue) |
mr.messages.ack(id, leaseId, { mark_read? }) · nack(id, leaseId, { delay_seconds? }) · extend(id, leaseId, { lease_seconds? }) | POST /v1/messages/:id/ack · /nack · /extend |
mr.inboxes.consume(inboxId, handler, { concurrency?, leaseSeconds?, signal?, onError? }) | The consumer loop: claims, extends the lease while handler(message, ctx) runs, acks when it returns, nacks with backoff when it throws. Runs until signal aborts |
mr.threads.list(inboxId, filters?) · listAll({ inbox_id?, labels?, before?, after?, participant?, subject?, ascending? }) · get(id) · delete(id) | /v1/threads · /v1/inboxes/:id/threads |
mr.search(q, { inbox_id? }) | GET /v1/search |
mr.inboxes.update(id, { display_name?, metadata? }) | PATCH /v1/inboxes/:id |
mr.threads.update(id, { metadata?, add_labels?, remove_labels? }) | PATCH /v1/threads/:id |
mr.contacts.list({ q?, label?, metadata? }) · get(id) · byAddress(address) | /v1/contacts |
mr.contacts.update(id, { name?, notes?, metadata?, add_labels?, remove_labels? }) | PATCH /v1/contacts/:id |
mr.contacts.delete(id) | DELETE /v1/contacts/:id |
mr.knowledge.list/create/get/update/delete | /v1/knowledge |
mr.knowledge.search(q, { inbox_id?, limit? }) | GET /v1/knowledge/search |
mr.messages.draftReply(id, { instructions?, save? }) | POST /v1/messages/:id/draft-reply |
mr.messages.attachmentText(messageId, attachmentId, { offset?, maxChars? }) | GET /v1/messages/:id/attachments/:attachmentId/text (Document extraction) |
mr.messages.extractAttachment(messageId, attachmentId, { schema, instructions? }) | POST /v1/messages/:id/attachments/:attachmentId/extract |
mr.drafts.create(inboxId, { to?, subject?, text?, reply_to_message_id?, send_at? }) · list · listAll (org-wide) · get · update · delete | /v1/inboxes/:id/drafts (Drafts) |
mr.drafts.send(inboxId, draftId) · schedule(inboxId, draftId, sendAt) · cancel(inboxId, draftId) | POST …/drafts/:draftId/send · /schedule · /cancel |
mr.webhooks.create/list/get/update/delete/test | /v1/webhooks |
mr.deliverability() | GET /v1/deliverability |
mr.stream({ inboxIds?, eventTypes?, payload?, since? }) | GET /v1/stream (WebSocket; async iterator, reconnects and resumes) |
mr.streamToken() | POST /v1/stream/token |
mr.identity.token({ inboxId | identityId, audience, nonce?, scope?, expiresIn? }) | POST /v1/inboxes/:id/identity-token (Sign in with Agentboxd) |
mr.identities.create/list/get/update/delete · pause(id) · resume(id) | /v1/identities (identities without a mailbox) |
mr.identity.clients.list/create/get/update/delete/rotateSecret | /v1/identity/clients |
mr.identity.inbox.get(inboxId) · update(inboxId, { enabled }) · signIns(inboxId) | /v1/inboxes/:id/identity |
verifyAgentIdentityToken(token, { audience, nonce?, replayCache? }) | For apps: verifies an agent’s ID token against the JWKS (from agentboxd/identity; needs jose) |
verifyAgentMessage(message, { recipient, replayCache?, maxAgeSeconds? }) | Verifies an agent message signature against agent-keys.json (from agentboxd/identity; needs jose, and canonicalize for messages with data) |
#Work queue
Delivery is at-least-once: a message whose handler crashed the process comes back after its lease, with a higher delivery_count. Make the handler idempotent. ctx.signal aborts if the lease is lost.
import { Agentboxd } from 'agentboxd';
const mr = new Agentboxd();
const stop = new AbortController();
process.on('SIGTERM', () => stop.abort()); // finish running work, then exit
await mr.inboxes.consume(
inboxId,
async (message, ctx) => {
await handleTicket(message, { signal: ctx.signal }); // acked when this returns, nacked if it throws
},
{ concurrency: 4, enriched: true, signal: stop.signal },
);#Contacts and knowledge
// Who is this? Name, notes, metadata and labels, created from mail.
const c = await mr.contacts.byAddress('priya.n@gmail.com');
console.log(c.notes, c.metadata.crm_id); // "Wholesale…" "hs_48213"
// Link it to your CRM and remember something. Metadata merges; null deletes a key.
await mr.contacts.update(c.id, {
notes: `${c.notes ?? ''}
Asked about order 1042 on 25 Sep.`,
metadata: { crm_id: 'hs_48213', plan: 'wholesale' },
add_labels: ['customer'],
});
// Find contacts by your own id.
const { data } = await mr.contacts.list({ metadata: { crm_id: 'hs_48213' } });// Workspace-wide (no inbox_id) or for one inbox.
await mr.knowledge.create({
title: 'Shipping times',
body: '- Germany: 1–2 working days (DHL)
- EU: 3–5 working days',
inbox_id: inbox.id,
});
// Ranked full-text search. With inbox_id, workspace-wide documents are included too.
const { data: hits } = await mr.knowledge.search('how long does shipping to France take', { inbox_id: inbox.id });
// [{ id, title: 'Shipping times', inbox_id, rank: 0.61, snippet: '…EU: 3–5 working days…' }]#Errors
Non-2xx responses throw AgentboxdError (also exported as MailroomError) with status, code and message from the API’s error body.
import { AgentboxdError } from 'agentboxd';
try {
await mr.messages.send(inbox.id, { to: 'x@example.com', subject: 'Hi', text: '…' });
} catch (e) {
if (e instanceof AgentboxdError && e.code === 'daily_send_limit_exceeded') {
// back off until 00:00 UTC
}
}#Webhook verification
verifyWebhook(signature, timestamp, rawBody, secret, { toleranceSeconds? }) returns a boolean. See Webhooks for a full Express example.