Install — axios or fetch, no smsroute npm package needed
The foundation of smsroute integration is the HTTP client. Node.js 18+ includes a global fetch function that works out of the box, eliminating the need for third-party dependencies if you're writing simple code. However, axios has become the de facto standard in Node.js projects because it provides built-in interceptors, automatic error handling, timeout management, and a cleaner API for headers and retries. For production applications, especially those handling critical communications like two-factor authentication or order confirmations, axios's features often justify the single extra dependency.
To install axios in your project, run:
npm install axios
That's all. No smsroute-specific package is needed. The smsroute API is a standard REST interface accessible to any HTTP client. If you prefer a lighter footprint or are already using fetch in your codebase, Node's native fetch works equally well—see the examples below for both approaches.
Next, keep your API key secure by storing it in an environment variable. Create a .env file (for local development) or set SMSROUTE_API_KEY in your deployment environment:
SMSROUTE_API_KEY=sk_live_xxxxxxxxxxxxxxxxxxxxxxxx
Load this variable in your code using dotenv or process.env:
// CommonJS
require('dotenv').config();
const apiKey = process.env.SMSROUTE_API_KEY;
// ESM
import dotenv from 'dotenv';
dotenv.config();
const apiKey = process.env.SMSROUTE_API_KEY;
CommonJS vs ESM — minimal send in both
Node.js supports two module systems: CommonJS (the traditional require/module.exports) and ESM (the modern import/export standard). Both work perfectly with smsroute. Your choice depends on your project configuration and team preference. CommonJS is still the default for most Node.js projects and has broader support in legacy code, while ESM is the future and is now the default in newer frameworks like SvelteKit and some serverless environments. Understanding both patterns ensures you can integrate smsroute regardless of your setup.
Here's a minimal CommonJS example that sends a single SMS:
const axios = require('axios');
require('dotenv').config();
const client = axios.create({
baseURL: 'https://api.smsroute.cc/v1',
headers: {
'Authorization': `Bearer ${process.env.SMSROUTE_API_KEY}`
}
});
async function sendSms() {
try {
const response = await client.post('/messages', {
to: '+1234567890',
from: 'MyApp',
body: 'Hello from smsroute!'
});
console.log('SMS sent:', response.data);
} catch (error) {
console.error('Error:', error.response?.data || error.message);
}
}
sendSms();
Now the same code in ESM:
import axios from 'axios';
import dotenv from 'dotenv';
dotenv.config();
const client = axios.create({
baseURL: 'https://api.smsroute.cc/v1',
headers: {
'Authorization': `Bearer ${process.env.SMSROUTE_API_KEY}`
}
});
async function sendSms() {
try {
const response = await client.post('/messages', {
to: '+1234567890',
from: 'MyApp',
body: 'Hello from smsroute!'
});
console.log('SMS sent:', response.data);
} catch (error) {
console.error('Error:', error.response?.data || error.message);
}
}
sendSms();
The logic is identical—only the import syntax differs. The axios.create() factory function returns a pre-configured client with your base URL and authentication header baked in, so every request automatically includes the Bearer token. This pattern keeps your code DRY and prevents accidental header misconfigurations.
Batch sending with /v1/messages/bulk
When you need to send SMS to multiple recipients—such as a notification campaign, customer alert, or mass reminder—the /v1/messages/bulk endpoint is far more efficient than looping with individual /messages calls. A single bulk request can send up to 10,000 SMS in one API call, reducing latency and improving throughput. The request structure is straightforward: instead of a single to, from, body tuple, you send an array of message objects. Each message inherits common fields and can override them per-recipient if needed.
const axios = require('axios');
require('dotenv').config();
const client = axios.create({
baseURL: 'https://api.smsroute.cc/v1',
headers: {
'Authorization': `Bearer ${process.env.SMSROUTE_API_KEY}`
}
});
async function sendBulkSms() {
try {
const response = await client.post('/messages/bulk', {
messages: [
{
to: '+1234567890',
from: 'MyApp',
body: 'Order #12345 confirmed!'
},
{
to: '+0987654321',
from: 'MyApp',
body: 'Your appointment is tomorrow at 3 PM.'
},
{
to: '+5555555555',
from: 'MyApp',
body: 'Thank you for signing up!'
}
]
});
console.log('Bulk send result:', response.data);
// Returns an array of message objects with id, status, price_usd, etc.
} catch (error) {
console.error('Bulk send error:', error.response?.data || error.message);
}
}
sendBulkSms();
The response from /v1/messages/bulk is an array of message objects, one per recipient. Each includes a unique id, status (usually "accepted"), delivery price in USD, and a created_at timestamp. Store the id values in your database to later match against delivery receipt webhooks, allowing you to track which messages were successfully delivered. For campaigns involving thousands of recipients, consider batching your requests into chunks of 1,000–5,000 messages to avoid timeouts and keep memory usage reasonable.
Error handling, retries, and axios interceptors
Production systems must handle transient failures gracefully. Network hiccups, rate limits (429), and temporary server errors (5xx) are inevitable. Rather than sprinkling retry logic throughout your codebase, axios interceptors provide a centralized, reusable way to handle these cases automatically. An interceptor runs on every response and can decide whether to retry, fail fast, or transform the response. Combined with exponential backoff, this pattern makes your integration resilient without cluttering your business logic.
Here's a robust axios setup with a response interceptor that retries on 5xx errors and respects Retry-After headers on 429 responses:
const axios = require('axios');
require('dotenv').config();
const client = axios.create({
baseURL: 'https://api.smsroute.cc/v1',
headers: {
'Authorization': `Bearer ${process.env.SMSROUTE_API_KEY}`
},
timeout: 10000 // 10 second timeout
});
// Track retry attempts per request
let retryCount = 0;
const maxRetries = 3;
client.interceptors.response.use(
response => response, // Success—pass through
async error => {
const config = error.config;
// Don't retry if no config or already retried max times
if (!config || !config.url) {
return Promise.reject(error);
}
// Retry on 5xx or 429
if ((error.response?.status >= 500 || error.response?.status === 429) && retryCount < maxRetries) {
retryCount++;
// Extract Retry-After header; default to exponential backoff
let delay = error.response?.headers['retry-after'];
if (!delay) {
delay = Math.pow(2, retryCount) * 1000; // 2s, 4s, 8s
} else {
delay = parseInt(delay) * 1000;
}
console.log(`Retry ${retryCount}/${maxRetries} after ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
return client(config);
}
retryCount = 0;
return Promise.reject(error);
}
);
// Usage
async function sendWithRetries() {
try {
const response = await client.post('/messages', {
to: '+1234567890',
from: 'MyApp',
body: 'Test with retries'
});
console.log('Success:', response.data);
} catch (error) {
console.error('Final error:', error.response?.data || error.message);
}
}
sendWithRetries();
For simple one-off calls, wrap them in try/catch. The interceptor catches network errors, timeouts, and API errors; try/catch catches exceptions. Together, this two-layer approach ensures no error goes unhandled. Common gotchas: don't retry on 4xx client errors (bad request, auth failure) because retrying won't help; only retry on 5xx and 429. Also, ensure your retry logic is idempotent by using idempotency keys (explained in a later section) so duplicate SMS aren't sent.
Webhook ingestion — Express + HMAC verification
Delivery receipts tell you when your SMS reached the recipient's phone. Rather than polling the API repeatedly, smsroute sends a webhook POST to your application whenever a message is delivered, undelivered, or failed. This real-time notification is more efficient and allows you to react immediately—for example, logging a successful two-factor auth, or retrying a failed payment reminder. For security, smsroute signs each webhook with HMAC-SHA256, preventing attackers from injecting fake delivery notifications.
Setting up an Express webhook receiver requires two steps: preserve the raw request body (before JSON parsing) for signature verification, and compute the expected HMAC and compare it using a timing-safe function. Here's a complete working example:
const express = require('express');
const crypto = require('crypto');
require('dotenv').config();
const app = express();
const webhookSecret = process.env.SMSROUTE_WEBHOOK_SECRET;
// Middleware to capture raw body for HMAC verification
app.use(express.raw({ type: 'application/json' }));
app.post('/webhooks/smsroute', (req, res) => {
// Extract signature header
const signature = req.headers['x-smsroute-signature'];
if (!signature) {
console.warn('Missing signature header');
return res.status(403).send('Forbidden');
}
// Compute expected HMAC-SHA256
const expectedSignature = crypto
.createHmac('sha256', webhookSecret)
.update(req.body) // req.body is a Buffer when using express.raw()
.digest('hex');
// Timing-safe comparison prevents timing attacks
try {
const isValid = crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
if (!isValid) {
return res.status(403).send('Forbidden');
}
} catch {
return res.status(403).send('Forbidden');
}
// Parse and handle the webhook payload
const event = JSON.parse(req.body.toString());
console.log('Webhook event:', event);
// Example: event.data contains { id, to, status, delivered_at, ... }
// Update your database, trigger actions, etc.
res.status(200).json({ success: true });
});
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});
Deploy this endpoint (e.g., https://yourapp.com/webhooks/smsroute) and register it in the smsroute dashboard. The webhook payload includes the message id, recipient phone, delivery status, and timestamp. A common gotcha: many frameworks automatically parse JSON, destroying the raw body needed for HMAC verification. Using express.raw() ensures req.body remains a Buffer; if you've already parsed JSON elsewhere, you're out of luck. Always set up raw body capture before any other middleware.
Idempotency keys and delivery receipts
Idempotency keys solve a critical problem in distributed systems: if your application sends an SMS and the request times out before receiving the response, should you retry or not? Retrying naively causes duplicate SMS. With an idempotency key—a unique string you generate and send in the Idempotency-Key header—smsroute memoizes the response for that key. If you retry the same request (same key), you get back the original response, never the duplicate.
Generate idempotency keys using crypto.randomUUID():
const axios = require('axios');
const { randomUUID } = require('crypto');
require('dotenv').config();
const client = axios.create({
baseURL: 'https://api.smsroute.cc/v1',
headers: {
'Authorization': `Bearer ${process.env.SMSROUTE_API_KEY}`
}
});
async function sendWithIdempotency() {
const idempotencyKey = randomUUID();
console.log('Idempotency key:', idempotencyKey);
try {
const response = await client.post('/messages', {
to: '+1234567890',
from: 'MyApp',
body: 'Idempotent SMS'
}, {
headers: {
'Idempotency-Key': idempotencyKey
}
});
console.log('Message ID:', response.data.id);
console.log('Status:', response.data.status);
} catch (error) {
console.error('Error:', error.response?.data || error.message);
}
}
sendWithIdempotency();
The response data includes the message id and current status. For most SMS, the initial status is "accepted" (queued for delivery). Later, delivery receipts arrive via webhook with updated statuses: "delivered" (reached the phone), "undelivered" (invalid number or network issue), or "failed" (rejected by our system). By correlating the message id from the initial send response with the id in the webhook, you can update your records and trigger downstream logic. Store both the idempotency key and message id in your database for auditing and debugging.
TypeScript types for smsroute responses
TypeScript provides compile-time safety and IDE autocomplete, making it easier to work with API responses. smsroute doesn't publish an npm package with built-in types, but defining your own interfaces is straightforward and gives you full control. Here are the essential types for single and bulk sends:
// types/smsroute.ts
export interface SmsRouteMessageResponse {
id: string;
status: 'accepted' | 'sent' | 'delivered' | 'undelivered' | 'failed';
to: string;
from: string;
body: string;
segments: number;
price_usd: number;
created_at: string; // ISO 8601 timestamp
}
export interface SmsBatchResponse {
messages: SmsRouteMessageResponse[];
}
export interface SmsRouteError {
code: string;
message: string;
status?: number;
}
Now create a typed wrapper function that sends SMS and returns properly typed responses:
// services/smsroute.ts
import axios, { AxiosInstance } from 'axios';
import { SmsRouteMessageResponse, SmsBatchResponse } from '../types/smsroute';
class SmsRouteClient {
private client: AxiosInstance;
constructor(apiKey: string) {
this.client = axios.create({
baseURL: 'https://api.smsroute.cc/v1',
headers: {
'Authorization': `Bearer ${apiKey}`
},
timeout: 10000
});
}
async sendSms(
to: string,
from: string,
body: string,
idempotencyKey?: string
): Promise {
const headers: Record = {};
if (idempotencyKey) {
headers['Idempotency-Key'] = idempotencyKey;
}
const response = await this.client.post('/messages', {
to,
from,
body
}, { headers });
return response.data as SmsRouteMessageResponse;
}
async sendBatch(
messages: Array<{ to: string; from: string; body: string }>
): Promise {
const response = await this.client.post('/messages/bulk', {
messages
});
return response.data.messages as SmsRouteMessageResponse[];
}
}
export default SmsRouteClient;
Usage in your application is now type-safe:
import SmsRouteClient from './services/smsroute';
const sms = new SmsRouteClient(process.env.SMSROUTE_API_KEY!);
async function notifyUser(phoneNumber: string): Promise {
const response = await sms.sendSms(
phoneNumber,
'MyApp',
'Your code is: 123456'
);
// TypeScript knows response.id, response.status, etc. exist
console.log(`Message ${response.id} accepted at ${response.created_at}`);
console.log(`Cost: $${response.price_usd}`);
}
Testing with sandbox mode
Before sending real SMS to real users, test your integration with smsroute's sandbox mode. Sandbox API keys begin with sk_test_. They allow you to send unlimited free messages and practice error handling without spending money or bothering your users. The sandbox supports magic phone numbers that always succeed, fail, or return specific statuses, letting you test every code path.
Here are the key sandbox numbers:
- +1234567890: Always succeeds with status "delivered".
- +9999999999: Always fails with status "failed".
- +0000000000: Triggers a rate limit error (429).
Use these in your test suite to verify your retry logic, error handlers, and webhook processing. Swap your sk_test_ key for sk_live_ when you're ready for production. Once you move to production, you'll see 99.9% uptime and 99% tier-1 delivery—the same reliability that powers critical applications worldwide.
Here's a quick test to verify your setup:
const axios = require('axios');
require('dotenv').config();
async function testSandbox() {
const client = axios.create({
baseURL: 'https://api.smsroute.cc/v1',
headers: {
'Authorization': `Bearer ${process.env.SMSROUTE_API_KEY}`
}
});
console.log('Testing sandbox...');
try {
const response = await client.post('/messages', {
to: '+1234567890',
from: 'TestApp',
body: 'Sandbox test'
});
console.log('✓ Sandbox test passed:', response.data);
} catch (error) {
console.error('✗ Sandbox test failed:', error.response?.data || error.message);
}
}
testSandbox();
Frequently asked questions
Do I need an smsroute npm package?
No. smsroute exposes a standard REST API accessible via any HTTP client. Install axios (or use Node's built-in fetch) and make requests to https://api.smsroute.cc/v1. This keeps your dependencies minimal and your code portable across runtimes and frameworks.
Can I use Node.js fetch instead of axios?
Yes. Node.js 18+ includes a global fetch function that works with smsroute. Axios offers more features (interceptors, automatic retries, timeout management), but fetch is sufficient for simple use cases. Choose based on your project's complexity and existing dependencies.
What's the difference between CommonJS and ESM?
CommonJS uses require() and module.exports (traditional Node.js); ESM uses import and export (modern standard). Both work with smsroute. Use CommonJS if your project is legacy or uses Node.js < 13; use ESM for new projects and modern frameworks.
How do I verify webhook signatures?
Extract the X-Smsroute-Signature header, compute HMAC-SHA256 of the raw request body using your webhook secret, and compare using crypto.timingSafeEqual. This prevents attackers from injecting fake delivery notifications.
What is an idempotency key and why do I need one?
An idempotency key is a unique string sent in the Idempotency-Key header. If a request times out and you retry, smsroute returns the same response for the same key, preventing duplicate SMS. Use crypto.randomUUID() to generate one.
How do I handle rate limits and retries?
On 429 (rate limit) or 5xx errors, the Retry-After header tells you how long to wait. Use an axios interceptor to automatically retry with exponential backoff. Respect Retry-After; don't just retry blindly.
What does the sandbox API key prefix sk_test_ do?
Sandbox keys allow free testing without sending real SMS or charging your account. Use magic numbers like +1234567890; they always succeed. Switch to sk_live_ keys in production.
Are TypeScript types available?
smsroute doesn't publish an npm package with built-in types. Define your own interfaces (shown in the TypeScript section above) or use tools like json2ts to generate them from API response examples. Custom types integrate seamlessly with axios.
Related pages
Explore other integration guides and resources to deepen your smsroute knowledge:
- Python integration guide: Similar patterns for Django, Flask, and asyncio.
- Migrating from Twilio: Step-by-step comparison and switchover checklist.
- Two-factor authentication: Use SMS OTP for secure login flows.
- One-time passwords: Detailed OTP generation and delivery patterns.
- API reference: Full endpoint documentation and schema.
- Pricing & countries: Rates by region and payment options.
Last updated: January 2024. smsroute is a production-grade SMS API serving millions of messages daily across 149 countries. Whether you're building notifications, authentication, or alerts, we've got you covered—and at our transparent, crypto-friendly pricing, you can scale without surprises.
Do I need an smsroute npm package?
No. smsroute uses a standard REST API. Install axios (or use Node's built-in fetch) and make HTTP requests directly to https://api.smsroute.cc/v1. This approach gives you full control and keeps dependencies minimal.
Can I use Node.js fetch instead of axios?
Yes. Node.js 18+ includes a global fetch function compatible with smsroute. Axios is more feature-rich (interceptors, automatic retries, timeout handling), but fetch works for simple use cases. Choose based on your project's needs.
What's the difference between CommonJS and ESM?
CommonJS uses require() and module.exports (older Node.js standard). ESM uses import and export (modern standard, default in newer Node and all bundlers). Both work with smsroute; choose whichever matches your project setup.
How do I verify webhook signatures?
smsroute sends an X-Smsroute-Signature header with each webhook. Extract the raw request body (as bytes), compute HMAC-SHA256 using your API secret, and compare using crypto.timingSafeEqual to prevent timing attacks.
What is an idempotency key and why do I need one?
An idempotency key is a unique string you send in the Idempotency-Key header. If a request fails and you retry, smsroute returns the same response for the same key, preventing duplicate SMS. Use crypto.randomUUID() to generate one.
How do I handle rate limits and retries?
When you hit a 429 (rate limit) or 5xx error, the Retry-After header tells you how long to wait. Use an axios interceptor to automatically retry with exponential backoff. For production, consider a queue like Bull.
What does the sandbox API key prefix sk_test_ do?
Sandbox keys allow free testing without sending real SMS or charging your account. Use +1234567890 as the recipient in tests; it always succeeds. Switch to sk_live_ keys in production to send real messages.
Are TypeScript types available?
smsroute doesn't publish an npm package with built-in types. Define your own interfaces (shown in the TypeScript section) or use a tool like json2ts to generate them from API response examples. Axios works seamlessly with custom interfaces.
Do I need an smsroute npm package?
No. smsroute uses a standard REST API. Install axios (or use Node's built-in fetch) and make HTTP requests directly to https://api.smsroute.cc/v1. This approach gives you full control and keeps dependencies minimal.
Can I use Node.js fetch instead of axios?
Yes. Node.js 18+ includes a global fetch function compatible with smsroute. Axios is more feature-rich (interceptors, automatic retries, timeout handling), but fetch works for simple use cases. Choose based on your project's needs.
What's the difference between CommonJS and ESM?
CommonJS uses require() and module.exports (older Node.js standard). ESM uses import and export (modern standard, default in newer Node and all bundlers). Both work with smsroute; choose whichever matches your project setup.
How do I verify webhook signatures?
smsroute sends an X-Smsroute-Signature header with each webhook. Extract the raw request body (as bytes), compute HMAC-SHA256 using your API secret, and compare using crypto.timingSafeEqual to prevent timing attacks.
What is an idempotency key and why do I need one?
An idempotency key is a unique string you send in the Idempotency-Key header. If a request fails and you retry, smsroute returns the same response for the same key, preventing duplicate SMS. Use crypto.randomUUID() to generate one.
How do I handle rate limits and retries?
When you hit a 429 (rate limit) or 5xx error, the Retry-After header tells you how long to wait. Use an axios interceptor to automatically retry with exponential backoff. For production, consider a queue like Bull.
What does the sandbox API key prefix sk_test_ do?
Sandbox keys allow free testing without sending real SMS or charging your account. Use +1234567890 as the recipient in tests; it always succeeds. Switch to sk_live_ keys in production to send real messages.
Are TypeScript types available?
smsroute doesn't publish an npm package with built-in types. Define your own interfaces (shown in the TypeScript section) or use a tool like json2ts to generate them from API response examples. Axios works seamlessly with custom interfaces.
Do I need an smsroute npm package?
No. smsroute uses a standard REST API. Install axios (or use Node's built-in fetch) and make HTTP requests directly to https://api.smsroute.cc/v1. This approach gives you full control and keeps dependencies minimal.
Can I use Node.js fetch instead of axios?
Yes. Node.js 18+ includes a global fetch function compatible with smsroute. Axios is more feature-rich (interceptors, automatic retries, timeout handling), but fetch works for simple use cases. Choose based on your project's needs.
What's the difference between CommonJS and ESM?
CommonJS uses require() and module.exports (older Node.js standard). ESM uses import and export (modern standard, default in newer Node and all bundlers). Both work with smsroute; choose whichever matches your project setup.
How do I verify webhook signatures?
smsroute sends an X-Smsroute-Signature header with each webhook. Extract the raw request body (as bytes), compute HMAC-SHA256 using your API secret, and compare using crypto.timingSafeEqual to prevent timing attacks.
What is an idempotency key and why do I need one?
An idempotency key is a unique string you send in the Idempotency-Key header. If a request fails and you retry, smsroute returns the same response for the same key, preventing duplicate SMS. Use crypto.randomUUID() to generate one.
How do I handle rate limits and retries?
When you hit a 429 (rate limit) or 5xx error, the Retry-After header tells you how long to wait. Use an axios interceptor to automatically retry with exponential backoff. For production, consider a queue like Bull.
What does the sandbox API key prefix sk_test_ do?
Sandbox keys allow free testing without sending real SMS or charging your account. Use +1234567890 as the recipient in tests; it always succeeds. Switch to sk_live_ keys in production to send real messages.
Are TypeScript types available?
smsroute doesn't publish an npm package with built-in types. Define your own interfaces (shown in the TypeScript section) or use a tool like json2ts to generate them from API response examples. Axios works seamlessly with custom interfaces.
Do I need an smsroute npm package?
No. smsroute uses a standard REST API. Install axios (or use Node's built-in fetch) and make HTTP requests directly to https://api.smsroute.cc/v1. This approach gives you full control and keeps dependencies minimal.
Can I use Node.js fetch instead of axios?
Yes. Node.js 18+ includes a global fetch function compatible with smsroute. Axios is more feature-rich (interceptors, automatic retries, timeout handling), but fetch works for simple use cases. Choose based on your project's needs.
What's the difference between CommonJS and ESM?
CommonJS uses require() and module.exports (older Node.js standard). ESM uses import and export (modern standard, default in newer Node and all bundlers). Both work with smsroute; choose whichever matches your project setup.
How do I verify webhook signatures?
smsroute sends an X-Smsroute-Signature header with each webhook. Extract the raw request body (as bytes), compute HMAC-SHA256 using your API secret, and compare using crypto.timingSafeEqual to prevent timing attacks.
What is an idempotency key and why do I need one?
An idempotency key is a unique string you send in the Idempotency-Key header. If a request fails and you retry, smsroute returns the same response for the same key, preventing duplicate SMS. Use crypto.randomUUID() to generate one.
How do I handle rate limits and retries?
When you hit a 429 (rate limit) or 5xx error, the Retry-After header tells you how long to wait. Use an axios interceptor to automatically retry with exponential backoff. For production, consider a queue like Bull.
What does the sandbox API key prefix sk_test_ do?
Sandbox keys allow free testing without sending real SMS or charging your account. Use +1234567890 as the recipient in tests; it always succeeds. Switch to sk_live_ keys in production to send real messages.
Are TypeScript types available?
smsroute doesn't publish an npm package with built-in types. Define your own interfaces (shown in the TypeScript section) or use a tool like json2ts to generate them from API response examples. Axios works seamlessly with custom interfaces.