@sendly/node
TypeScript icon, indicating that this package has built-in type declarations

3.34.0 • Public • Published

Sendly Node.js SDK

npm version license

@sendly/node

Official Node.js SDK for the Sendly SMS API.

Installation

npm install @sendly/node
# or
yarn add @sendly/node
# or
pnpm add @sendly/node

Requirements

Quick Start

import Sendly from '@sendly/node';

// Initialize with your API key
const sendly = new Sendly('sk_live_v1_your_api_key');

// Send an SMS
const message = await sendly.messages.send({
  to: '+15551234567',
  text: 'Hello from Sendly!'
});

console.log(`Message sent: ${message.id}`);
console.log(`Status: ${message.status}`);

Prerequisites for Live Messaging

Before sending live SMS messages, you need:

  1. Business Verification - Complete verification in the Sendly dashboard

    • International: Instant approval (just provide Sender ID)
    • US/Canada: Requires carrier approval (3-7 business days)
  2. Credits - Add credits to your account

    • Test keys (sk_test_*) work without credits (sandbox mode)
    • Live keys (sk_live_*) require credits for each message
  3. Live API Key - Generate after verification + credits

    • Dashboard → API Keys → Create Live Key

Test vs Live Keys

Key Type Prefix Credits Required Verification Required Use Case
Test sk_test_v1_* No No Development, testing
Live sk_live_v1_* Yes Yes Production messaging

Note: You can start development immediately with a test key. Messages to sandbox test numbers are free and don't require verification.

Features

  • ✅ Full TypeScript support with exported types
  • ✅ Automatic retries with exponential backoff
  • ✅ Rate limit handling (respects Retry-After)
  • ✅ Promise-based async/await API
  • ✅ ESM and CommonJS support
  • ✅ Zero runtime dependencies

Usage

Sending Messages

import Sendly from '@sendly/node';

const sendly = new Sendly('sk_live_v1_xxx');

// Basic usage (marketing message - default)
const message = await sendly.messages.send({
  to: '+15551234567',
  text: 'Check out our new features!'
});

// Transactional message (bypasses quiet hours)
const message = await sendly.messages.send({
  to: '+15551234567',
  text: 'Your verification code is: 123456',
  messageType: 'transactional'
});

// With custom sender ID (international)
const message = await sendly.messages.send({
  to: '+447700900123',
  text: 'Hello from MyApp!',
  from: 'MYAPP'
});

Listing Messages

// Get recent messages (default limit: 50)
const { data: messages, count } = await sendly.messages.list();

// Get last 10 messages
const { data: messages } = await sendly.messages.list({ limit: 10 });

// Iterate through messages
for (const msg of messages) {
  console.log(`${msg.to}: ${msg.status}`);
}

Getting a Message

const message = await sendly.messages.get('msg_xxx');

console.log(`Status: ${message.status}`);
console.log(`Delivered: ${message.deliveredAt}`);

Scheduling Messages

// Schedule a message for future delivery
const scheduled = await sendly.messages.schedule({
  to: '+15551234567',
  text: 'Your appointment is tomorrow!',
  scheduledAt: '2025-01-15T10:00:00Z'
});

console.log(`Scheduled: ${scheduled.id}`);
console.log(`Will send at: ${scheduled.scheduledAt}`);

// List scheduled messages
const { data: scheduledMessages } = await sendly.messages.listScheduled();

// Get a specific scheduled message
const msg = await sendly.messages.getScheduled('sched_xxx');

// Cancel a scheduled message (refunds credits)
const result = await sendly.messages.cancelScheduled('sched_xxx');
console.log(`Refunded: ${result.creditsRefunded} credits`);

Batch Messages

// Send multiple messages in one API call (up to 1000)
const batch = await sendly.messages.sendBatch({
  messages: [
    { to: '+15551234567', text: 'Hello User 1!' },
    { to: '+15559876543', text: 'Hello User 2!' },
    { to: '+15551112222', text: 'Hello User 3!' }
  ]
});

console.log(`Batch ID: ${batch.batchId}`);
console.log(`Queued: ${batch.queued}`);
console.log(`Failed: ${batch.failed}`);
console.log(`Credits used: ${batch.creditsUsed}`);

// Get batch status
const status = await sendly.messages.getBatch('batch_xxx');

// List all batches
const { data: batches } = await sendly.messages.listBatches();

// Preview batch (dry run) - validates without sending
const preview = await sendly.messages.previewBatch({
  messages: [
    { to: '+15551234567', text: 'Hello User 1!' },
    { to: '+447700900123', text: 'Hello UK!' }
  ]
});
console.log(`Credits needed: ${preview.creditsNeeded}`);
console.log(`Will send: ${preview.willSend}, Blocked: ${preview.blocked}`);

