TypeScript SDK
The official TypeScript SDK for Sent provides type-safe access to the entire Sent API. Built for modern Node.js applications with native ESM and CommonJS support, automatic retries, and error handling.
Installation
npm install @sentdm/sentdmyarn add @sentdm/sentdmpnpm add @sentdm/sentdmbun add @sentdm/sentdmQuick Start
Initialize the client
import SentDm from '@sentdm/sentdm';
const client = new SentDm(); // Uses SENT_DM_API_KEY env var by defaultSend your first message
const response = await client.messages.send({
to: ['+1234567890'],
template: {
id: '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
name: 'welcome',
parameters: {
name: 'John Doe'
}
}
});
console.log('Message sent:', response.data.recipients[0].message_id);
console.log('Status:', response.data.status);New to Sent? Follow Your first Sent integration with the TypeScript SDK, a guided tutorial that takes you from an empty project to a sandbox send, delivery webhooks on your own machine, and a status lookup.
Authentication
The client can be configured using environment variables or explicitly:
import SentDm from '@sentdm/sentdm';
// Using environment variables (recommended)
// SENT_DM_API_KEY=your_api_key
const client = new SentDm();
// Or explicit configuration
const client = new SentDm({
apiKey: 'your_api_key',
});Send Messages
Send a message
const response = await client.messages.send({
to: ['+1234567890'],
template: {
id: '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
name: 'welcome',
parameters: {
name: 'John Doe',
order_id: '12345'
}
},
channel: ['whatsapp', 'sms', 'rcs'] // Optional. Defaults to ["sent"], which auto-detects the channel per recipient
});
console.log('Message ID:', response.data.recipients[0].message_id);
console.log('Status:', response.data.status);Sandbox mode
Use sandbox: true to validate requests without sending real messages:
const response = await client.messages.send({
to: ['+1234567890'],
template: {
id: '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
name: 'welcome'
},
sandbox: true // Validates but doesn't send
});
// Response will have test data
console.log('Validation passed:', response.data.recipients[0].message_id);Check message status
Retrieve the current status of a sent message. The direction field indicates whether the message is "OUTBOUND" (sent by you) or "INBOUND" (a reply or opt-out keyword received from an end user):
const status = await client.messages.retrieveStatus('msg-uuid');
console.log('Status:', status.data.status); // e.g. "DELIVERED"
console.log('Channel:', status.data.channel); // e.g. "sms"
console.log('Direction:', status.data.direction); // "OUTBOUND" | "INBOUND"Message activities
Retrieve the full activity log for a message, useful for auditing delivery attempts across carriers:
const activities = await client.messages.retrieveActivities('msg-uuid');
for (const activity of activities.data.activities) {
console.log(`${activity.timestamp}: ${activity.status} via ${activity.from}`);
console.log(' Price:', activity.price);
console.log(' Active contact price:', activity.active_contact_price);
}Numbers
Look up carrier and line-type information for any phone number before sending:
const result = await client.numbers.lookup('+12025551234');
console.log('Valid:', result.data.is_valid);
console.log('Carrier:', result.data.carrier_name);
console.log('Line type:', result.data.line_type); // "mobile", "landline", "voip"
console.log('VoIP:', result.data.is_voip);Handle errors
When the library is unable to connect to the API, or if the API returns a non-success status code (that is, 4xx or 5xx response), a subclass of APIError will be thrown:
try {
const response = await client.messages.send({
to: ['+1234567890'],
template: {
id: '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
name: 'welcome'
}
});
} catch (err) {
if (err instanceof SentDm.APIError) {
console.log(err.status); // 400
console.log(err.constructor.name); // BadRequestError
console.log(err.headers); // {server: 'nginx', ...}
} else {
throw err;
}
}Error codes are as follows:
| Cause | Error Type |
|---|---|
| HTTP 400 | BadRequestError |
| HTTP 401 | AuthenticationError |
| HTTP 403 | PermissionDeniedError |
| HTTP 404 | NotFoundError |
| HTTP 409 | ConflictError |
| HTTP 422 | UnprocessableEntityError |
| HTTP 429 | RateLimitError |
| HTTP >= 500 | InternalServerError |
| Timeout | APIConnectionTimeoutError |
| Network error | APIConnectionError |
Retries
Certain errors will be automatically retried 2 times by default, with a short exponential backoff. Connection errors, 408 Request Timeout, 409 Conflict, 429 Rate Limit, and >=500 Internal errors will all be retried by default.
// Configure the default for all requests:
const client = new SentDm({
maxRetries: 0, // default is 2
});
// Or, configure per-request:
await client.messages.send({
to: ['+1234567890'],
template: { id: 'welcome-template', name: 'welcome' }
}, {
maxRetries: 5,
});Timeouts
Requests time out after 1 minute by default. You can configure this with a timeout option:
// Configure the default for all requests:
const client = new SentDm({
timeout: 20 * 1000, // 20 seconds (default is 1 minute)
});
// Override per-request:
await client.messages.send({
to: ['+1234567890'],
template: { id: 'welcome-template', name: 'welcome' }
}, {
timeout: 5 * 1000,
});Contacts
Create and manage contacts:
// Create a contact
const contact = await client.contacts.create({
phone_number: '+1234567890',
});
console.log('Contact ID:', contact.data.id);
// List contacts
const contacts = await client.contacts.list({
page: 1,
page_size: 100,
});
console.log('Total:', contacts.data.contacts.length);
// Get a contact
const retrieved = await client.contacts.retrieve('contact-uuid');
// Update a contact
const updated = await client.contacts.update('contact-uuid', {
default_channel: 'whatsapp',
});
// Delete a contact
await client.contacts.delete('contact-uuid', {});Templates
List and retrieve templates:
// List all templates
const templates = await client.templates.list({ page: 1, page_size: 100 });
for (const template of templates.data.templates) {
console.log(`${template.name} (${template.status}): ${template.id}`);
}
// Get a specific template
const template = await client.templates.retrieve('template-uuid');
console.log('Template name:', template.data.name);
console.log('Status:', template.data.status);Framework Integration
NestJS
See the NestJS Integration guide for complete dependency injection, module setup, and testing examples.
// messages/messages.service.ts
import { Injectable, Inject } from '@nestjs/common';
import SentDm from '@sentdm/sentdm';
import { SENT_CLIENT } from '../sent/sent.module';
@Injectable()
export class MessagesService {
constructor(
@Inject(SENT_CLIENT) private readonly sentClient: SentDm,
) {}
async sendWelcomeMessage(phoneNumber: string, name: string) {
const response = await this.sentClient.messages.send({
to: [phoneNumber],
template: {
id: 'welcome-template',
name: 'welcome',
parameters: { name }
}
});
return response.data.recipients[0];
}
}Next.js (App Router)
See the Next.js Integration guide for complete Server Actions, Route Handlers, and Edge Runtime examples.
// app/api/send-message/route.ts
import SentDm from '@sentdm/sentdm';
import { NextResponse } from 'next/server';
const client = new SentDm();
export async function POST(request: Request) {
const { phoneNumber, templateId, variables } = await request.json();
try {
const response = await client.messages.send({
to: [phoneNumber],
template: {
id: templateId,
name: 'welcome',
parameters: variables
}
});
return NextResponse.json({
messageId: response.data.recipients[0].message_id,
status: response.data.status,
});
} catch (error) {
if (error instanceof SentDm.APIError) {
return NextResponse.json(
{ error: error.message },
{ status: error.status }
);
}
throw error;
}
}Express.js
See the Express.js Integration guide for complete dependency injection, validation, and structured logging examples.
import express from 'express';
import SentDm from '@sentdm/sentdm';
const app = express();
const client = new SentDm();
app.use(express.json());
app.post('/send-message', async (req, res) => {
const { phoneNumber, templateId, variables } = req.body;
try {
const response = await client.messages.send({
to: [phoneNumber],
template: {
id: templateId,
name: 'welcome',
parameters: variables
}
});
res.json({
success: true,
message: response.data,
});
} catch (error) {
if (error instanceof SentDm.APIError) {
res.status(error.status).json({ error: error.message });
} else {
res.status(500).json({ error: 'Internal server error' });
}
}
});
app.listen(3000);Webhooks
Recommended pattern: Webhooks are the primary way to track message delivery, so don't poll the API. Save the message ID when you send, then update your database as webhook events arrive.
Sent delivers signed POST requests to your endpoint for every status change. Two event types exist:
message: Message status changes (QUEUED,ROUTED,SCHEDULED,SENT,DELIVERED,READ,FAILED,FILTERED,BLOCKED,RECEIVED); each fires as a sub-type (for example,message.delivered,message.filtered). Usemessage.receivedto receive inbound messages from contacts.templates: WhatsApp template approval/rejection
Every request includes these headers:
| Header | Description |
|---|---|
X-Webhook-ID | UUID of the webhook configuration |
X-Webhook-Timestamp | Unix timestamp (seconds) when the event was created |
X-Webhook-Signature | v1,{base64}: HMAC-SHA256 of {webhookId}.{timestamp}.{rawBody} |
X-Webhook-Event-Type | message (for example, message.delivered) or templates |
The signing secret (from the Sent Dashboard) has a whsec_ prefix. Strip it and base64-decode the remainder to obtain the raw HMAC key.
import express from 'express';
import { createHmac, timingSafeEqual } from 'crypto';
const app = express();
// Must use raw body — do NOT use express.json() for this route
app.post('/webhooks/sent', express.raw({ type: 'application/json' }), (req, res) => {
const payload = req.body as Buffer;
const webhookId = req.headers['x-webhook-id'] as string;
const timestamp = req.headers['x-webhook-timestamp'] as string;
const signature = req.headers['x-webhook-signature'] as string;
// 1. Verify: signed content = "{webhookId}.{timestamp}.{rawBody}"
const secret = process.env.SENT_DM_WEBHOOK_SECRET!; // "whsec_abc123..."
const keyBytes = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
const signed = `${webhookId}.${timestamp}.${payload.toString('utf8')}`;
const expected = 'v1,' + createHmac('sha256', keyBytes).update(signed).digest('base64');
if (!signature || !timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).json({ error: 'Invalid signature' });
}
// 2. Optional: reject replayed events older than 5 minutes
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return res.status(401).json({ error: 'Timestamp too old' });
}
const event = JSON.parse(payload.toString());
// 3. Handle events — update message status in your own database
if (event.field === 'message') {
if (event.event === 'message.received') {
// Inbound message from a contact
const { inbound_number, outbound_number, text, channel, received_at } = event.payload;
console.log(`Inbound ${channel} from ${inbound_number}: ${text}`);
// await db.inboundMessages.create({ from: inbound_number, to: outbound_number, text, channel, receivedAt: received_at });
} else {
// Outbound message status update
const { message_id, message_status, channel } = event.payload;
// await db.messages.update({ where: { sentId: message_id }, data: { status: message_status } })
console.log(`Message ${message_id} → ${message_status} (${channel})`);
}
}
// 4. Always return 200 quickly
res.json({ received: true });
});See the Webhooks reference for the full payload schema and all status values.
Logging
The log level can be configured via the SENT_LOG environment variable or using the logLevel client option:
import SentDm from '@sentdm/sentdm';
const client = new SentDm({
logLevel: 'debug', // Show all log messages
});Available log levels: 'debug', 'info', 'warn' (default), 'error', 'off'
Source & Issues
- Releases: GitHub Releases
- GitHub:
sentdm/sent-dm-typescript - NPM:
@sentdm/sentdm - Issues: Report a bug
Getting Help
- Documentation: API Reference
- Troubleshooting: Common Issues
- Support: email support@sent.dm with your request ID
SDK Troubleshooting
Common issues and solutions when using Sent SDKs. Error codes, debugging tips, and FAQs.
Your first Sent integration with the TypeScript SDK
Learn Sent by building a real TypeScript integration: send sandbox messages, receive delivery webhooks on your machine, and look up message status with the SDK.