Skip to the documentation
SDK

Configuration

Three ways to build a client, every option, and what it refuses before a request is sent.

Options

openemail.ts
import OpenEmail, { createOpenEmail, init, openemail } from '@openemail/sdk' init({ apiKey: process.env.OPENEMAIL_API_KEY })await openemail.me.ping() export const billing = createOpenEmail({ apiKey: process.env.BILLING_API_KEY! }) const pinned = new OpenEmail({ apiKey: 'oe_live_…', baseUrl: 'https://api.openemail.uk' }) const quick = new OpenEmail('oe_live_…')
Entry pointWhat it gives you
`init(options)`Configures the shared client and returns it. openemail is that client from then on, in every module, and anything you leave out is read from the environment.
`openemail`The shared client. Used before init, it builds itself from OPENEMAIL_API_KEY and OPENEMAIL_BASE_URL on the first call.
`createOpenEmail(options)`A separate client with the same environment fallback, for a second key beside the shared one, or to build the instance your own module exports. createClient is the same function under the name the envless SDK uses.
`new OpenEmail(options)` or `new OpenEmail(apiKey)`A separate client built from exactly what you pass. It reads no environment, so apiKey is required. Also the default export.
options.ts
init({  apiKey: 'oe_live_…',  baseUrl: 'https://api.openemail.uk',  timeoutMs: 30_000,  maxRetries: 2,  fetch: myFetch,  headers: {},  userAgent: 'billing-service/1.4',  disableUpdateNotice: true,})
OptionDefaultNotes
`apiKey`OPENEMAIL_API_KEYRead from the environment by init and createOpenEmail. Must begin oe_live_ or oe_test_.
`baseUrl`https://api.openemail.ukOr OPENEMAIL_BASE_URL. A trailing slash is trimmed, and init and createOpenEmail put https:// in front of a bare host, or http:// in front of localhost.
`timeoutMs`30000Per attempt, not per call. Covers reading the body, not just the headers. 0 disables it.
`maxRetries`2Extra attempts after the first, on calls that are safe to repeat. Set on the client, not per call.
`fetch`the globalBound for you. Pass one for a proxy, a Worker binding or a test double.
`headers`{}Sent on every request.
`userAgent`openemail-sdk/<version>Sent from every runtime except a browser, which does not allow setting it.
`disableUpdateNotice`falseSkips the once-per-process check for a newer version on npm. The check only runs when output goes to a terminal, and OPENEMAIL_DISABLE_UPDATE_NOTICE turns it off too.
`dangerouslyAllowBrowser`falseLets the client start where window and document exist. Meant for a test harness that defines them, not for a page.

What it refuses before sending

These throw a plain Error from the line that had the wrong value in it, rather than surfacing as a confusing failure on your first send. The message says what was wrong and what to pass instead.

RefusedWhy
No key at allNeither apiKey nor OPENEMAIL_API_KEY was set, so there is nothing to authenticate with.
A session cookie or session tokenOnly oe_live_ and oe_test_ authenticate here, and the API says so too. The check is a prefix and nothing more, so a revoked key still fails on the wire.
A `baseUrl` that is not an http or https URLNothing else can be fetched, and an unvalidated one would fail later as a raw TypeError from somewhere else entirely.
A browserThe key would be readable by anyone who opens devtools. See the section below.
No `fetch` anywherePass one as fetch, or run on Node 20+.
An empty or all-dots id on any methodThrown when the method is called. A path segment of dots is removed by every URL parser, so the request would reach a different endpoint.

There is no testMode option and there will not be one. The key scheme is part of the credential rather than a hint, so mode is a property of the key. openemail.mode reads the prefix and decides nothing.

One client, several keys

Build the client once and share it. A fresh instance per request throws away the fetch binding and the configuration for nothing, and none of the state on it is per-caller.

For the case that would otherwise force one instance per key, such as a job sending on behalf of several workspaces, pass apiKey on the call. It replaces the Authorization header for that request and leaves nothing behind on the client.

per-call-key.ts
await openemail.emails.send(message) await openemail.emails.send(message, { apiKey: workspace.apiKey }) await openemail.threads.list({ folder: 'inbox', apiKey: workspace.apiKey })await openemail.webhooks.list({ apiKey: workspace.apiKey })

Every method outside tempMail takes it in its last argument, beside signal, and on a list that is the same object as the filters. It is checked before the request is sent, by the same rule the constructor uses, so a typo throws an Error naming { apiKey } on this call rather than a 401 about a credential you then have to go and find. A retried call keeps the key it was given.

signal is an AbortSignal. Aborting it stops the request, and any retry waiting behind it.

openemail.mode describes the key the client was CONSTRUCTED with and does not follow an override. Once one client serves several keys there is no single mode to report, so read it off the key you passed.

From a browser

The client refuses to start in a browser and throws before any request goes out. A key in a page is a key you have published: it can send mail and read the mailbox for anyone who opens devtools. Call it from a server, a serverless function or a script instead.

Disposable inboxes are the exception. createTempMail() builds a client that carries no API key, so it is safe in a page. It creates inboxes anonymously, and each read sends the token create returned, either per call as inboxToken or once as createTempMail({ inboxToken }).

temp-mail.ts
import { createTempMail } from '@openemail/sdk' const tempMail = createTempMail() const inbox = await tempMail.create()const { items, expiresAt } = await tempMail.listMessages(inbox.id, { inboxToken: inbox.token })

Where you pass dangerouslyAllowBrowser: true anyway, the API allows exactly Content-Type, Authorization and Idempotency-Key through its CORS preflight, so an extra header in headers fails the preflight rather than the request, and what a browser reports for that says nothing useful.