Rate Limit Information

// After any API call, you can check rate limit status
await sendly.messages.send({ to: '+1555...', text: 'Hello!' });

const rateLimit = sendly.getRateLimitInfo();
if (rateLimit) {
  console.log(`${rateLimit.remaining}/${rateLimit.limit} requests remaining`);
  console.log(`Resets in ${rateLimit.reset} seconds`);
}

Configuration

import Sendly from '@sendly/node';

const sendly = new Sendly({
  apiKey: 'sk_live_v1_xxx',
  
  // Optional: Custom base URL (for testing)
  baseUrl: 'https://sendly.live/api/v1',
  
  // Optional: Request timeout in ms (default: 30000)
  timeout: 60000,
  
  // Optional: Max retry attempts (default: 3)
  maxRetries: 5
});

Webhooks

Manage webhook endpoints to receive real-time delivery status updates.

// Create a webhook endpoint
const webhook = await sendly.webhooks.create({
  url: 'https://example.com/webhooks/sendly',
  events: ['message.delivered', 'message.failed']
});

console.log(`Webhook ID: ${webhook.id}`);
console.log(`Secret: ${webhook.secret}`); // Only returned at creation - store securely!

// List all webhooks
const webhooks = await sendly.webhooks.list();

// Get a specific webhook
const wh = await sendly.webhooks.get('whk_xxx');

// Update a webhook
await sendly.webhooks.update('whk_xxx', {
  url: 'https://new-endpoint.example.com/webhook',
  events: ['message.delivered', 'message.failed', 'message.sent']
});

// Test a webhook (sends a test event)
const testResult = await sendly.webhooks.test('whk_xxx');
console.log(`Test ${testResult.success ? 'passed' : 'failed'}`);

// Rotate webhook secret
const rotation = await sendly.webhooks.rotateSecret('whk_xxx');
console.log(`New secret: ${rotation.secret}`);

// View delivery history
const deliveries = await sendly.webhooks.getDeliveries('whk_xxx');

// Retry a failed delivery
await sendly.webhooks.retryDelivery('whk_xxx', 'del_yyy');

// Delete a webhook
await sendly.webhooks.delete('whk_xxx');

Verifying Webhook Signatures

import { Webhooks } from '@sendly/node';

const webhooks = new Webhooks('your_webhook_secret');

// In your webhook handler
app.post('/webhooks/sendly', (req, res) => {
  const signature = req.headers['x-sendly-signature'];
  const timestamp = req.headers['x-sendly-timestamp'];
  const payload = req.body;

  try {
    const event = webhooks.parse(payload, signature, timestamp);
    
    switch (event.type) {
      case 'message.delivered':
        console.log(`Message ${event.data.id} delivered`);
        break;
      case 'message.failed':
        console.log(`Message ${event.data.id} failed: ${event.data.errorCode}`);
        break;
    }
    
    res.status(200).send('OK');
  } catch (error) {
    console.error('Invalid signature');
    res.status(400).send('Invalid signature');
  }
});

Account & Credits

// Get account information
const account = await sendly.account.get();
console.log(`Email: ${account.email}`);

// Check credit balance
const credits = await sendly.account.getCredits();
console.log(`Available: ${credits.availableBalance} credits`);
console.log(`Reserved (scheduled): ${credits.reservedBalance} credits`);
console.log(`Total: ${credits.balance} credits`);

// View credit transaction history
const transactions = await sendly.account.getCreditTransactions();
for (const tx of transactions) {
  console.log(`${tx.type}: ${tx.amount} credits - ${tx.description}`);
}

// List API keys
const keys = await sendly.account.listApiKeys();
for (const key of keys) {
  console.log(`${key.name}: ${key.prefix}*** (${key.type})`);
}

// Get API key usage stats
const usage = await sendly.account.getApiKeyUsage('key_xxx');
console.log(`Messages sent: ${usage.messagesSent}`);
console.log(`Credits used: ${usage.creditsUsed}`);

// Create a new API key
const { apiKey, key } = await sendly.account.createApiKey('Production Key');
console.log(`New key: ${key}`); // Only shown once!
console.log(`Key ID: ${apiKey.id}`);

// Revoke an API key
await sendly.account.revokeApiKey('key_xxx');

Error Handling

The SDK provides typed error classes for different error scenarios:

import Sendly, {
  SendlyError,
  AuthenticationError,
  RateLimitError,
  InsufficientCreditsError,
  ValidationError,
  NotFoundError
} from '@sendly/node';

const sendly = new Sendly('sk_live_v1_xxx');

