@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.

Terminal
npm install @nexiom/connect

Initialize the client

Create one client on your server and reuse it. Load the API key from your environment; never ship it to the browser.

TypeScript
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 });
OptionTypeDescription
apiKeystring · requiredYour API key, starting with nc_.
timeoutnumber · optionalTotal deadline per call in milliseconds, including retries. Default 30000.
maxRetriesnumber · optionalExtra attempts for reads and idempotent sends. Default 2.
baseUrlstring · optionalAPI root. Default https://api-connect.nxiom.com/api.
fetchfunction · optionalA Fetch-compatible implementation, for instrumentation or tests.

Send an email

TypeScript
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.

TypeScript
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.

TypeScript
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.

TypeScript
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.

TypeScript
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.

TypeScript
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.

TypeScript
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;
}
PropertyDescription
kindapi for an error response; otherwise the transport failure type.
statusHTTP status, when the API responded.
codeMachine-readable error code, when present.
requestIdThe 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 }.

Nexiom Connect
Open dashboard