Skip to main content
@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

string
required
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.
string
default:"https://api.senderkit.com"
Override the API base URL. Useful for pointing at a proxy or a self-hosted gateway.
number
default:"30000"
Per-request timeout in milliseconds. A timed-out request rejects with SenderKitTimeoutError (after retries are exhausted).
number
default:"2"
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.
typeof fetch
Inject a custom fetch implementation — handy for tests or edge runtimes that don’t expose a global fetch. Defaults to globalThis.fetch.
"live" | "test"
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.
string
required
Template slug, e.g. "welcome".
string
required
Recipient address — email address, E.164 phone number, push device token, or JSON-encoded web-push PushSubscription, depending on the resolved channel. A non-E.164 SMS recipient is rejected.
Record<string, unknown>
Template variables. Defaults to {}.
"email" | "sms" | "push" | "web-push"
Force a channel. Defaults to the template’s primary channel.
number
Pin a specific template version. Omit to use the current published version for the environment.
Record<string, string | number | boolean>
Free-form metadata attached to the message. Indexed server-side, so you can later filter with messages.list({ metadata }).
string | Date
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.
string
Idempotency key. If omitted, the SDK auto-generates one so a retried request never duplicates a send. Reusing a key returns the original message.
string[]
Cc recipients. Email only. Max 50 addresses.
string[]
Bcc recipients. Email only. Max 50 addresses.
string
Reply-To address. Email only.
Attachment[]
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.
string
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.
string
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

string
Message id, e.g. "msg_…".
"queued" | "scheduled"
"scheduled" when scheduledAt is in the future, otherwise "queued".
boolean
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().
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.
number
default:"5"
Max parallel in-flight requests.
string
Base key. Each item is sent with ${key}-${index} (unless the item carries its own idempotencyKey).
Each BatchSendResult is one of:

context()

Fetch the workspace the API key belongs to and the active send mode.
Returns { 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

number
Max messages to return, 1-200 (default 50).
string
Pagination cursor — pass the previous response’s nextCursor.
string
Filter by status, e.g. "delivered", "failed", "blocked".
"email" | "sms" | "push" | "web-push"
Filter by channel.
string
Filter by template slug.
Record<string, string | number | boolean>
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 statusMessage.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" }).
suppressed statusMessage.status can be "suppressed" on managed (AWS SES) sending: the provider accepted the send but never attempted delivery, because the recipient address failed validation or was already on the account’s suppression list. It’s distinct from "failed", which is a bounce reported by the receiving mail server after an actual delivery attempt. Bring-your-own-provider connections never return "suppressed". Subscribe to the message.suppressed webhook event to be notified as it happens.
EngagementMessage.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.

Inbound

Receive email, not just send it. Requires an API key with the inbound scope. See Inbound Email for the full concept reference.
string
1–64 chars of a-z 0-9 . _ -, starting and ending alphanumeric. Omit to auto-generate an unguessable rcv-xxxxxxxxxx local part. Pass "*" for a catch-all that receives every local part on its domain that no exact address already claims — an exact-match address always takes priority.
string
A verified custom domain’s id (from domains.list()) to mint the address on. Omit for the workspace’s shared {slug}.in.senderkit.email domain.
boolean
default:"true"
Test-mode (false) addresses still receive real mail and fan out to test-mode webhook endpoints, but any forwardTo is recorded as a test send rather than actually delivered. Every address, test or live, counts toward the inbound-address plan limit — only received messages on a test-mode address skip the message quota.
string
Also forward received mail to a real inbox. Can’t point at another inbound address (rejected as a mail loop).
string
Bind the address to one specific webhook endpoint — it receives message.received even if not itself subscribed, but must be active and match the address’s livemode. Left unset, message.received fans out to every active endpoint subscribed to it in the address’s mode.
string
required
Domain to claim for receiving, e.g. "inbound.acme.com". Must not already be claimed and must not be a senderkit.com/senderkit.email suffix.
boolean
Only set true after the user has confirmed. Omit on the first attempt — a domain with live MX records pointing elsewhere rejects with a 409 (SenderKitApiError, code: "existing_mx") naming the current host(s), so you can confirm before redirecting all of that domain’s mail to SenderKit.
domains.create() returns the DNS records to publish — an MX plus a DKIM TXT proving ownership, unless the domain already has a verified custom sending domain on the workspace, which reuses that identity and needs only the MX. Nothing is received until the records are live and a background sweep flips the domain’s status from "pending" to "verified" (or "failed" after the same verification window as custom sending domains). messages.raw() and messages.attachment(id, index) return the original message/rfc822 source and one attachment’s bytes as a BinaryResponse; both are only available for 30 days after receipt.

Error handling

Every error extends SenderKitError. API errors carry status, code, issues, and requestId (echoed from the x-request-id response header — quote it in support requests). The SDK already retries transient failures (429, 5xx, network, timeout) up to maxRetries with backoff, so a thrown error means retries were exhausted.

Exports

The package exports the SenderKit 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, CancelMessageResponse, InboundAddress, CreateInboundAddressParams, DeleteInboundAddressResponse, InboundMessage, InboundMessageSummary, InboundMessageStatus, InboundAttachment, ListInboundMessagesParams, InboundDomain, InboundDnsRecord, CreateInboundDomainParams, DeleteInboundDomainResponse, and BinaryResponse.

Sending

What a send accepts now and delivers later.

Messages

The lifecycle behind messages.list and cancel.

Inbound Email

Receive mail on your workspace’s domain or your own.

HTTP API

Integrate without the SDK, via raw fetch.

API Reference

The underlying REST endpoints.