@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().
- 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.
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).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
number
Max messages to return, 1-200 (default 50).
string
Pagination cursor — pass the previous response’s
nextCursor."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 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" }).suppressed status — Message.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.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.Inbound
Receive email, not just send it. Requires an API key with theinbound
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 extendsSenderKitError. 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 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, 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.