try {
  await sendly.messages.send({
    to: '+15551234567',
    text: 'Hello!'
  });
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.error('Invalid API key:', error.message);
  } else if (error instanceof RateLimitError) {
    console.error(`Rate limited. Retry after ${error.retryAfter} seconds`);
  } else if (error instanceof InsufficientCreditsError) {
    console.error(`Not enough credits. Need ${error.creditsNeeded}, have ${error.currentBalance}`);
  } else if (error instanceof ValidationError) {
    console.error('Invalid request:', error.message);
  } else if (error instanceof NotFoundError) {
    console.error('Resource not found:', error.message);
  } else if (error instanceof SendlyError) {
    console.error(`API error [${error.code}]:`, error.message);
  } else {
    throw error;
  }
}

Testing (Sandbox Mode)

Use a test API key (sk_test_v1_xxx) to test without sending real messages:

import Sendly, { SANDBOX_TEST_NUMBERS } from '@sendly/node';

const sendly = new Sendly('sk_test_v1_xxx');

// Check if in test mode
console.log(sendly.isTestMode()); // true

// Use sandbox test numbers
await sendly.messages.send({
  to: SANDBOX_TEST_NUMBERS.SUCCESS,  // +15005550000 - Always succeeds
  text: 'Test message'
});

await sendly.messages.send({
  to: SANDBOX_TEST_NUMBERS.INVALID,  // +15005550001 - Returns invalid_number error
  text: 'Test message'
});

Available Test Numbers

Number Behavior
+15005550000 Success (instant)
+15005550001 Fails: invalid_number
+15005550002 Fails: unroutable_destination
+15005550003 Fails: queue_full
+15005550004 Fails: rate_limit_exceeded
+15005550006 Fails: carrier_violation

Pricing Tiers

import { CREDITS_PER_SMS, SUPPORTED_COUNTRIES } from '@sendly/node';

// Credits per SMS by tier
console.log(CREDITS_PER_SMS.domestic); // 2 (US/Canada)
console.log(CREDITS_PER_SMS.tier1);    // 8 (UK, Poland, India, etc.)
console.log(CREDITS_PER_SMS.tier2);    // 12 (France, Japan, Australia, etc.)
console.log(CREDITS_PER_SMS.tier3);    // 16 (Germany, Italy, Mexico, etc.)

// Supported countries by tier
console.log(SUPPORTED_COUNTRIES.domestic); // ['US', 'CA']
console.log(SUPPORTED_COUNTRIES.tier1);    // ['GB', 'PL', 'IN', ...]

Utilities

The SDK exports validation utilities for advanced use cases:

import {
  validatePhoneNumber,
  getCountryFromPhone,
  isCountrySupported,
  calculateSegments
} from '@sendly/node';

// Validate phone number format
validatePhoneNumber('+15551234567'); // OK
validatePhoneNumber('555-1234'); // Throws ValidationError

// Get country from phone number
getCountryFromPhone('+447700900123'); // 'GB'
getCountryFromPhone('+15551234567');  // 'US'

// Check if country is supported
isCountrySupported('GB'); // true
isCountrySupported('XX'); // false

// Calculate SMS segments
calculateSegments('Hello!'); // 1
calculateSegments('A'.repeat(200)); // 2

TypeScript

The SDK is written in TypeScript and exports all types:

import type {
  SendlyConfig,
  SendMessageRequest,
  Message,
  MessageStatus,
  ListMessagesOptions,
  MessageListResponse,
  RateLimitInfo,
  PricingTier
} from '@sendly/node';

API Reference

Sendly

Constructor

new Sendly(apiKey: string)
new Sendly(config: SendlyConfig)

Properties

  • messages - Messages resource
  • webhooks - Webhooks resource
  • account - Account resource

Methods

  • isTestMode() - Returns true if using a test API key
  • getRateLimitInfo() - Returns current rate limit info
  • getBaseUrl() - Returns configured base URL

sendly.messages

send(request: SendMessageRequest): Promise<Message>

Send an SMS message.

list(options?: ListMessagesOptions): Promise<MessageListResponse>

List sent messages.

get(id: string): Promise<Message>

Get a specific message by ID.

schedule(request: ScheduleMessageRequest): Promise<ScheduledMessage>

Schedule a message for future delivery.

listScheduled(options?: ListScheduledMessagesOptions): Promise<ScheduledMessageListResponse>

List scheduled messages.

getScheduled(id: string): Promise<ScheduledMessage>

Get a scheduled message by ID.

cancelScheduled(id: string): Promise<CancelledMessageResponse>

Cancel a scheduled message and refund credits.

sendBatch(request: BatchMessageRequest): Promise<BatchMessageResponse>

Send multiple messages in one API call.

getBatch(batchId: string): Promise<BatchMessageResponse>

Get batch status by ID.

listBatches(options?: ListBatchesOptions): Promise<BatchListResponse>

List all batches.

sendly.webhooks

create(options: CreateWebhookOptions): Promise<WebhookCreatedResponse>

Create a new webhook endpoint. The returned object includes a one-time secret.

list(): Promise<Webhook[]>

List all webhooks.

get(id: string): Promise<Webhook>

Get a webhook by ID.

update(id: string, options: UpdateWebhookOptions): Promise<Webhook>

Update a webhook.

delete(id: string): Promise<void>

Delete a webhook.

test(id: string): Promise<WebhookTestResult>

Send a test event to a webhook.

rotateSecret(id: string): Promise<WebhookSecretRotation>

Rotate webhook secret.

getDeliveries(id: string): Promise<WebhookDelivery[]>

Get delivery history for a webhook.

retryDelivery(webhookId: string, deliveryId: string): Promise<void>

Retry a failed delivery.

sendly.account

get(): Promise<Account>

Get account information.

getCredits(): Promise<Credits>

Get credit balance.

getCreditTransactions(options?: { limit?: number; offset?: number }): Promise<CreditTransaction[]>

Get credit transaction history.

listApiKeys(): Promise<ApiKey[]>

List API keys.

getApiKey(id: string): Promise<ApiKey>

Get an API key by ID.

getApiKeyUsage(id: string): Promise<ApiKeyUsage>

Get usage statistics for an API key.

Enterprise

The Enterprise API lets you programmatically manage workspaces, verification, credits, and API keys for multi-tenant platforms. Requires an enterprise master key (sk_live_v1_master_*).

Quick Provision

Create a fully configured workspace in a single call:

const client = new Sendly('sk_live_v1_master_YOUR_KEY');

// Inherit verification from an existing workspace (fastest)
const result = await client.enterprise.provision({
  name: 'Acme Insurance - Austin',
  sourceWorkspaceId: 'ws_verified',
  creditAmount: 5000,
  creditSourceWorkspaceId: 'SOURCE_WORKSPACE_ID',
  keyName: 'Production',
  keyType: 'live',
  generateOptInPage: true
});

console.log(result.workspace.id);
console.log(result.key?.key);  // shown once
console.log(result.optInPage?.url);  // hosted opt-in page

Three provisioning modes:

Mode Params Description
Inherit sourceWorkspaceId Shares toll-free number from verified workspace
Inherit + New Number sourceWorkspaceId + inheritWithNewNumber: true Copies business info, purchases new number
Fresh verification: { ... } Full business details, new number + carrier approval

Workspace Management

// Create
const ws = await client.enterprise.workspaces.create({ name: 'Acme Insurance' });

// List
const { workspaces } = await client.enterprise.workspaces.list();

// Get details
const detail = await client.enterprise.workspaces.get('ws_xxx');

// Delete
await client.enterprise.workspaces.delete('ws_xxx');

Verification

// Submit full verification
await client.enterprise.workspaces.submitVerification('ws_xxx', {
  businessName: 'Acme Insurance LLC',
  website: 'https://acme.com',
  entityType: 'PRIVATE_PROFIT',
  brn: '12-3456789',
  brnType: 'EIN',
  brnCountry: 'US',
  address: { street: '100 Main St', city: 'Austin', state: 'TX', zip: '78701', country: 'US' },
  contact: { firstName: 'Jane', lastName: 'Doe', email: 'jane@acme.com', phone: '+15551234567' },
  useCase: 'Policy renewal reminders',
  sampleMessages: 'Your policy renews on 3/15.'
});

// Inherit from verified workspace (shares toll-free number)
await client.enterprise.workspaces.inheritVerification('ws_new', {
  sourceWorkspaceId: 'ws_verified'
});

// Inherit + new number: use provision() with inheritWithNewNumber instead
await client.enterprise.provision({
  name: 'Acme Insurance - Austin',
  sourceWorkspaceId: 'ws_verified',
  inheritWithNewNumber: true
});

Credits & API Keys

// Transfer credits
await client.enterprise.workspaces.transferCredits('ws_dest', {
  sourceWorkspaceId: 'ws_source',
  amount: 5000
});

// Create workspace API key
const key = await client.enterprise.workspaces.createKey('ws_xxx', {
  name: 'Production',
  type: 'live'
});
console.log(key.key); // shown once

// Revoke a key
await client.enterprise.workspaces.revokeKey('ws_xxx', 'key_abc');

Webhooks & Analytics

// Set enterprise webhook
await client.enterprise.webhooks.set({ url: 'https://yourapp.com/webhooks' });

// Analytics
const overview = await client.enterprise.analytics.overview();
const messages = await client.enterprise.analytics.messages({ period: '30d' });
const delivery = await client.enterprise.analytics.delivery();

Full enterprise docs: sendly.live/docs/enterprise

Support

License

MIT