@senderkit/sdk is the official TypeScript client. It is ESM + CJS,
zero-dependency, fully typed, and runs on Node.js 18+ and edge runtimes (it uses
the global fetch). It wraps the REST API with
typed requests, automatic retries with backoff, idempotency, and batch helpers.
Install
Quickstart
Client construction
Your API key. Must start with
sk_live_ (live mode) or sk_test_ (test
mode); the constructor throws a TypeError otherwise. The SDK does not
read process.env for you — pass it explicitly. See
Authentication.Override the API base URL. Useful for pointing at a proxy or a self-hosted
gateway.
Per-request timeout in milliseconds. A timed-out request rejects with
SenderKitTimeoutError (after retries are exhausted).Max retry attempts for transient failures — network errors, timeouts,
429,
and 5xx (except 501). Retries use exponential backoff with jitter and
honor a Retry-After response header.Inject a custom
fetch implementation — handy for tests or edge runtimes
that don’t expose a global fetch. Defaults to globalThis.fetch.Read-only property (not a constructor option). Derived from the API key
prefix:
sk_test_… → "test", anything else → "live". Useful for
conditional logging or safety checks: if (senderkit.mode === "test") ….send()
Send a templated message, interpolating
variables at send time.
Template slug, e.g.
"welcome".Recipient address — email, phone number, or push token.
Template variables. Defaults to
{}.Force a channel. Defaults to the template’s primary channel.
Pin a specific template version. Omit to use the
current published version for the environment.
Free-form metadata attached to the message. Indexed server-side, so you can
later filter with
messages.list({ metadata }).Defer delivery to a future time — an ISO 8601 string or a
Date. Must be in
the future and within 30 days. The response comes back with
status: "scheduled". See Sending.Idempotency key. If omitted, the SDK auto-generates one so a retried
request never duplicates a send. Reusing a key returns the original message.
Cc recipients. Email only.
Bcc recipients. Email only.
Reply-To address. Email only.
File or inline attachments (email only). Each
Attachment is
{ filename, contentType, content, inline?, contentId? } where content is
base64-encoded bytes. Provider caps the total across all attachments at
10 MB.Per-message From address override (email only, bare address). Falls back to
the connection’s From address. On managed sending it’s honored only on the
workspace’s verified custom sending domain.
Per-message From display name override (email only), rendered as
Name <address>. Falls back to the connection’s From name. Max 128
characters; no control characters or angle brackets. Unlike from, it
always applies regardless of sending domain.Response
Message id, e.g.
"msg_…"."scheduled" when scheduledAt is in the future, otherwise "queued".Whether the request ran against live mode. Derived from the API key prefix.
sendRaw()
Send inline content without a registered template. The content shape is
selected by channel.
SendRawRequest is a discriminated union on channel. Shared fields: to
(required), vars, metadata, interpolate, scheduledAt, idempotencyKey.
By default content is delivered verbatim — set interpolate: true to run
server-side variable substitution over it. Returns the same SendResponse as
send().
- email
- sms
- push
- web-push
content: RawEmailContent — { subject, html, preheader?, text? } plus the
email envelope fields (cc, bcc, replyTo, attachments). Two top-level
From overrides are also available, identical to send(): from? (bare
address; must match a
verified custom sending domain
on managed sending) and fromName? (display name, always applies).sendBatch()
Send many messages with bounded concurrency. Never throws for individual
failures — each result reports success or the error for that item, so one bad
recipient doesn’t sink the batch.
Max parallel in-flight requests.
Base key. Each item is sent with
${key}-${index} (unless the item carries
its own idempotencyKey).BatchSendResult is one of:
context()
Fetch the workspace the API key belongs to and the active send mode.
{ workspace: { id, slug, name }, mode: "live" | "test" }. Useful to
confirm which workspace subsequent calls will affect — mirrors
GET /v1/context and the
senderkit_context MCP tool.
Templates
list() returns templates without their version body. get(slug) includes
currentVersion ({ versionNumber, variables, publishedAt }). A Template
has slug, channel, description, status, and updatedAt.
The
content (raw HTML/blocks) field is intentionally omitted from both
list() and get() responses to keep payloads lean. Use the dashboard or
the /v1/templates/{slug}/render endpoint when you need the rendered output.Messages
Max messages to return. The server applies its own default and cap.
Pagination cursor — pass the previous response’s
nextCursor.Filter by channel.
Filter by template slug.
Filter by metadata attached at send time. Every key/value pair must match.
list() resolves to { data: Message[]; nextCursor: string | null }.
cancel(id) only works on scheduled or queued messages — later states
return a 409 (SenderKitApiError) — and resolves to
{ id, status: "canceled" }.
Message reads are lean — the rendered
content blob is intentionally omitted
to keep payloads manageable. The vars, timeline, and metadata attached at
send time are still returned on every message.blocked status — Message.status can be "blocked" when outbound abuse
detection halts a send before it reaches a provider. The message timeline records a
generic notice ("Blocked by automated content safety checks."); detailed detection
signals are operator-only and not returned in customer API responses. Filter for
blocked messages with messages.list({ status: "blocked" }).Engagement —
Message.openedAt / Message.clickedAt record the first
provider-reported email open / link click as an ISO 8601 string, or null if
it hasn’t happened yet. Each is set once, on the first occurrence — later
opens or clicks don’t update it. Subscribe to the message.opened /
message.clicked webhook events to be notified as they happen.Error handling
Every error extendsSenderKitError. API errors carry status, code,
issues, and requestId (echoed from the x-request-id response header — quote
it in support requests).
| Class | Thrown when | Extra properties |
|---|---|---|
SenderKitAuthenticationError | 401 — bad, missing, or revoked key | status, code, issues, requestId |
SenderKitPermissionError | 403 — key is valid but lacks the required scope; extends SenderKitApiError (see Scopes) | status, code, issues, requestId |
SenderKitValidationError | 400 / 422 — invalid request | status, code, issues, requestId |
SenderKitRateLimitError | 429 — rate limited | retryAfter (milliseconds, when the server sent Retry-After) + the above |
SenderKitApiError | any other non-2xx (e.g. 409, 5xx) | status, code, issues, requestId |
SenderKitTimeoutError | request exceeded timeout | — |
SenderKitNetworkError | network-level failure | cause |
429, 5xx, network, timeout) up
to maxRetries with backoff, so a thrown error means retries were exhausted.
Exports
The package exports theSenderKit class, every error class above, the
VERSION constant, and types including SenderKitOptions, SenderKitContext,
ApiScope, SendRequest, SendRawRequest, SendResponse, BatchSendOptions,
BatchSendResult, Attachment, EmailEnvelope, Template, Message,
ListMessagesParams, ListMessagesResponse, and CancelMessageResponse.
Sending
What a send accepts now and delivers later.
Messages
The lifecycle behind
messages.list and cancel.HTTP API
Integrate without the SDK, via raw
fetch.API Reference
The underlying REST endpoints.