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.
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
fetchoraxios. - 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:
- A working Node.js environment (v18.0.0+ recommended for native
fetchsupport). - An active SharkSMS account and an Android smartphone paired as an active gateway device.
- 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.
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.