Back to Blog
Developer Guides

How to Send SMS with Node.js: A Step-by-Step Tutorial (2026)

Collins VidzroCollins Vidzro2026-08-089 min read
How to Send SMS with Node.js: A Step-by-Step Tutorial (2026)

How to Send SMS with Node.js: A Step-by-Step Tutorial (2026)

This tutorial walks through sending your first SMS from a Node.js application — from getting API credentials to a production-shaped Express endpoint with proper error handling. By the end, you'll have a working /api/send-sms route you can drop straight into a real project.


1. Prerequisites

You'll need:

  • Node.js 18+ installed
  • A Sendexa account — sign up at sendexa.co to get a sandbox API key and secret instantly
  • A basic Express project (or any Node.js HTTP framework — the SMS-sending logic is the same regardless)

2. Install the SDK

npm install @sendexa/sdk

or, if you're using Bun:

bun add @sendexa/sdk

3. Set Up Your API Credentials

Never hardcode API keys. Create a .env file in your project root:

SENDEXA_API_KEY=your_api_key_here
SENDEXA_API_SECRET=your_api_secret_here

Make sure .env is in your .gitignore — committing credentials to version control is one of the most common ways API keys end up leaked and abused.


4. Send Your First SMS

Create a small script to confirm everything's wired up correctly:

// send-test.js
import { Sendexa } from "@sendexa/sdk";
import "dotenv/config";

const sendexa = new Sendexa({
  apiKey: process.env.SENDEXA_API_KEY,
  apiSecret: process.env.SENDEXA_API_SECRET,
});

async function main() {
  const result = await sendexa.sms.send({
    to: "+233501234567",
    from: "SENDEXA",
    message: "Hello from my Node.js app! 🚀",
  });

  console.log(result);
}

main().catch(console.error);

Run it:

node send-test.js

If your credentials and phone number are valid, you should see a response similar to this, and a text message should land on the destination phone within a few seconds:

{
  "success": true,
  "code": "SMS_SENT",
  "message": "Message accepted for delivery.",
  "data": {
    "messageId": "MSG-8f21c4a9",
    "delivery": {
      "status": "PENDING",
      "route": { "type": "domestic", "destination": "Ghana" }
    },
    "billing": { "units": 1, "totalCost": 0.01, "currency": "USD" }
  }
}

status: "PENDING" means the message has been accepted and handed off to the carrier — final delivery confirmation arrives asynchronously (see webhooks, below).


5. Build an Express Endpoint

Now let's wrap this in a real API route your frontend can call:

// routes/sms.js
import express from "express";
import { Sendexa } from "@sendexa/sdk";

const router = express.Router();

const sendexa = new Sendexa({
  apiKey: process.env.SENDEXA_API_KEY,
  apiSecret: process.env.SENDEXA_API_SECRET,
});

router.post("/send-sms", async (req, res) => {
  const { to, message } = req.body;

  if (!to || !message) {
    return res.status(400).json({ error: "'to' and 'message' are required" });
  }

  try {
    const result = await sendexa.sms.send({
      to,
      from: "SENDEXA",
      message,
    });

    return res.status(200).json({
      success: true,
      messageId: result.data.messageId,
    });
  } catch (error) {
    console.error("SMS send failed:", error);
    return res.status(502).json({
      success: false,
      error: "Failed to send SMS. Please try again.",
    });
  }
});

export default router;

Mount it in your app:

// app.js
import express from "express";
import smsRouter from "./routes/sms.js";

const app = express();
app.use(express.json());
app.use("/api", smsRouter);

app.listen(3000, () => console.log("Server running on port 3000"));

6. Validate Phone Numbers Before Sending

Sending to a malformed number wastes a request and gives users a confusing failure. Validate the format before calling the API — E.164 (+ followed by country code and subscriber number, no spaces or dashes) is the standard to enforce:

function isValidE164(phoneNumber) {
  return /^\+[1-9]\d{7,14}$/.test(phoneNumber);
}

// In your route handler, before calling sendexa.sms.send():
if (!isValidE164(to)) {
  return res.status(400).json({ error: "Phone number must be in E.164 format, e.g. +233501234567" });
}

7. Tracking Delivery Status

Because SMS delivery is asynchronous, the initial API response only tells you the message was accepted, not that it arrived. To get the final delivery outcome, listen for delivery receipts on a webhook endpoint:

// routes/webhooks.js
import express from "express";

const router = express.Router();

router.post("/webhooks/sms-status", express.json(), (req, res) => {
  const { messageId, status } = req.body;

  console.log(`Message ${messageId} status: ${status}`);
  // status is typically one of: DELIVERED, FAILED, EXPIRED

  // TODO: update your own database record for this messageId

  res.status(200).send("OK");
});

export default router;

Register this URL in your Sendexa dashboard so delivery events get pushed to it in real time, instead of polling.


8. Handling Errors Gracefully

Common failure modes to handle explicitly rather than letting them bubble up as generic 500s:

try {
  await sendexa.sms.send({ to, from: "SENDEXA", message });
} catch (error) {
  if (error.status === 401) {
    // Invalid or expired API credentials
  } else if (error.status === 400) {
    // Malformed request — bad phone number, empty message, etc.
  } else if (error.status === 429) {
    // Rate limited — back off and retry
  } else {
    // Network error, upstream outage, etc.
  }
}

For anything user-facing, don't leak raw API error details to the client — log them server-side and return a clean, generic message.


Conclusion

You now have a working SMS endpoint: credentials loaded from environment variables, input validation, a clean Express route, and a webhook to track delivery status past the initial "accepted" response. From here, the same pattern extends to OTP verification, order notifications, and any other transactional message your app needs to send — swap the message string and you're done.

#SMS API#Node.js#Tutorial#Integration
Collins Vidzro

Collins Vidzro

Founder & Lead Developer at Sendexa, writing about high-throughput communication APIs, security, and digital inclusion.

Share: