Skip to content
AI Inbx
Esc
navigateopen⌘Jpreview
On this page

TypeScript

Install, configure, and use the aiinbx package — resources, pagination, response metadata, typed errors, React Email, and webhook verification.

The official TypeScript client. No dependencies — it uses fetch and Web Crypto, so it runs on Node.js 20+, Bun, Deno, Cloudflare Workers, and Vercel Edge without a polyfill.

npm install aiinbx
pnpm add aiinbx
yarn add aiinbx
bun add aiinbx

Getting started

import AIInbx from "aiinbx"

const aiinbx = new AIInbx() // reads AI_INBX_API_KEY

const email = await aiinbx.emails.send(
  {
    from: { name: "Ada", address: "ada@example.com" },
    to: "grace@example.com",
    subject: "Hello",
    text: "Sent through AI Inbx.",
  },
  { idempotencyKey: "welcome-grace-v1" }
)

Configuration

const aiinbx = new AIInbx({
  apiKey: process.env.AI_INBX_API_KEY!,
  timeout: 30_000,
  maxRetries: 2,
  // baseURL: "http://localhost:3000/api/v2",
})
PropType
apiKey?string

Defaults to AI_INBX_API_KEY where the runtime exposes environment variables.

Typestring
baseURL?string

Point at a local or self-hosted API.

Typestring
Default"https://api.aiinbx.com/api/v2"
timeout?number

Per-request timeout in milliseconds.

Typenumber
Default60000
maxRetries?number

Retries for network errors and 408, 409, 429, 5xx.

Typenumber
Default2
fetch?typeof fetch

Supply your own fetch — for instrumentation or a proxy agent.

Typetypeof fetch
defaultHeaders?HeadersInit

Headers added to every request.

TypeHeadersInit

Every request can override the client defaults:

await aiinbx.emails.send(payload, {
  timeout: 10_000,
  maxRetries: 0,
  idempotencyKey: "order-8812-receipt",
  signal: controller.signal,
  headers: { "X-Request-ID": traceId },
})

Resources

  • apiKeyslist, create, delete
  • emailssend, list, retrieve, reschedule, cancel
  • threadslist, retrieve, iterateMessages, reply, forward
  • domainslist, create, retrieve, update, diagnostics, delete, verify
  • mailboxeslist, retrieve, connect, sync, disconnect
  • oauthAppslist, create, retrieve, update, delete
  • webhookEndpointslist, create, retrieve, update, delete, rotateSecret, test, listDeliveries, retryDeliveries
  • suppressionslist, add, remove
  • pacingRuleslist, create, retrieve, update, delete, spread
  • pacingretrieve, release
  • attachmentsdownload, content

Replying

No headers to reconstruct — the sender, recipients, subject, and reply headers come from the thread:

await aiinbx.threads.reply(
  "thr_123",
  { text: "Sounds good — see you then." },
  { idempotencyKey: "reply-thr-123-v1" }
)

React Email

Pass a component as react and it’s rendered to HTML before the send, the same way Resend’s Node SDK does it:

import AIInbx from "aiinbx"
import { WelcomeEmail } from "./emails/welcome"

const aiinbx = new AIInbx()

await aiinbx.emails.send({
  from: "Acme <hello@example.com>",
  to: "ada@example.com",
  subject: "Welcome",
  react: <WelcomeEmail name="Ada" />,
})

Install @react-email/render (or @react-email/components) alongside React. The renderer is imported lazily — only when react is present — so it costs nothing if you never use it.

Pagination

A list call is awaitable for one page:

const page = await aiinbx.threads.list({ limit: 50, query: "invoice" })

…and iterable for all of them, carrying your filters onto every request:

for await (const thread of aiinbx.threads.list({
  mailbox: "team@example.com",
})) {
  console.log(thread.subject)
}

// A known-small collection, in full:
const domains = await aiinbx.domains.list().all()

// Page by page, when you want to checkpoint:
for await (const page of aiinbx.emails.list().iterPages()) {
  await saveCheckpoint(page.next_cursor)
}

A thread’s messages paginate separately:

for await (const message of aiinbx.threads.iterateMessages("thr_123", {
  message_limit: 100,
})) {
  console.log(message.subject)
}

Response metadata

Every call is awaitable. .withResponse() gets you the headers and request ID too:

const { data, response, requestId } = await aiinbx.emails
  .retrieve("eml_123")
  .withResponse()

console.log(response.status, requestId, data.subject)

Errors

Non-2xx responses throw APIError with status, code, requestId, headers, and the parsed body. Subclasses let you branch without comparing numbers:

import { APIError, NotFoundError, RateLimitError } from "aiinbx"

try {
  await aiinbx.domains.retrieve("dom_missing")
} catch (error) {
  if (error instanceof NotFoundError) return null
  if (error instanceof RateLimitError) return scheduleRetry()
  if (error instanceof APIError) console.error(error.status, error.code)
  throw error
}
Class Status
BadRequestError 400
AuthenticationError 401
PermissionDeniedError 403
NotFoundError 404
RequestTimeoutError 408
ConflictError 409
UnprocessableEntityError 422
RateLimitError 429
InternalServerError 5xx

Codes and what to do about each are in Errors.

Webhooks

verifyWebhookRequest handles the raw body, header parsing, timing-safe comparison, and the five-minute replay window:

import { verifyWebhookRequest } from "aiinbx/webhooks"

export async function POST(request: Request) {
  const event = await verifyWebhookRequest(
    request,
    process.env.AI_INBX_WEBHOOK_SECRET!
  )

  // `type` is a discriminant — TypeScript knows this event's exact data.
  if (event.type === "email.received") {
    console.log(event.data.thread_id, event.data.category)
  }

  return new Response(null, { status: 204 })
}

When your framework hands you the body and signature separately:

import { verifyWebhook } from "aiinbx/webhooks"

const event = await verifyWebhook(rawBody, signature, secret)

Both throw WebhookSignatureError. More, including per-framework raw-body recipes, in Verifying.

Types

Every request and response type is exported from the package root:

import type {
  Email,
  FullThread,
  SendEmailParams,
  WebhookEvent,
  WebhookEventType,
} from "aiinbx"

function summarize(thread: FullThread): string {
  return thread.messages.map((m) => m.snippet).join("\n")
}

WebhookEvent is a discriminated union over type, so a switch narrows data in each branch with no casts.

Runtimes

Runtime Notes
Node.js 20+ fetch and Web Crypto are built in.
Bun Works as-is.
Deno Works as-is.
Cloudflare Workers Works. apiKey must be passed explicitly — there’s no process.env.
Vercel Edge Same.
Browsers Technically works; don’t. It would ship your API key to every visitor.

Last updated on September 12, 2026

Was this page helpful?