MyComm docs
Menu

SDKs

Node.js SDK

Install the Node.js SDK, send a message, handle errors and webhooks, and know what it checks before it sends.

@mycomm/node is the official client for Node.js 20+ and TypeScript; it also runs on Bun, Deno and edge runtimes. It has a method for every operation of the API, typed parameters and results, retries that cannot double-send, a check of your arguments before a request goes out, and webhook verification. It has no dependencies and ships as ES modules and CommonJS.

npm install @mycomm/node

Send a message#

import { MyComm } from '@mycomm/node';

const mycomm = new MyComm({ apiKey: process.env.MYCOMM_API_KEY });

const message = await mycomm.messages.send({
  to: '+923001234567',
  body: 'Your order #1094 is ready for pickup.',
  category: 'transactional',
  reference_id: 'order-1094',
});

message.id;
message.status; // "queued"

Every resource of the API reference is a property of the client (messages, contacts, webhookEndpoints, ...) and every operation a method on it, with the same names as the PHP SDK. Ids in the path come first; the other fields go in one object named like the API's fields, so the reference and your editor agree.

The SDK runs on your server. It refuses to run in a browser, where anyone could read the key: to let your users pair their phone from your page, create a hosted link on your server and show it with the Connect widget.

What it checks before sending#

The limits and rules of the OpenAPI document are checked first, and a mistake fails at once with the field named:

import { InvalidArgumentError } from '@mycomm/node';

try {
  await mycomm.messages.send({ to: number, body: text, expires_in: 30 });
} catch (error) {
  if (error instanceof InvalidArgumentError) {
    error.message; // expires_in must be between 60 and 604800 (got 30).
    error.field; // "expires_in"
    error.rule; // "OUT_OF_RANGE"
    error.violations; // every problem found, not only the first
  }
}

It checks ranges and lengths, allowed values, UUID format, metadata size and types, required fields, that you give exactly one of to and contact_id and at least one content field, that variables comes with template_id, and that category comes with the bulk lane. The list is generated from the contract, so it follows the API. What depends on state, such as an opt-out, is answered by the server. If the API relaxes a rule before you update the SDK, create the client with validate: false.

A dry run is typed as the preview: await mycomm.messages.send({ ..., dry_run: true }) has segments and hold_reason.

Handle errors#

Every error answer is an error object with the code, status, message, details and request id.

import { APIError } from '@mycomm/node';

try {
  await mycomm.messages.send({ to: number, body: text, category: 'marketing' });
} catch (error) {
  if (error instanceof APIError) {
    if (error.is('RECIPIENT_OPTED_OUT')) {
      // do not retry: the recipient unsubscribed
    }
    console.warn(error.summary()); // RECIPIENT_OPTED_OUT (422): The recipient ... details: {...}  request_id: ...
    error.docUrl; // the error reference entry for this code
  }
}

There is an error class per status (NotFoundError, RateLimitError, ValidationError with fields(), ...); the package README lists them. message says what happened and what to do; never parse it.

Retries and idempotency#

The SDK retries twice (configurable) after a network error or a timeout, a 429, a 503 or another 5xx, waiting as long as Retry-After asks (a number of seconds or an HTTP date). Every POST carries an Idempotency-Key, and a retry repeats the same key, so a retried send cannot send twice. Pass your own to make a whole job repeatable:

await mycomm.messages.send(
  { to: order.phone, body: `Order ${order.number} has shipped.` },
  { idempotencyKey: `order-shipped-${order.id}`, maxRetries: 0 },
);

The rules behind this are on Idempotency and retries.

Verify your settings#

const result = await mycomm.verify({ to: '+923001234567', lineId: 'LINE_ID' }); // lineId may be left out with a key limited to one line

if (!result.ok) {
  result.problems.forEach((problem) => console.log(problem));
}

verify() checks the key and dry-runs a send to your number. It sends nothing, and it reports a failure as a failed check, not an error. See Verify your integration.

Webhooks#

Pass the raw body, exactly as received (express.raw({ type: 'application/json' }), or await request.text() in a route handler):

import { Webhook } from '@mycomm/node';

const event = await Webhook.constructEvent(rawBody, signatureHeader, secret); // throws if the signature is wrong

if (Webhook.isKnown(event) && event.type === 'message.delivered') {
  event.data.message.id;
}

There is a type for each event in the event catalogue; Webhook.isKnown() narrows the event so a check of type types its data, and a type newer than the SDK is still returned as it arrived. Verification accepts a rotated secret during the overlap and rejects a timestamp more than 300 seconds off.

Test your own code#

MyCommFake from @mycomm/node/testing replaces the network: every operation answers with the example of the reference unless you stub it.

import { FakeResponse, MyCommFake } from '@mycomm/node/testing';

const fake = new MyCommFake();
fake.respond('messages.send', FakeResponse.error(422, 'RECIPIENT_OPTED_OUT'));

await notifyCustomer(fake.client, order);
fake.assertCalled('messages.send');

The package README has the full reference, the partner API (mycomm.partner.accounts.provision(...)) and the changelog.