@sendly/node
Official Node.js SDK for the Sendly SMS API.
Installation
npm install @sendly/node
yarn add @sendly/node
pnpm add @sendly/node
Requirements
Quick Start
import Sendly from '@sendly/node';
const sendly = new Sendly('sk_live_v1_your_api_key');
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:
-
Business Verification - Complete verification in the Sendly dashboard
- International: Instant approval (just provide Sender ID)
- US/Canada: Requires carrier approval (3-7 business days)
-
Credits - Add credits to your account
- Test keys (
sk_test_*) work without credits (sandbox mode)
- Live keys (
sk_live_*) require credits for each message
-
Live API Key - Generate after verification + credits
- Dashboard → API Keys → Create Live Key
Test vs Live Keys
| 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');
const message = await sendly.messages.send({
to: '+15551234567',
text: 'Check out our new features!'
});
const message = await sendly.messages.send({
to: '+15551234567',
text: 'Your verification code is: 123456',
messageType: 'transactional'
});
const message = await sendly.messages.send({
to: '+447700900123',
text: 'Hello from MyApp!',
from: 'MYAPP'
});
Listing Messages
const { data: messages, count } = await sendly.messages.list();
const { data: messages } = await sendly.messages.list({ limit: 10 });
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
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}`);
const { data: scheduledMessages } = await sendly.messages.listScheduled();
const msg = await sendly.messages.getScheduled('sched_xxx');
const result = await sendly.messages.cancelScheduled('sched_xxx');
console.log(`Refunded: ${result.creditsRefunded} credits`);
Batch Messages
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}`);
const status = await sendly.messages.getBatch('batch_xxx');
const { data: batches } = await sendly.messages.listBatches();
const preview = await sendly.messages.previewBatch({
messages: [
{ to: '+15551234567', text: 'Hello User 1!' },
{ to: '+447700900123', text: 'Hello UK!' }
]
});
console.log(`Total credits needed: ${preview.totalCredits}`);
console.log(`Valid: ${preview.valid}, Invalid: ${preview.invalid}`);
Rate Limit Information
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',
baseUrl: 'https://sendly.live/api/v1',
timeout: 60000,
maxRetries: 5
});
Webhooks
Manage webhook endpoints to receive real-time delivery status updates.
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}`);
const webhooks = await sendly.webhooks.list();
const wh = await sendly.webhooks.get('whk_xxx');
await sendly.webhooks.update('whk_xxx', {
url: 'https://new-endpoint.example.com/webhook',
events: ['message.delivered', 'message.failed', 'message.sent']
});
const testResult = await sendly.webhooks.test('whk_xxx');
console.log(`Test ${testResult.success ? 'passed' : 'failed'}`);
const rotation = await sendly.webhooks.rotateSecret('whk_xxx');
console.log(`New secret: ${rotation.secret}`);
const deliveries = await sendly.webhooks.getDeliveries('whk_xxx');
await sendly.webhooks.retryDelivery('whk_xxx', 'del_yyy');
await sendly.webhooks.delete('whk_xxx');
Verifying Webhook Signatures
import { Webhooks } from '@sendly/node';
const webhooks = new Webhooks('your_webhook_secret');
app.post('/webhooks/sendly', (req, res) => {
const signature = req.headers['x-sendly-signature'];
const payload = req.body;
try {
const event = webhooks.verifyAndParse(payload, signature);
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
const account = await sendly.account.get();
console.log(`Email: ${account.email}`);
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`);
const { data: transactions } = await sendly.account.getCreditTransactions();
for (const tx of transactions) {
console.log(`${tx.type}: ${tx.amount} credits - ${tx.description}`);
}
const { data: keys } = await sendly.account.listApiKeys();
for (const key of keys) {
console.log(`${key.name}: ${key.prefix}*** (${key.type})`);
}
const usage = await sendly.account.getApiKeyUsage('key_xxx');
console.log(`Messages sent: ${usage.messagesSent}`);
console.log(`Credits used: ${usage.creditsUsed}`);
const newKey = await sendly.account.createApiKey({
name: 'Production Key',
type: 'live',
scopes: ['sms:send', 'sms:read']
});
console.log(`New key: ${newKey.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');
console.log(sendly.isTestMode());
await sendly.messages.send({
to: SANDBOX_TEST_NUMBERS.SUCCESS,
text: 'Test message'
});
await sendly.messages.send({
to: SANDBOX_TEST_NUMBERS.INVALID,
text: 'Test message'
});
Available Test Numbers
+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';
console.log(CREDITS_PER_SMS.domestic);
console.log(CREDITS_PER_SMS.tier1);
console.log(CREDITS_PER_SMS.tier2);
console.log(CREDITS_PER_SMS.tier3);
console.log(SUPPORTED_COUNTRIES.domestic);
console.log(SUPPORTED_COUNTRIES.tier1);
Utilities
The SDK exports validation utilities for advanced use cases:
import {
validatePhoneNumber,
getCountryFromPhone,
isCountrySupported,
calculateSegments
} from '@sendly/node';
validatePhoneNumber('+15551234567');
validatePhoneNumber('555-1234');
getCountryFromPhone('+447700900123');
getCountryFromPhone('+15551234567');
isCountrySupported('GB');
isCountrySupported('XX');
calculateSegments('Hello!');
calculateSegments('A'.repeat(200));
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<CancelScheduledMessageResponse>
Cancel a scheduled message and refund credits.
sendBatch(request: SendBatchRequest): 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(request: CreateWebhookRequest): Promise<Webhook>
Create a new webhook endpoint.
list(): Promise<Webhook[]>
List all webhooks.
get(id: string): Promise<Webhook>
Get a webhook by ID.
update(id: string, request: UpdateWebhookRequest): 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(): Promise<CreditTransactionListResponse>
Get credit transaction history.
listApiKeys(): Promise<ApiKeyListResponse>
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');
const result = await client.enterprise.provision({
name: 'Acme Insurance - Austin',
sourceWorkspaceId: 'ws_verified',
creditAmount: 5000,
creditSourceWorkspaceId: 'ws_pool',
keyName: 'Production',
keyType: 'live',
generateOptInPage: true
});
console.log(result.workspace.id);
console.log(result.apiKey?.rawKey);
console.log(result.optInPage?.url);
Three provisioning modes:
| 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
const ws = await client.enterprise.workspaces.create({ name: 'Acme Insurance' });
const { workspaces } = await client.enterprise.workspaces.list();
const detail = await client.enterprise.workspaces.get('ws_xxx');
await client.enterprise.workspaces.delete('ws_xxx');
Verification
await client.enterprise.workspaces.submitVerification('ws_xxx', {
businessName: 'Acme Insurance LLC',
businessType: 'llc',
ein: '12-3456789',
address: '100 Main St',
city: 'Austin',
state: 'TX',
zip: '78701',
useCase: 'Policy renewal reminders',
sampleMessages: ['Your policy renews on 3/15.']
});
await client.enterprise.workspaces.inheritVerification('ws_new', {
sourceWorkspaceId: 'ws_verified'
});
await client.enterprise.workspaces.inheritVerification('ws_new', {
sourceWorkspaceId: 'ws_verified',
purchaseNewNumber: true
});
Credits & API Keys
await client.enterprise.workspaces.transferCredits('ws_dest', {
sourceWorkspaceId: 'ws_source',
amount: 5000
});
const key = await client.enterprise.workspaces.createKey('ws_xxx', {
name: 'Production',
type: 'live'
});
console.log(key.rawKey);
await client.enterprise.workspaces.revokeKey('ws_xxx', 'key_abc');
Webhooks & Analytics
await client.enterprise.webhooks.set({ url: 'https://yourapp.com/webhooks' });
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