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.