All articles

How to Send SMS with Node.js and Express Using an Android Gateway

Learn how to send SMS text messages and OTP verification codes in Node.js and Express using SharkSMS and an Android gateway without recurring per-message aggregator fees.

Send SMS with Node.js and Express

Integrating SMS capabilities into a Node.js backend typically requires third-party aggregator SDKs that charge per-message fees, setup rates, and monthly virtual number rentals. By pairing your own Android device and SIM card with SharkSMS, you can turn your Node.js application into a direct, flat-rate messaging pipeline capable of sending one-time passwords (OTP), customer alerts, and bulk notifications through a lightweight REST API.

Why Use an Android SMS Gateway with Node.js?

Traditional cloud SMS aggregators operate on a metered model ($0.0079 to $0.025+ per message fragment). For applications sending transaction receipts, booking confirmations, or multi-factor authentication codes to local users, these costs scale rapidly. With an Android gateway:

  • Flat Monthly Cost: Utilize existing carrier bundles with unlimited SMS allowances.
  • Direct REST API Integration: Standard JSON endpoints compatible with Node's native fetch or axios.
  • Two-Way Webhook Routing: Catch incoming customer replies and delivery statuses directly inside Express route handlers.
  • Local Sender Trust: Messages arrive from standard local mobile numbers, improving open rates over generic shortcodes.

Prerequisites and Environment Setup

To follow this tutorial, you will need:

  1. A working Node.js environment (v18.0.0+ recommended for native fetch support).
  2. An active SharkSMS account and an Android smartphone paired as an active gateway device.
  3. Your SharkSMS API Key (accessible under API & Integrations in your dashboard).
# Initialize your project and install Express
mkdir sms-nodejs-gateway && cd sms-nodejs-gateway
npm init -y
npm install express dotenv

Sending an SMS via Node.js (Modern Fetch API)

The code below demonstrates a standalone helper function to dispatch a text message via the SharkSMS REST API. We use environment variables for sensitive credentials:

// smsService.js
import dotenv from 'dotenv';
dotenv.config();

const SHARKSMS_API_URL = 'https://sharksms.com/api/send/sms';
const API_KEY = process.env.SHARKSMS_API_KEY;
const DEVICE_ID = process.env.SHARKSMS_DEVICE_ID; // Your paired Android device ID

/**
 * Send an SMS via SharkSMS Android Gateway
 * @param {string} phone - Recipient phone number with international dial code
 * @param {string} message - Text content of the SMS
 * @returns {Promise<object>} API response
 */
export async function sendSMS(phone, message) {
  try {
    const response = await fetch(SHARKSMS_API_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${API_KEY}`
      },
      body: JSON.stringify({
        secret: API_KEY,
        mode: 'devices',
        device: DEVICE_ID,
        phone: phone,
        message: message,
        sim: 1 // SIM slot (1 or 2 for dual-SIM devices)
      })
    });

    const result = await response.json();
    if (result.status === 200) {
      console.log(`[SMS Sent] Message successfully queued to ${phone}. Message ID: ${result.data?.id}`);
      return { success: true, data: result.data };
    } else {
      console.error(`[SMS Error] Gateway returned code ${result.status}:`, result.message);
      return { success: false, error: result.message };
    }
  } catch (error) {
    console.error('[Network Error] Failed to reach SMS gateway:', error.message);
    return { success: false, error: error.message };
  }
}

Building an Express OTP Verification Endpoint

One of the most frequent developer use cases is phone number verification. Below is an Express server implementing OTP generation, SMS dispatch, and in-memory verification:

// server.js
import express from 'express';
import { sendSMS } from './smsService.js';

const app = express();
app.use(express.json());

// In-memory OTP cache (use Redis in production)
const otpStore = new Map();

// 1. Request OTP Endpoint
app.post('/api/auth/request-otp', async (req, res) => {
  const { phone } = req.body;
  if (!phone) {
    return res.status(400).json({ error: 'Phone number is required.' });
  }

  // Generate 6-digit cryptographically secure OTP
  const code = Math.floor(100000 + Math.random() * 900000).toString();
  otpStore.set(phone, { code, expiresAt: Date.now() + 5 * 60 * 1000 }); // 5 min TTL

  const smsText = `Your verification code is: ${code}. Valid for 5 minutes.`;
  const result = await sendSMS(phone, smsText);

  if (result.success) {
    return res.json({ message: 'Verification code sent via SMS.' });
  } else {
    return res.status(500).json({ error: 'Failed to deliver SMS.' });
  }
});

// 2. Verify OTP Endpoint
app.post('/api/auth/verify-otp', (req, res) => {
  const { phone, code } = req.body;
  const record = otpStore.get(phone);

  if (!record) {
    return res.status(400).json({ error: 'No OTP requested for this phone number.' });
  }

  if (Date.now() > record.expiresAt) {
    otpStore.delete(phone);
    return res.status(400).json({ error: 'Verification code expired.' });
  }

  if (record.code !== code) {
    return res.status(400).json({ error: 'Invalid verification code.' });
  }

  // OTP is valid
  otpStore.delete(phone);
  return res.json({ status: 'verified', message: 'Phone successfully authenticated.' });
});

app.listen(3000, () => {
  console.log('Authentication server running on http://localhost:3000');
});

Handling Two-Way Inbound Webhooks in Express

SharkSMS can forward incoming SMS replies directly to your server via HTTP POST webhooks. Configure your webhook URL in your dashboard (e.g. https://api.yourdomain.com/webhooks/sms-received):

// Express Webhook Handler
app.post('/webhooks/sms-received', (req, res) => {
  const { from, message, timestamp } = req.body;
  console.log(`[Inbound SMS] From: ${from} | Text: "${message}" at ${timestamp}`);

  // Handle opt-out requests automatically
  if (message.trim().toUpperCase() === 'STOP') {
    console.log(`User ${from} requested STOP. Updating database opt-out status...`);
  }

  // Acknowledge receipt to the gateway
  res.status(200).json({ status: 'received' });
});

Throughput Best Practices for Android Gateways

When running a mobile hardware gateway, keep these production best practices in mind:

  • Rate Limiting: Mobile network carriers typically cap SMS transmission at 15 to 30 messages per minute per SIM. Implement a queue (such as BullMQ or Redis) to smooth out burst campaigns.
  • Dual-SIM Load Balancing: If your Android device supports dual SIM cards, route messages across SIM 1 and SIM 2 to double delivery capacity.
  • Keep-Alive Settings: Disable Android OS battery optimization and background sleep restrictions on your gateway device to prevent sleep timeouts.

For more architectural options and companion guides, see our web service SMS API overview, our Python SMS gateway walkthrough, and our Twilio alternative cost comparison.

All articles

Comments

No comments yet. Be the first.

Sign in to comment

Comments come from SharkSMS accounts, so you always know who you are reading. Creating one is free and takes a minute.