Node.js SMS API — SMSRoute lets you send and receive text messages from any Node.js application with zero SDK overhead. Make standard HTTP POST requests using axios or Node's built-in fetch, and get real-time delivery receipts delivered to your Express webhooks. This guide covers sending your first message, handling delivery callbacks, retry strategies, and testing in sandbox mode. You can integrate the SMSRoute Node.js SMS API in under 10 minutes, whether your stack uses CommonJS or ESM.

Why Use SMSRoute for Node.js SMS Delivery?

SMSRoute routes messages through direct carrier interconnects across 149 countries. Typical delivery times stay under 2 seconds for tier-1 markets — the US, UK, Germany, India, and Brazil. You pay per message with transparent real-time pricing starting at $0.004 per SMS, with no monthly minimums or hidden fees.

The API supports alphanumeric sender IDs (where local regulation permits), two-way SMS via dedicated long codes, and HMAC-signed delivery receipts. Fund your account with cryptocurrency — BTC, ETH, USDT, LTC, XMR, or SOL — and start sending without credit checks or KYC delays. For production systems, you get 99.9% API uptime and automated failover across multiple data centres.

How to Send SMS with Node.js — Step by Step

Install axios, or use Node's built-in fetch (available from Node 18 onwards). Create an HTTP client with your API key as a Bearer token, then POST structured JSON to the send endpoint. Here is a complete CommonJS example:

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 { data } = await client.post('/messages', {
      to: '+1234567890',
      from: 'MyApp',
      body: 'Hello from SMSRoute!'
    });
    console.log('Message ID:', data.id, 'Status:', data.status);
  } catch (err) {
    console.error('Send error:', err.response?.data || err.message);
  }
}

sendSms();

The same logic in ESM uses import instead of require:

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() {
  const { data } = await client.post('/messages', {
    to: '+1234567890',
    from: 'MyApp',
    body: 'Hello from SMSRoute!'
  });
  console.log('Message ID:', data.id);
}

sendSms();

The axios.create() factory returns a pre-configured client with your base URL and auth header baked into every request. This keeps your code DRY and avoids accidentally sending unauthenticated requests. Store your API key in an environment variable (SMSROUTE_API_KEY) — never hard-code it.

Send Bulk SMS from Node.js

The /messages/bulk endpoint accepts an array of up to 10,000 messages in a single POST. Each message object can carry a different recipient, sender ID, and body. The response returns an array of message objects with unique IDs that you can store and match against delivery receipts later.

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 sendBulk() {
  const { data } = await client.post('/messages/bulk', {
    messages: [
      { to: '+1234567890', from: 'MyApp', body: 'Order confirmed!' },
      { to: '+0987654321', from: 'MyApp', body: 'Your appointment is tomorrow.' },
      { to: '+5555555555', from: 'MyApp', body: 'Welcome aboard!' }
    ]
  });
  console.log('Bulk result:', data);
}

sendBulk();

Each returned message object includes id, status (typically "accepted"), price_usd, and a created_at timestamp. Store these IDs in your database so you can reconcile them against delivery receipt webhooks later. For campaigns exceeding 10,000 recipients, batch your requests into chunks of 1,000–5,000 to avoid timeouts and keep memory usage predictable.

Handle Delivery Receipts with Express Webhooks

Instead of polling the API for delivery status, configure a webhook URL in the SMSRoute dashboard. SMSRoute sends a POST request whenever a message is delivered, undelivered, or fails. Each request includes an X-Smsroute-Signature header for verification. You must preserve the raw request body (as a Buffer) before any JSON body-parser middleware runs.

const express = require('express');
const crypto = require('crypto');
require('dotenv').config();

const app = express();
const webhookSecret = process.env.SMSROUTE_WEBHOOK_SECRET;

app.use(express.raw({ type: 'application/json' }));

app.post('/webhooks/smsroute', (req, res) => {
  const signature = req.headers['x-smsroute-signature'];
  if (!signature) return res.status(403).send('Forbidden');

  const expected = crypto
    .createHmac('sha256', webhookSecret)
    .update(req.body)
    .digest('hex');

  try {
    if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
      return res.status(403).send('Forbidden');
    }
  } catch { return res.status(403).send('Forbidden'); }

  const event = JSON.parse(req.body.toString());
  console.log('Delivery event:', event.data.status);
  // event.data = { id, to, status, delivered_at, ... }

  res.json({ success: true });
});

app.listen(3000);

Deploy this endpoint (e.g. https://yourapp.com/webhooks/smsroute) and register it in the SMSRoute dashboard. The payload includes the message ID, recipient phone, current delivery status, and a timestamp. A common pitfall: if your framework parses JSON before this middleware, req.body will be an object instead of a Buffer and the HMAC verification will fail. Always register express.raw() before any other body parser.

Implement Retry Logic and Idempotency Keys

Network hiccups and rate limits (HTTP 429) are inevitable in production. Rather than wrapping every API call in retry logic manually, use an axios response interceptor. It centralises retry decisions — retry on 5xx and 429 only, never on 4xx client errors. Combine this with an Idempotency-Key header to guarantee that retries do not produce duplicate SMS charges.

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}` },
  timeout: 10000
});

let retries = 0;
client.interceptors.response.use(
  r => r,
  async err => {
    const cfg = err.config;
    if (!cfg || retries >= 3) return Promise.reject(err);
    if (err.response?.status >= 500 || err.response?.status === 429) {
      retries++;
      const delay = err.response?.headers['retry-after']
        ? parseInt(err.response.headers['retry-after']) * 1000
        : Math.pow(2, retries) * 1000;
      await new Promise(r => setTimeout(r, delay));
      return client(cfg);
    }
    return Promise.reject(err);
  }
);

async function sendWithIdempotency() {
  const key = randomUUID();
  const { data } = await client.post('/messages', {
    to: '+1234567890',
    from: 'MyApp',
    body: 'Idempotent SMS'
  }, { headers: { 'Idempotency-Key': key } });
  console.log('Sent:', data.id);
}

The Idempotency-Key header tells SMSRoute to return the same response for repeated requests with the same key, preventing duplicate SMS. Generate the key with crypto.randomUUID() and store it alongside the message ID in your database for auditing.

TypeScript Types for the Node.js SMS API

SMSRoute does not publish an npm package with built-in TypeScript types, but defining your own interfaces is straightforward. This gives you full control and editor autocomplete without depending on a third-party type package.

// types/smsroute.ts
export interface SmsRouteMessage {
  id: string;
  status: 'accepted' | 'delivered' | 'undelivered' | 'failed';
  to: string;
  from: string;
  body: string;
  segments: number;
  price_usd: number;
  created_at: string;
}

export interface SmsRouteError {
  code: string;
  message: string;
  status?: number;
}

Create a typed wrapper that returns proper response types without casting at every call site:

// services/smsroute.ts
import axios from 'axios';
import type { SmsRouteMessage, SmsRouteError } from '../types/smsroute';

export class SmsRouteClient {
  private client = axios.create({
    baseURL: 'https://api.smsroute.cc/v1',
    headers: { 'Authorization': `Bearer ${process.env.SMSROUTE_API_KEY!}` },
    timeout: 10000
  });

  async send(to: string, from: string, body: string, idempotencyKey?: string) {
    const headers: Record<string, string> = {};
    if (idempotencyKey) headers['Idempotency-Key'] = idempotencyKey;
    const { data } = await this.client.post('/messages', { to, from, body }, { headers });
    return data as SmsRouteMessage;
  }

  async sendBulk(messages: Array<{ to: string; from: string; body: string }>) {
    const { data } = await this.client.post('/messages/bulk', { messages });
    return data.messages as SmsRouteMessage[];
  }
}

Test Your Integration in Sandbox Mode

Use API keys prefixed with sk_test_ to send free test messages. The sandbox supports magic phone numbers that return specific outcomes:

Write tests that exercise every code path — successful delivery, failed delivery, and rate-limited responses — before switching to a live sk_live_ key. This confirms your retry logic, error handlers, and webhook processing all work correctly in production.

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}` }
  });
  try {
    const { data } = await client.post('/messages', {
      to: '+1234567890',
      from: 'TestApp',
      body: 'Sandbox test'
    });
    console.log('PASS:', data.status); // "delivered"
  } catch (err) {
    console.error('FAIL:', err.response?.data || err.message);
  }
}

Frequently Asked Questions

Do I need an SMSRoute npm package to send SMS from Node.js?

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. No proprietary SDK required.

Can I use Node.js fetch instead of axios with the SMS API?

Yes. Node.js 18+ includes a global fetch function compatible with SMSRoute. Axios provides interceptors, automatic retries, and timeout handling, but fetch works for simple use cases.

How do I verify SMS webhook signatures in Node.js?

Extract the X-Smsroute-Signature header, compute HMAC-SHA256 of the raw request body using your webhook 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 sent in the Idempotency-Key header. If a request times out and you retry with the same key, SMSRoute returns the original response and does not send a duplicate SMS.

How do I handle rate limits when sending SMS from Node.js?

On 429 (rate limit) or 5xx errors, read the Retry-After header and use an axios interceptor to retry with exponential backoff. Never retry on 4xx client errors.

What does the sk_test_ API key prefix do?

Sandbox keys (sk_test_) let you test integration for free without sending real SMS or charging your account. Use magic numbers like +1234567890 to simulate different delivery outcomes.

How many countries does SMSRoute support for Node.js SMS delivery?

SMSRoute routes messages to 149 countries through direct carrier interconnects, with typical delivery times under 2 seconds for tier-1 markets like the US, UK, Germany, and India.

Can I send bulk SMS from Node.js with SMSRoute?

Yes. Use the /messages/bulk endpoint to send up to 10,000 messages in a single API request. Each message can have a different recipient, sender ID, and body.

Related Guides and Resources

Explore more integration patterns and product documentation: