Node.js SDK
Send email and manage contacts from Node.js with the official, fully typed Nexiom Connect SDK.
@nexiom/connect is the official SDK for Node.js. It is fully typed, ships ESM and CommonJS builds, has no runtime dependencies, and retries safely for you.
Install
Node.js 22 or later is required.
npm install @nexiom/connectInitialize the client
Create one client on your server and reuse it. Load the API key from your environment; never ship it to the browser.
import { NexiomConnect } from '@nexiom/connect';
const apiKey = process.env.NEXIOM_API_KEY;
if (!apiKey) {
throw new Error('NEXIOM_API_KEY is required');
}
export const nexiom = new NexiomConnect({ apiKey });| Option | Type | Description |
|---|---|---|
apiKey | string · required | Your API key, starting with nc_. |
timeout | number · optional | Total deadline per call in milliseconds, including retries. Default 30000. |
maxRetries | number · optional | Extra attempts for reads and idempotent sends. Default 2. |
baseUrl | string · optional | API root. Default https://api-connect.nxiom.com/api. |
fetch | function · optional | A Fetch-compatible implementation, for instrumentation or tests. |
Send an email
const { data, error } = await nexiom.emails.send(
{
from: 'hello@mail.example.com',
fromName: 'Acme',
to: 'reader@example.com',
subject: 'Your receipt',
html: '<p>Thanks for your order.</p>',
text: 'Thanks for your order.',
},
{ idempotencyKey: `receipt-${order.id}` },
);
if (error) {
throw error;
}
console.log(data.messageId, data.deliveryIds);Pass an idempotencyKey that identifies the email in your system. If you omit it, the SDK generates one per call, which protects its own retries but not a retry of your whole operation. See Sending email.
To send a template, pass templateId and templateVariables instead of subject and content.
Schedule an email
Scheduling needs @nexiom/connect 0.2.1 or later. Pass scheduledAt as a Date or an ISO 8601 timestamp with an offset, up to 30 days ahead. Keep the returned messageId to move or cancel the send before it goes out.
const DAY = 24 * 60 * 60 * 1000;
const { data, error } = await nexiom.emails.send({
from: 'hello@mail.example.com',
to: 'reader@example.com',
subject: 'Your trial ends soon',
text: 'Your trial ends soon.',
scheduledAt: new Date(Date.now() + DAY),
});
if (error) {
throw error;
}
// Move it one day later, or cancel it before it is sent.
await nexiom.emails.reschedule(data.messageId, { scheduledAt: new Date(Date.now() + 2 * DAY) });
await nexiom.emails.cancel(data.messageId);See Reschedule and Cancel for the rules.
Read email logs
emails.list and emails.get need 0.2.1 or later and a full-access key. list pages with a cursor; get takes a delivery ID, not a message ID.
const { data: page, error } = await nexiom.emails.list({ status: 'bounced', limit: 50 });
if (error) {
throw error;
}
const [first] = page.items;
if (first) {
const { data: delivery, error: getError } = await nexiom.emails.get(first.id);
if (getError) {
throw getError;
}
console.log(delivery.recipient, delivery.delivery_reason);
}
// nextCursor is null on the last page.
if (page.nextCursor) {
await nexiom.emails.list({ status: 'bounced', cursor: page.nextCursor });
}See List emails and Retrieve an email for every field.
Manage contacts
Define a contact property before you store a value for it.
const { error: propertyError } = await nexiom.contacts.properties.create({ name: 'plan', type: 'string' });
if (propertyError) {
throw propertyError;
}
const { data: contact, error } = await nexiom.contacts.create({
email: 'reader@example.com',
firstName: 'Sam',
properties: { plan: 'pro' },
});
await nexiom.contacts.update(contact.id, {
email: 'reader@example.com',
emailStatus: 'unsubscribed',
});
const { data: page } = await nexiom.contacts.list({ search: 'example.com', limit: 50 });contacts.get, contacts.delete, and contacts.properties are available too. See the Contacts API for every field.
Send a WhatsApp message
messaging.send sends on every messaging channel; channel selects the fields, and TypeScript checks them for that channel. For WhatsApp, send an approved template from your connected number, named by its phone number in from. Requires 0.4.0 or later and a key with the whatsapp.send permission.
const { data, error } = await nexiom.messaging.send(
{
channel: 'whatsapp',
from: '+213555000111',
to: '+213555123456',
template: {
id: 'wat_example',
variables: { body: { 1: 'Amina', 2: 'A-1234' } },
},
},
{ idempotencyKey: 'order-A-1234-shipped' },
);
if (data) {
const message = await nexiom.messaging.get(data.id);
console.log(message.data?.status);
}See Sending WhatsApp messages.
Verify webhooks
webhooks.constructEventAsync (0.4.0 or later) checks a webhook request's signature, with the runtime's Web Crypto API, and its timestamp, then resolves to the typed event. Pass the raw body, not parsed JSON.
const event = await nexiom.webhooks.constructEventAsync(
rawBody,
request.headers,
process.env.NEXIOM_WEBHOOK_SECRET,
);
if (event.type === 'email.bounced') {
console.log(event.data.recipient);
}It rejects with NexiomWebhookVerificationError when the request is not genuine. See Verify webhook requests.
Handle errors
Every method resolves to { data, error, response } instead of throwing for API failures. Check error before using data.
const { data, error, response } = await nexiom.emails.send(message, { idempotencyKey });
if (error) {
// error.kind: 'api' | 'network' | 'timeout' | 'aborted' | 'protocol'
console.error(error.status, error.code, error.message, error.requestId);
return;
}| Property | Description |
|---|---|
kind | api for an error response; otherwise the transport failure type. |
status | HTTP status, when the API responded. |
code | Machine-readable error code, when present. |
requestId | The request's correlation ID. Include it when contacting support. |
Invalid arguments — for example a missing from — throw a NexiomValidationError before any request is made.
Retries and timeouts
The SDK retries reads, sends and cancels on 408, 429, and 5xx responses and on network failures, honouring Retry-After. Sends are retried with the same idempotency key, so a retry never sends twice. Other writes, such as creating a contact or rescheduling an email, are not retried automatically. If Retry-After asks for a longer wait than the remaining timeout, the SDK returns that error straight away.
Override per call with { timeout, maxRetries, signal }.