Installation¶
API Client¶
Initialize¶
In production, prefer environment variables (SEMA_API_KEY, SEMA_BASE_URL) and omit the key (and optionally base URL) below.
import { SemaClient } from '@withsema/sdk';
const client = new SemaClient({
apiKey: 'sk_live_...', // optional if SEMA_API_KEY is set
baseUrl: 'https://dev-api.withsema.com', // optional, falls back to SEMA_BASE_URL
timeout: 30000, // optional, in milliseconds
maxRetries: 3, // optional, set to 0 to disable retries
});
You can omit apiKey when SEMA_API_KEY is set; omit baseUrl when SEMA_BASE_URL is set.
The client requires HTTPS in production: it rejects live keys (sk_live_...) with a non-HTTPS base URL and warns for other HTTP base URLs (e.g. localhost).
The client automatically retries requests on transient failures (5xx errors, network errors, rate limits) with exponential backoff. Rate-limited requests (429) respect the Retry-After header when present.
Create an Inbox¶
const inbox = await client.createInbox({
name: 'Support Inbox',
webhook_url: 'https://example.com/webhooks/sema',
webhook_secret: 'whsec_...', // optional, auto-generated if omitted
});
console.log(inbox.id);
Upload Content¶
// From Buffer
const item = await client.uploadItem(
inbox.id,
Buffer.from('Hello, World!'),
{
sender_address: 'sender@example.com',
subject: 'Test Upload',
filename: 'hello.txt',
}
);
// From file
import { readFileSync } from 'fs';
const file = readFileSync('document.pdf');
const item = await client.uploadItem(inbox.id, file, {
sender_address: 'sender@example.com',
});
console.log(item.id, item.status, item.is_duplicate);
Get Item Details¶
const item = await client.getItem('...');
console.log(item.sender_address, item.subject, item.auth_decision);
List Items¶
const result = await client.listItems(inbox.id, { limit: 10 });
for (const item of result.items) {
console.log(item.id, item.status);
}
List Inboxes¶
const result = await client.listInboxes({ limit: 10, offset: 0 });
console.log(result.total);
for (const inbox of result.inboxes) {
console.log(inbox.id, inbox.name);
}
listInboxes(params?) accepts optional limit and offset for pagination and returns { inboxes, total }.
Pagination Iterators¶
For large result sets, use async iterators to automatically paginate:
// Iterate through all items in an inbox
for await (const item of client.iterItems(inboxId)) {
console.log(item.id, item.status);
}
// Iterate through all inboxes
for await (const inbox of client.iterInboxes()) {
console.log(inbox.id, inbox.name);
}
// Custom page size
for await (const item of client.iterItems(inboxId, { pageSize: 50 })) {
process.stdout.write('.');
}
Check Deliveries¶
const result = await client.getItemDeliveries('...');
for (const delivery of result.deliveries) {
console.log(delivery.status, delivery.attempt_count);
for (const attempt of delivery.attempts) {
console.log(` Attempt ${attempt.attempt_number}: ${attempt.status_code}`);
}
}
Webhook Verification¶
Initialize¶
import { WebhookVerifier } from '@withsema/sdk';
const verifier = new WebhookVerifier('whsec_...', {
toleranceSeconds: 300, // optional, default 5 minutes
});
Verify and Parse¶
import { WebhookVerificationError } from '@withsema/sdk';
function handleWebhook(req: Request, res: Response) {
try {
const event = verifier.verify(req.body, req.headers);
} catch (e) {
if (e instanceof WebhookVerificationError) {
return res.status(400).send(e.message);
}
throw e;
}
// Use webhookId as idempotency key
if (alreadyProcessed(event.webhookId)) {
return res.status(200).send('OK');
}
// Process the event
console.log(event.payload.event_type); // "ITEM_READY"
console.log(event.payload.item_id);
console.log(event.payload.inbox_id);
console.log(event.payload.payload_mode); // "full" or "thin"
// Access deliverable data
console.log(event.payload.deliverable.raw_ref);
if (event.payload.deliverable.sender) {
console.log(event.payload.deliverable.sender.address);
}
markProcessed(event.webhookId);
return res.status(200).send('OK');
}
Error Handling¶
import {
SemaError, // Base error
SemaAPIError, // API errors (has statusCode, responseBody)
AuthenticationError, // 401 - Invalid API key
NotFoundError, // 404 - Resource not found
RateLimitError, // 429 - Rate limit exceeded
WebhookVerificationError, // Signature/timestamp invalid
} from '@withsema/sdk';
try {
const item = await client.getItem('nonexistent');
} catch (e) {
if (e instanceof NotFoundError) {
console.log('Item not found');
} else if (e instanceof SemaAPIError) {
console.log(`API error ${e.statusCode}: ${e.message}`);
}
}
Observability Hooks¶
Add optional hooks for logging, tracing, or metrics:
const client = new SemaClient({
apiKey: 'sk_live_...',
hooks: {
beforeRequest: (ctx) => {
console.log(`→ ${ctx.method} ${ctx.url} (attempt ${ctx.attempt})`);
},
afterResponse: (ctx) => {
console.log(`← ${ctx.status} in ${ctx.durationMs}ms`);
},
onError: (ctx, error) => {
console.error(`✗ ${ctx.method} ${ctx.url}: ${error.message}`);
},
},
});
Hook context includes:
| Field | Description | Available In |
|---|---|---|
method |
HTTP method (GET, POST, etc.) | All hooks |
url |
Request URL | All hooks |
attempt |
Attempt number (1-based) | All hooks |
status |
HTTP status code | afterResponse |
durationMs |
Request duration in milliseconds | afterResponse |
Hooks can be sync or async. Errors thrown in hooks are silently ignored to prevent breaking SDK functionality.
Inline Images¶
Emails with pasted screenshots or inline images include cid: references in the HTML body. To render these images:
// Get attachments with presigned URLs
const { attachments } = await client.getItemAttachments(itemId);
// Build lookup from content_id to download URL
const cidToUrl = new Map<string, string>();
for (const att of attachments) {
if (att.content_id && att.download_url) {
cidToUrl.set(att.content_id, att.download_url);
}
}
// Replace cid: references in HTML body
let html = event.payload.deliverable.content_summary?.body_html ?? '';
for (const [cid, url] of cidToUrl) {
html = html.replace(`cid:${cid}`, url);
}
Type Reference¶
All responses are fully typed:
Inbox- Inbox configuration and statusItem- Item details including sender, status, metadataDelivery- Delivery status and attemptsWebhookEvent- Verified webhook withwebhookId,timestamp,payloadWebhookPayload- Parsed webhook body withdeliverabledata
Types are exported from the main package: