Skip to main content

📦 Node.js SDK

The official Sendly Node.js SDK provides a TypeScript-first, developer-friendly interface for the Sendly SMS API. Featuring automatic retries, comprehensive error handling, and full TypeScript support.

Installation

npm install @sendly/node

Quick Start

Basic Usage
import { SendlyClient } from '@sendly/node';

const sendly = new SendlyClient({
apiKey: 'sl_live_YOUR_API_KEY'
});

// Send your first SMS
const result = await sendly.sms.send({
to: '+14155552671',
text: 'Hello from Sendly! 🚀'
});

console.log(`Message sent: ${result.messageId}`);

TypeScript Support

The SDK is built with TypeScript and includes complete type definitions:

TypeScript Example
import { SendlyClient, SMSResponse, SMSRequest } from '@sendly/node';

const sendly = new SendlyClient({
apiKey: process.env.SENDLY_API_KEY!
});

const request: SMSRequest = {
to: '+14155552671',
text: 'Hello from TypeScript!',
messageType: 'transactional'
};

const response: SMSResponse = await sendly.sms.send(request);

API Reference

SendlyClient

The main client class for interacting with the Sendly API.

Constructor

new SendlyClient(options: SendlyClientOptions)

Parameters:

ParameterTypeRequiredDescription
apiKeystringYour Sendly API key
baseUrlstringAPI base URL (default: https://sendly.live/api)
timeoutnumberRequest timeout in ms (default: 30000)
maxRetriesnumberMax retry attempts (default: 3)
userAgentstringCustom user agent

Example:

Basic Configuration
const sendly = new SendlyClient({
apiKey: 'sl_live_YOUR_API_KEY'
});

SMS Methods

sendly.sms.send()

Send an SMS or MMS message.

send(request: SMSRequest): Promise<SMSResponse>

Parameters:

ParameterTypeRequiredDescription
tostringDestination phone number (E.164 format)
textstring❌*Message text
fromstringSender phone number (auto-selected if not provided)
messageTypestringMessage type for routing priority
mediaUrlsstring[]HTTPS URLs for MMS media (max 10)
subjectstringMMS subject line
webhookUrlstringHTTPS webhook URL for delivery notifications
webhookFailoverUrlstringHTTPS backup webhook URL
tagsstring[]Message tags for analytics (max 20, 50 chars each)

*Either text or mediaUrls must be provided.

Message Types:

  • transactional - Order confirmations, receipts, account notifications (highest priority)
  • otp - One-time passwords, verification codes (high priority)
  • marketing - Promotional messages, newsletters (standard routing)
  • alert - System alerts, emergency notifications (priority routing)
  • promotional - Sales, offers, announcements (standard routing)

Examples:

Send Basic SMS
const result = await sendly.sms.send({
to: '+14155552671',
text: 'Hello from Sendly!'
});

console.log('Message ID:', result.messageId);
console.log('Status:', result.status);
console.log('Cost:', `$${result.cost.amount} ${result.cost.currency}`);

sendly.sms.get()

Retrieve message status and details.

get(messageId: string): Promise<SMSResponse>

Example:

Get Message Status
const message = await sendly.sms.get('msg_2cY8vH8LkQx3J5oP');

console.log('Status:', message.status);
console.log('Delivered at:', message.deliveredAt);
console.log('Delivery attempts:', message.deliveryAttempts);

sendly.sms.list()

List messages with filtering and pagination.

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

Parameters:

ParameterTypeDescription
limitnumberNumber of messages to return (max 100)
cursorstringCursor for pagination
statusstringFilter by message status
tostringFilter by destination number
fromstringFilter by sender number
dateFromstringFilter messages from date (ISO 8601)
dateTostringFilter messages to date (ISO 8601)

Example:

List Recent Messages
const messages = await sendly.sms.list({
limit: 50,
status: 'delivered',
dateFrom: '2023-10-01T00:00:00Z'
});

for (const message of messages.data) {
console.log(`${message.messageId}: ${message.status}`);
}

// Handle pagination
if (messages.pagination.hasMore) {
const nextPage = await sendly.sms.list({
cursor: messages.pagination.nextCursor
});
}

Response Types

SMSResponse

SMSResponse Interface
interface SMSResponse {
messageId: string; // Unique message identifier
status: MessageStatus; // Message status
from: string; // Sender phone number
to: string; // Destination phone number
text?: string; // Message text
createdAt: string; // ISO 8601 timestamp
updatedAt: string; // ISO 8601 timestamp
deliveredAt?: string; // ISO 8601 timestamp
failedAt?: string; // ISO 8601 timestamp
segments: number; // Number of SMS segments
cost: CostInfo; // Cost information
direction: 'outbound'; // Message direction
routing: RoutingInfo; // Routing information
messageType?: MessageType; // Message type
mediaType?: string; // MMS media type
mediaUrls?: string[]; // MMS media URLs
subject?: string; // MMS subject
webhookUrl?: string; // Webhook URL
webhookFailoverUrl?: string; // Backup webhook URL
tags?: string[]; // Message tags
carrier?: string; // Carrier name
lineType?: string; // Line type (mobile, landline, voip)
deliveryAttempts?: number; // Number of delivery attempts
failureReason?: string; // Failure reason if failed
}

Message Status Values

StatusDescription
queuedMessage queued for delivery
sentMessage sent to carrier
deliveredMessage delivered to device
failedMessage delivery failed
undeliveredMessage not delivered (temporary)

Error Handling

The SDK provides comprehensive error handling with specific error types:

Complete Error Handling
import { 
SendlyClient,
SendlyError,
ValidationError,
AuthenticationError,
RateLimitError
} from '@sendly/node';

const sendly = new SendlyClient({
apiKey: 'sl_live_YOUR_API_KEY'
});

try {
const result = await sendly.sms.send({
to: '+14155552671',
text: 'Hello world!'
});

console.log('Success:', result.messageId);

} catch (error) {
if (error instanceof ValidationError) {
console.error('Validation error:', error.message);
console.error('Field:', error.field);

} else if (error instanceof AuthenticationError) {
console.error('Authentication error:', error.message);

} else if (error instanceof RateLimitError) {
console.error('Rate limited:', error.message);
console.error('Retry after:', error.retryAfter, 'seconds');

} else if (error instanceof SendlyError) {
console.error('API error:', error.message);
console.error('Status code:', error.statusCode);
console.error('Error code:', error.code);

} else {
console.error('Network error:', error.message);
}
}

Error Types

Error TypeHTTP CodeDescription
ValidationError400Invalid request parameters
AuthenticationError401Invalid or missing API key
RateLimitError429Rate limit exceeded
SendlyError4xx/5xxGeneral API errors
NetworkError-Network connectivity issues

Advanced Features

Automatic Retries

The SDK automatically retries failed requests with exponential backoff:

Custom Retry Configuration
const sendly = new SendlyClient({
apiKey: 'sl_live_YOUR_API_KEY',
maxRetries: 5, // Max retry attempts
retryDelay: 1000, // Base delay in ms
retryMultiplier: 2 // Exponential backoff multiplier
});

Webhook Verification

Verify webhook signatures for security:

Webhook Signature Verification
import { SendlyClient } from '@sendly/node';

// In your webhook handler
function verifyWebhook(payload, signature, secret) {
return SendlyClient.verifyWebhookSignature(payload, signature, secret);
}

// Express.js example
app.post('/webhook', (req, res) => {
const signature = req.headers['x-sendly-signature'];
const payload = JSON.stringify(req.body);

if (!verifyWebhook(payload, signature, process.env.SENDLY_WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}

const { messageId, status, to } = req.body;
console.log(`Message ${messageId} to ${to}: ${status}`);

res.status(200).send('OK');
});

Custom User Agent

Set a custom user agent for tracking:

Custom User Agent
const sendly = new SendlyClient({
apiKey: 'sl_live_YOUR_API_KEY',
userAgent: 'MyApp/1.0.0 (contact@myapp.com)'
});

Environment Configuration

Use environment variables for configuration:

.env
SENDLY_API_KEY=sl_live_YOUR_API_KEY
SENDLY_BASE_URL=https://sendly.live/api
SENDLY_WEBHOOK_SECRET=whsec_YOUR_WEBHOOK_SECRET
Environment-based Setup
import dotenv from 'dotenv';
dotenv.config();

const sendly = new SendlyClient({
apiKey: process.env.SENDLY_API_KEY,
baseUrl: process.env.SENDLY_BASE_URL
});

Examples

E-commerce Order Confirmation

Order Confirmation SMS
async function sendOrderConfirmation(orderId, customerPhone, total) {
try {
const result = await sendly.sms.send({
to: customerPhone,
text: `Order #${orderId} confirmed! Total: $${total}. Track: https://shop.com/track/${orderId}`,
messageType: 'transactional',
tags: ['order', 'confirmation', `order-${orderId}`],
webhookUrl: 'https://shop.com/webhook/sms'
});

console.log(`Order confirmation sent: ${result.messageId}`);
return result.messageId;

} catch (error) {
console.error('Failed to send order confirmation:', error.message);
throw error;
}
}

Two-Factor Authentication

2FA Code Sending
async function send2FACode(phone, code) {
try {
const result = await sendly.sms.send({
to: phone,
text: `Your verification code is: ${code}. Valid for 5 minutes.`,
messageType: 'otp',
tags: ['2fa', 'security']
});

return {
messageId: result.messageId,
cost: result.cost.amount
};

} catch (error) {
if (error instanceof RateLimitError) {
throw new Error(`Rate limited. Retry after ${error.retryAfter} seconds`);
}
throw error;
}
}

Bulk Messaging

Bulk Message Sending
async function sendBulkMessages(recipients, messageTemplate) {
const results = [];
const batchSize = 10; // Process in batches

for (let i = 0; i < recipients.length; i += batchSize) {
const batch = recipients.slice(i, i + batchSize);

const batchPromises = batch.map(async (recipient) => {
try {
const personalizedMessage = messageTemplate.replace('{name}', recipient.name);

const result = await sendly.sms.send({
to: recipient.phone,
text: personalizedMessage,
messageType: 'marketing',
tags: ['bulk', 'campaign-2023']
});

return {
phone: recipient.phone,
status: 'sent',
messageId: result.messageId
};

} catch (error) {
return {
phone: recipient.phone,
status: 'failed',
error: error.message
};
}
});

const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);

// Rate limiting: wait between batches
if (i + batchSize < recipients.length) {
await new Promise(resolve => setTimeout(resolve, 1000)); // 1 second delay
}
}

return results;
}

Testing

Using Test API Keys

Test Mode Configuration
// Use test API key for development
const sendly = new SendlyClient({
apiKey: 'sl_test_YOUR_TEST_API_KEY' // No SMS sent, no charges
});

// Test messages will return success responses but won't be delivered
const result = await sendly.sms.send({
to: '+14155552671',
text: 'Test message - not delivered'
});

console.log('Test message ID:', result.messageId); // Returns fake ID

Unit Testing with Mocks

Jest Unit Test Example
import { SendlyClient } from '@sendly/node';

jest.mock('@sendly/node');

describe('SMS Service', () => {
let mockSendly;

beforeEach(() => {
mockSendly = {
sms: {
send: jest.fn()
}
};
(SendlyClient as jest.Mock).mockImplementation(() => mockSendly);
});

test('should send SMS successfully', async () => {
const mockResponse = {
messageId: 'msg_test_123',
status: 'queued',
cost: { amount: 0.0075, currency: 'USD' }
};

mockSendly.sms.send.mockResolvedValue(mockResponse);

const sendly = new SendlyClient({ apiKey: 'test-key' });
const result = await sendly.sms.send({
to: '+14155552671',
text: 'Test message'
});

expect(result.messageId).toBe('msg_test_123');
expect(mockSendly.sms.send).toHaveBeenCalledWith({
to: '+14155552671',
text: 'Test message'
});
});
});

Migration Guide

From Direct HTTP Requests

Direct HTTP Requests
import fetch from 'node-fetch';

const response = await fetch('https://sendly.live/api/v1/send', {
method: 'POST',
headers: {
'Authorization': 'Bearer sl_live_YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: '+14155552671',
text: 'Hello world!'
})
});

const result = await response.json();

Benefits of using the SDK:

  • ✅ Automatic retries with exponential backoff
  • ✅ Type safety with TypeScript support
  • ✅ Comprehensive error handling
  • ✅ Built-in webhook signature verification
  • ✅ Rate limiting and connection pooling
  • ✅ Automatic request/response validation

Support

Next Steps