Node.js SDK
Node.js SDK
Section titled “Node.js SDK”Official Node.js client for the Optail API. Works with Node.js 18+ and any runtime that supports the Fetch API (Bun, Deno, Cloudflare Workers).
Installation
Section titled “Installation”npm install optailConfiguration
Section titled “Configuration”import { Optail } from 'optail';
const optail = new Optail({ apiKey: process.env.OPTAIL_API_KEY, // Required baseUrl: 'https://api.optail.io', // Optional, default shown timeout: 30000, // Optional, ms, default: 30000});Config Options
Section titled “Config Options”| Option | Type | Required | Default | Description |
|---|---|---|---|---|
apiKey | string | Yes | — | Your Optail API key. |
baseUrl | string | No | https://api.optail.io | API base URL. Override for self-hosted instances. |
timeout | number | No | 30000 | Request timeout in milliseconds. |
Methods
Section titled “Methods”send(params)
Section titled “send(params)”Send a single email. Returns immediately with a message ID (the email is queued for async delivery).
const result = await optail.send({ to: 'user@example.com', from: 'hello@yourdomain.com', subject: 'Welcome!', html: '<h1>Hello World</h1>', tags: ['welcome'],});
console.log(result.messageId); // "a1b2c3d4-..."console.log(result.status); // "queued" | "suppressed"Parameters: SendEmailParams
Section titled “Parameters: SendEmailParams”| Field | Type | Required | Description |
|---|---|---|---|
to | string | string[] | Yes | Recipient email(s). |
from | string | Yes | Sender email address. |
subject | string | Yes | Email subject. |
html | string | Conditional | HTML body. |
text | string | Conditional | Plain text body. |
templateId | string | Conditional | Template UUID to render. |
variables | Record<string, unknown> | No | Template variables. |
replyTo | string | No | Reply-to address. |
headers | Record<string, string> | No | Custom email headers. |
tags | string[] | No | Tags for routing and analytics. |
metadata | Record<string, unknown> | No | Arbitrary metadata. |
trackOpens | boolean | No | Enable open tracking. |
trackClicks | boolean | No | Enable click tracking. |
At least one of html, text, or templateId must be provided.
Returns: SendEmailResponse
Section titled “Returns: SendEmailResponse”| Field | Type | Description |
|---|---|---|
messageId | string | Unique message ID. |
status | "queued" | "suppressed" | Whether the email was queued or suppressed. |
sendBatch(params)
Section titled “sendBatch(params)”Send multiple emails in a single request.
const result = await optail.sendBatch({ messages: [ { to: 'alice@example.com', from: 'hello@yourdomain.com', subject: 'Hello Alice', html: '<p>Hi Alice!</p>', }, { to: 'bob@example.com', from: 'hello@yourdomain.com', subject: 'Hello Bob', html: '<p>Hi Bob!</p>', }, ],});
for (const item of result.results) { if ('messageId' in item) { console.log(`Queued: ${item.messageId}`); } else { console.error(`Error: ${item.error}`); }}Returns: BatchSendResponse
Section titled “Returns: BatchSendResponse”| Field | Type | Description |
|---|---|---|
results | Array<{ messageId: string; status: string } | { error: string }> | Result for each message. |
listTemplates()
Section titled “listTemplates()”List all templates for the organization.
const templates = await optail.listTemplates();
for (const tpl of templates) { console.log(`${tpl.name} (${tpl.id})`);}Returns: TemplateResponse[]
Section titled “Returns: TemplateResponse[]”| Field | Type | Description |
|---|---|---|
id | string | Template ID. |
name | string | Template name. |
tags | string[] | Template tags. |
currentVersionId | string | undefined | Active version ID. |
getTemplate(templateId)
Section titled “getTemplate(templateId)”Get a single template by ID.
const template = await optail.getTemplate('tpl-uuid');console.log(template.name);checkSuppression(email)
Section titled “checkSuppression(email)”Check if an email address is suppressed.
const result = await optail.checkSuppression('user@example.com');
if (result.suppressed) { console.log(`Suppressed in groups: ${result.groups.join(', ')}`);}Returns: SuppressionCheckResponse
Section titled “Returns: SuppressionCheckResponse”| Field | Type | Description |
|---|---|---|
suppressed | boolean | Whether the email is suppressed. |
groups | string[] | Unsubscribe group IDs where the email is suppressed. |
addSuppression(email, groupId)
Section titled “addSuppression(email, groupId)”Add a suppression for an email address in a specific unsubscribe group.
await optail.addSuppression('user@example.com', 'grp-uuid');Returns void on success.
removeSuppression(email, groupId)
Section titled “removeSuppression(email, groupId)”Remove a suppression, allowing emails to that address again.
await optail.removeSuppression('user@example.com', 'grp-uuid');Returns void on success.
Webhook Verification
Section titled “Webhook Verification”verifyWebhookSignature(payload, signature, secret)
Section titled “verifyWebhookSignature(payload, signature, secret)”Verify the HMAC-SHA256 signature of an incoming webhook request. Uses timing-safe comparison to prevent timing attacks.
import { verifyWebhookSignature } from 'optail';
const isValid = verifyWebhookSignature( rawBody, // string | Buffer -- the raw request body signature, // string -- X-Optail-Signature header webhookSecret, // string -- the secret from webhook creation);Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
payload | string | Buffer | Raw request body (not parsed JSON). |
signature | string | Value of the X-Optail-Signature header. |
secret | string | The webhook signing secret (whsec_...). |
Returns
Section titled “Returns”boolean — true if the signature is valid.
Express Example
Section titled “Express Example”import express from 'express';import { verifyWebhookSignature } from 'optail';import type { WebhookEvent } from 'optail';
const app = express();
app.post( '/webhooks/optail', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.headers['x-optail-signature'] as string; const isValid = verifyWebhookSignature( req.body, signature, process.env.OPTAIL_WEBHOOK_SECRET!, );
if (!isValid) { return res.status(401).send('Invalid signature'); }
const event: WebhookEvent = JSON.parse(req.body.toString()); console.log(`Event: ${event.type}, Message: ${event.messageId}`);
res.sendStatus(200); },);Error Handling
Section titled “Error Handling”The SDK throws typed errors that you can catch and handle:
import { Optail, OptailError, AuthenticationError, ValidationError, RateLimitError, NotFoundError,} from 'optail';
try { await optail.send({ ... });} catch (error) { if (error instanceof AuthenticationError) { // 401 -- Invalid or missing API key console.error('Auth failed:', error.message); } else if (error instanceof ValidationError) { // 422 -- Request validation failed console.error('Invalid request:', error.message); } else if (error instanceof RateLimitError) { // 429 -- Too many requests console.error('Rate limited, retry after:', error.retryAfter, 'seconds'); } else if (error instanceof NotFoundError) { // 404 -- Resource not found console.error('Not found:', error.message); } else if (error instanceof OptailError) { // Any other API error console.error(`Error ${error.status}: ${error.message} (${error.code})`); }}Error Classes
Section titled “Error Classes”| Class | HTTP Status | Code | Description |
|---|---|---|---|
AuthenticationError | 401 | AUTHENTICATION_ERROR | Invalid or missing API key. |
ValidationError | 422 | VALIDATION_ERROR | Request body failed validation. |
RateLimitError | 429 | RATE_LIMIT_EXCEEDED | Too many requests. Has retryAfter property (seconds). |
NotFoundError | 404 | NOT_FOUND | Requested resource does not exist. |
OptailError | varies | varies | Base class for all SDK errors. Has status, code, and message. |
TypeScript Types
Section titled “TypeScript Types”All types are exported for use in your application:
import type { OptailConfig, SendEmailParams, SendEmailResponse, BatchSendParams, BatchSendResponse, TemplateResponse, SuppressionCheckResponse, WebhookEvent,} from 'optail';