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 comprehensive 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.messages[0].id);
console.log('Status:', response.data.messages[0].status);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'] // Optional: defaults to template channels
});
console.log('Message ID:', response.data.messages[0].id);
console.log('Status:', response.data.messages[0].status);Test mode
Use testMode to validate requests without sending real messages:
const response = await client.messages.send({
to: ['+1234567890'],
template: {
id: '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
name: 'welcome'
},
testMode: true // Validates but doesn't send
});
// Response will have test data
console.log('Validation passed:', response.data.messages[0].id);Handle errors
When the library is unable to connect to the API, or if the API returns a non-success status code (i.e., 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.name); // BadRequestError
console.log(err.headers); // {server: 'nginx', ...}
} else {
throw err;
}
}Error codes are as follows:
| Status Code | Error Type |
|---|---|
| 400 | BadRequestError |
| 401 | AuthenticationError |
| 403 | PermissionDeniedError |
| 404 | NotFoundError |
| 422 | UnprocessableEntityError |
| 429 | RateLimitError |
| >=500 | InternalServerError |
| N/A | 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({
phoneNumber: '+1234567890',
});
console.log('Contact ID:', contact.data.id);
// List contacts
const contacts = await client.contacts.list({
limit: 100,
});
console.log('Total:', contacts.data.length);
// Get a contact
const contact = await client.contacts.get('contact-uuid');
// Update a contact
const updated = await client.contacts.update('contact-uuid', {
phoneNumber: '+1987654321',
});
// Delete a contact
await client.contacts.delete('contact-uuid');Templates
List and retrieve templates:
// List all templates
const templates = await client.templates.list();
for (const template of templates.data) {
console.log(`${template.name} (${template.status}): ${template.id}`);
}
// Get a specific template
const template = await client.templates.get('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.messages[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.messages[0].id,
status: response.data.messages[0].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 — 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:
messages— Message status changes (SENT,DELIVERED,READ,FAILED, …)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 | messages 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_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 === 'messages') {
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_DM_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
- Version: 0.20.0
- 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