Quickstart
From nothing to a delivered email. Every snippet on this page lives in
packages/docs/examples/
and is typechecked against the current SDK in CI — if it compiles here, it
compiles for you.
1. Install
npm install @mailroom/sdk
# bun add @mailroom/sdk · pnpm add @mailroom/sdk · yarn add @mailroom/sdk
Node 18+ (or any runtime with global fetch: Bun, Deno, Cloudflare Workers,
Vercel Functions). No dependencies, ESM + CJS builds, types included.
2. Get an API key
In the Mailroom admin console: Settings → API keys → Create key. Choose the
scopes the integration actually needs — send alone for transactional email,
contacts:write if you also sync your list. See
Authentication for the full scope list.
The key is shown once. Put it in your server environment:
MAILROOM_URL="https://mailroom.example.com" # your Mailroom deployment
MAILROOM_API_KEY="pk_live_…"
The key grants full access to the project it belongs to. Never ship it to a browser, a mobile app, or a public repo. Mailroom has no publishable key.
3. Verify the connection
import { mailroom } from "./client";
// Cheapest possible check that the key, base URL, and network path all work.
const health = await mailroom.health();
console.log(health); // { service: "mailroom", sdk: "0.3.0" }
A MailroomAuthError here means the key is wrong or revoked; a
MailroomConnectionError means the base URL or network path is wrong. Fix that
before moving on — everything else depends on it.
4. Send your first email
Templates are enabled per project. welcome ships with Mailroom; swap in any key
from mailroom.templates.list().
import { createClient } from "@mailroom/sdk";
const mailroom = createClient({
apiKey: process.env.MAILROOM_API_KEY!,
baseUrl: process.env.MAILROOM_URL!,
});
const { id } = await mailroom.send({
to: "ada@example.com",
template: "welcome",
data: { name: "Ada" },
});
console.log(`queued send ${id}`);
send() resolves to { id } — the send-log id — or throws. Common failures:
| Error | code | Fix |
|---|---|---|
MailroomValidationError | unknown_template | The template key does not exist in the registry. |
MailroomValidationError | template_not_enabled | Enable it: templates.setEnabled(key, { enabled: true }). |
MailroomValidationError | no_brand | The project has no brand configured yet. |
MailroomAuthError | forbidden | The key is missing the send scope. |
Watch it land in Sends in the admin console, or read the log:
const [latest] = await mailroom.sends.list({ limit: 1 });
console.log(latest.status, latest.delivery_status);
5. Put contacts on the list
Sending to to: addresses works without ever creating a contact, but campaigns,
sequences, and unsubscribe handling all key off the contact list.
import { mailroom } from "./client";
// Upsert is idempotent on (project, email): safe to replay for the same person.
const contacts = await mailroom.contacts.upsert([
{ email: "ada@example.com", tags: ["beta"], source: "signup-form" },
{ email: "grace@example.com" },
]);
console.log(contacts.map((c) => `${c.email} → ${c.status}`));
Now you can send by contactId instead of to, and every marketing email
automatically carries a working unsubscribe link.
Where to next
- Sending — transactional vs marketing, brands, idempotency
- Templates — what
dataa template accepts, publishing content - Sequences — lifecycle automation and event triggers
- Webhooks — receive delivery and engagement events
- Errors and retries — what to catch, what retries itself
- SDK reference — generated from the SDK's types