TypeScript SDK
Reference for the samva TypeScript SDK, covering client setup, the email send facade, every service namespace, errors, and the native Effect client.
The samva package is the official TypeScript SDK for the Samva API. It is generated from the
OpenAPI document the API publishes, so every method matches
the operation it calls and covers the whole public API.
This page is a reference. For a step-by-step first send, see Send your first email.
Installation
npm install samvaEntrypoints
| Import | Style | When to use |
|---|---|---|
samva | Promise (default) | Most apps. |
samva/promises | Promise (explicit) | The same client as the root; use it when the same file also imports samva/effect. |
samva/effect/* | Native Effect v4 | Effect applications. Namespace modules, client, errors, and focused helpers. Requires effect as a peer dependency. |
@samva/webhooks | Promise | Verify and decode signed webhook deliveries. @samva/webhooks/effect is the Effect form. See Verify webhooks. |
The rest of this page documents the Promise client until Native Effect client.
Client
createClient(config)
import { createClient } from "samva";
const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });| Option | Type | Required | Default | Description |
|---|---|---|---|---|
apiKey | string | One of apiKey / authToken | none | API key, sent as X-API-Key. The organization comes from the key. |
authToken | string | One of apiKey / authToken | none | OAuth bearer token, such as a samva login session. |
baseUrl | string | No | https://api.samva.dev | API base URL. Override it for testing. |
headers | object | No | none | Headers sent on every request. |
fetch | function | No | global fetch | The fetch implementation to use. |
orgSlug | string | With authToken | none | Organization selected for an OAuth session. |
maxRetries | number | No | SDK default | Maximum retries per call; 0 disables retries. |
Pass exactly one of apiKey or authToken. With authToken, also pass the organization as
orgSlug: "<slug>". See Authentication.
The Promise client accepts string API keys and does not require Effect. The Effect client accepts
string | Redacted<string> and unwraps a Redacted key only for authentication.
The client sends an advisory x-samva-client header naming the SDK and its version. Override it
through headers, or set it to null in a call's request options to omit it. Samva does not use it
for authentication or authorization.
Calling convention
Every method takes one flat object of request parameters, path, query, and body fields together,
and resolves with the decoded success value. Request options such as headers and signal go in an
optional second argument. A failure throws; see Error handling.
import { createClient } from "samva";
const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });
const message = await samva.messages.get({ id: "msg_7q2xk9mvt4znw8rh" });Each method is named for the operation it calls in the API reference: for
example samva.contacts.find calls
Find a contact, and the reference page documents its
parameters, response, and errors. samva.email.send is the one method with no operation of its own.
Namespaces
| Namespace | Covers | Effect module | Reference |
|---|---|---|---|
analytics | Aggregate send statistics and exports | samva/effect/analytics | Analytics |
apiKeys | API keys, rotation, and usage | samva/effect/api-keys | API keys |
attachments | A message's attachments and their download URLs | samva/effect/attachments | Attachments |
brands | Brand themes, logos, and footers that templates import | samva/effect/brands | Brands |
campaigns | Audience campaigns, their runs, and recipients | samva/effect/campaigns | Campaigns |
contactGroups | Static and dynamic contact groups and their members | samva/effect/contact-groups | Contact groups |
contacts | Contact records, bulk changes, and activity | samva/effect/contacts | Contacts |
conversations | Threads of related messages and their contacts | samva/effect/conversations | Conversations |
customFields | Custom contact field definitions | samva/effect/custom-fields | Contacts |
email | The send facade, domains, senders, tracking, review, and blocks | samva/effect/email | |
extensionInstallations | Install, enable, disable, and uninstall extensions, and read their runs | samva/effect/extension-installations | Mailbox extensions guide |
extensions | Publish mailbox extensions and read their versions | samva/effect/extensions | Mailbox extensions guide |
feedback | Reports to Samva about what was confusing, broken, or missing | samva/effect/feedback | Send feedback |
mailboxes | Mailboxes, threads, messages, grants, quarantine, and events | samva/effect/mailboxes | Mailboxes guides |
media | Organization-owned uploads for attachments | samva/effect/media | Media |
messages | Sending, listing, status, and events for messages | samva/effect/messages | Messages |
namespaces | Sub-tenants for a platform's customers, with their usage | samva/effect/namespaces | Namespaces |
operations | Long-running operation status and retry | samva/effect/operations | Operations |
organizations | The organization record and its usage | samva/effect/organizations | Organizations |
scheduledMessages | Future sends: create, list, get, cancel, and resume | samva/effect/scheduled-messages | Scheduled messages |
subscriptions | Request and confirm recipient consent | samva/effect/subscriptions | Double opt-in |
templates | Template projects, families, workspaces, and publications | samva/effect/templates | Templates |
topics | Topics and per-channel contact subscriptions | samva/effect/topics | Topics |
webhooks | Webhook endpoints, test deliveries, logs, retries, and secrets | samva/effect/webhooks | Webhooks |
whoami | The organization and credential behind the request | samva/effect/whoami | Get the calling credential |
raw | Every namespace unwrapped: result envelopes and the HTTP client | Promise client only | Raw access |
| Namespace | What it does | Effect module | Guide |
|---|---|---|---|
topicForms | Manage hosted and embedded topic signup forms | samva/effect/topic-forms | Topic signup |
Preview · Available to selected organizations.
| Namespace | What it does | Effect module | Guide |
|---|---|---|---|
newsletters | Newsletters and publishing settings | samva/effect/newsletters | Newsletters |
newsletters.issues | Author issues and save campaign content | samva/effect/newsletter-issues | Issues |
| Namespace | What it does | Effect module | Guide |
|---|---|---|---|
newsletters.domains | Manage custom archive domains | samva/effect/newsletter-domains | Domains |
Preview · Available to selected organizations.
| Namespace | What it covers | Effect module | Reference |
|---|---|---|---|
events | Custom contact event ingestion | samva/effect/events | Contact events |
journeys | Event-triggered journeys: drafts, versions, lifecycle, and runs | samva/effect/journeys | Journeys |
Preview · Available to selected organizations.
| Namespace | What it covers | Effect module | Reference |
|---|---|---|---|
agents | Hosted agent definitions: drafts, published versions, and their history | samva/effect/agents | Agents |
samva.email.send
email.send is an SDK-only facade over messages.send; both call
Send a message. It fixes the channel to email, flattens the
fields of the email object into its input, and accepts a single recipient or an array.
import { createClient } from "samva";
const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });
const message = await samva.email.send({
to: "ada@example.com",
cc: "grace@example.com",
replyTo: "support@example.com",
subject: "Welcome to Samva",
html: "<h1>Welcome!</h1>",
});
console.log("Sent:", message.id);The input takes to, cc, bcc, from, conversationId, and metadata from the message, plus
every field of the email object documented on
Send a message, such as subject, html, text,
attachments, templateId, templateData, inReplyToMessageId, and topicId.
to, cc, and bcc accept a single value or an array of any of:
| Form | Example |
|---|---|
| Email string | "ada@example.com" |
| Email object | { email: "ada@example.com" } |
| Contact reference | { contactId: "contact_k9mvt4znw8rh7q2x" } |
| Contact object | { id: "contact_k9mvt4znw8rh7q2x" }, such as a contact the SDK returned |
Attachments reference ready media from samva.media or carry bounded base64 content; see
Send an attachment for the flow and limits.
Retry a send safely
Every idempotent operation sends an Idempotency-Key header. Both clients generate one fresh key
per logical call and reuse it across built-in retries. Pass { idempotencyKey } only in the second
options argument when your code may
retry the same logical send. An identical retry returns the original message; reusing the key with a
different request returns a ConflictError. See Idempotency.
import { createClient } from "samva";
const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });
const receipt = await samva.email.send(
{ to: "ada@example.com", subject: "Your receipt", text: "Thanks for your order." },
{ idempotencyKey: "receipt:order-4417" },
);samva.messages.send
messages.send takes the message body of Send a message
directly: an explicit channel, recipient arrays, and content nested under its channel key.
Use email.send for email, including contact IDs, multiple recipients, and conversation replies.
Use messages.send when working with a channel-aware message body.
See Email and the messages API for both request shapes.
Error handling
Every error the API declares becomes a class named for its _tag, exported from samva, with the
error's fields as properties. All of them extend SamvaApiError. A network failure or an
unreadable response throws SamvaTransportError. The
Error reference lists every error and its fields.
import { createClient, RateLimitedError, SamvaApiError, SamvaTransportError } from "samva";
const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });
try {
const message = await samva.email.send({
to: "ada@example.com",
subject: "Test",
text: "Hello",
});
console.log(message.id);
} catch (error) {
if (error instanceof RateLimitedError) {
console.error(`Retry after ${error.retryAfterSeconds} seconds`);
} else if (error instanceof SamvaApiError) {
console.error(error._tag, error.status, error.message);
} else if (error instanceof SamvaTransportError) {
console.error("Network or response failure", error.cause);
} else {
throw error;
}
}Raw access
samva.raw exposes each namespace without the direct-value wrapper. A raw call returns the
{ data, error, request, response } envelope instead of throwing, which is useful when you need the
HTTP response itself.
import { createClient } from "samva";
const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });
const result = await samva.raw.messages.get({ id: "msg_7q2xk9mvt4znw8rh" });
if (result.error) {
console.error(result.response?.status, result.error);
} else {
console.log(result.data);
}The lower-level samva.raw.client HTTP transport uses your configured credentials and accepts
HTTP-shaped request options. It does not apply operation-specific normalization, retries, or
idempotency defaults.
Promise retries and pagination
The Promise client retries reads and idempotent mutations on retryable failures. Set
createClient({ apiKey, maxRetries: 0 }) to disable retries, or pass { maxRetries } in an
operation's second options argument. Non-idempotent mutations never retry.
contacts.startImport retries only mode: "new". Resumes use a generation fence and never retry
automatically, even with an explicit key; read contacts.getImportJob after an ambiguous response.
Cursor lists expose lazy async iterators: samva.contacts.list.pages(params) yields page
envelopes, and .items(params) yields individual items while preserving filters.
With an OAuth token, createClient({ authToken, orgSlug }) selects the organization. API keys
already identify their organization. A client-level idempotency: (operation) => key hook can
supply deterministic keys; an explicit per-call option takes precedence.
import { createClient } from "samva";
const samva = createClient({ apiKey: process.env.SAMVA_API_KEY!, maxRetries: 0 });
for await (const contact of samva.contacts.list.items()) {
console.log(contact.id);
}
for await (const page of samva.contacts.list.pages({ limit: 100 })) {
console.log(page.items.length, page.nextCursor);
}
const page = await samva.contacts.list({ limit: 20 }, { maxRetries: 1 });
console.log(page.items);Native Effect client
The samva/effect/* entrypoints form a native Effect v4 SDK. Each
namespace module exports operations that return Effects directly. Install the pinned peer alongside
samva:
npm install samva effect@4.0.6Import the client capability and the modules you use, and provide the fetch-backed client Layer at the program boundary:
import { Effect } from "effect";
import * as Client from "samva/effect/client";
import * as Email from "samva/effect/email";
const program = Email.send(
{
to: "ada@example.com",
subject: "Welcome to Samva",
html: "<h1>Welcome!</h1>",
},
{ idempotencyKey: "welcome:customer-123" },
).pipe(Effect.provide(Client.layerFetch({ apiKey: process.env.SAMVA_API_KEY! })));
const message = await Effect.runPromise(program);Each module is the Pascal-cased namespace, imported from the path in the
Namespaces table, such as * as Contacts from "samva/effect/contacts". Operations
take the same flat parameter objects as the Promise client and depend on Client.Service through
the Effect environment.
Errors arrive in the Effect error channel as the tagged errors from the
Error reference, so Effect.catchTag("RateLimitedError", …)
works on any operation. samva/effect/categories exports helpers such as isRetryable. The
Effect client retries reads and idempotent mutations on retryable failures by default; samva/effect/retry
configures or disables that. Paginated operations add pages and items Streams. Fields annotated
as absolute instants decode to Date, and secrets such as a new API key decode to Redacted.
Import constructors from samva/effect/errors when you need instanceof, schema decoding, or to
construct a failure in a test:
import { InternalError, MediaUploadError } from "samva/effect/errors";These are the same constructors the operations use. The focused entry avoids loading namespace
modules; native imports still initialize shared schemas. Effect.catchTag needs no constructor
import.
Serverless runtimes
Each samva/effect/<namespace> entrypoint loads only that namespace's part of the API, so a
Cloudflare Worker or other serverless function pays at startup only for the namespaces it imports.
A bundler can discard unused operations and their exclusive API groups. Importing only
Email.send retains Messages and Media, without email administration operations; Media handles
oversized inline attachments. Operations within a retained group still share its schemas. The
samva/effect barrel loads every namespace on a native import; prefer focused entrypoints in
serverless code.
Client.layerFetch does no work until the first call, and the first call to a namespace prepares
only that namespace, so providing the Layer costs little. In a Cloudflare Worker, provide it per
request and pass the request's abort signal, so no request waits on work another request started:
import { Effect } from "effect";
import * as Client from "samva/effect/client";
import * as Contacts from "samva/effect/contacts";
export default {
async fetch(request: Request, env: { SAMVA_API_KEY: string }): Promise<Response> {
const page = await Effect.runPromise(
Contacts.list({}).pipe(Effect.provide(Client.layerFetch({ apiKey: env.SAMVA_API_KEY }))),
{ signal: request.signal },
);
return Response.json(page.items);
},
};In a runtime that keeps one process alive across invocations, such as a Node.js server or a
function host that reuses its process, build the Layer once in a ManagedRuntime at module scope
so each namespace is prepared once for the life of the process:
import { ManagedRuntime } from "effect";
import * as Client from "samva/effect/client";
import * as Contacts from "samva/effect/contacts";
const runtime = ManagedRuntime.make(Client.layerFetch({ apiKey: process.env.SAMVA_API_KEY! }));
export const handler = async () => {
const page = await runtime.runPromise(Contacts.list({}));
return page.items;
};Support matrix
| SDK | Package | API version | Runtime |
|---|---|---|---|
| TypeScript | samva | v1 | Node.js, Bun, and Deno (ESM) |
| REST API | none | v1 | Any HTTP client |
For other languages, use the REST API with your language's HTTP client.