Project Setup
Step 1: Initialize Project
mkdir twilio-bulk-sms cd twilio-bulk-sms npm init -y npm install express twilio dotenv csv-parser multer cors npm install -D typescript @types/node @types/express @types/cors @types/multer ts-node-dev
Step 2: Create TypeScript Configuration
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
Step 3: Create Environment File
# .env TWILIO_ACCOUNT_SID=your_account_sid_here TWILIO_AUTH_TOKEN=your_auth_token_here TWILIO_PHONE_NUMBER=+12345678901 PORT=3000
Main Application Code
Step 4: Create the Main Server
// src/index.ts
import express from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import multer from 'multer';
import fs from 'fs';
import csv from 'csv-parser';
import { Twilio } from 'twilio';
dotenv.config();
const app = express();
const upload = multer({ dest: 'uploads/' });
const port = process.env.PORT || 3000;
// Validate environment variables
if (!process.env.TWILIO_ACCOUNT_SID || !process.env.TWILIO_AUTH_TOKEN || !process.env.TWILIO_PHONE_NUMBER) {
console.error('Missing Twilio environment variables. Please check your .env file.');
process.exit(1);
}
// Initialize Twilio client
const twilioClient = new Twilio(
process.env.TWILIO_ACCOUNT_SID,
process.env.TWILIO_AUTH_TOKEN
);
app.use(cors());
app.use(express.json());
// Interface for CSV row
interface Contact {
phone_number: string;
name?: string;
[key: string]: string | undefined;
}
// Helper function to validate phone number
function isValidPhoneNumber(phoneNumber: string): boolean {
const phoneRegex = /^\+[1-9]\d{1,14}$/;
return phoneRegex.test(phoneNumber);
}
// Helper function to send single SMS
async function sendSMS(to: string, message: string): Promise<any> {
try {
const result = await twilioClient.messages.create({
body: message,
from: process.env.TWILIO_PHONE_NUMBER!,
to: to
});
return { success: true, sid: result.sid, to, status: result.status };
} catch (error: any) {
return {
success: false,
to,
error: error.message || 'Unknown error'
};
}
}
// Endpoint 1: Send SMS to a single number
app.post('/api/send-single', async (req, res) => {
try {
const { phone_number, message } = req.body;
if (!phone_number || !message) {
return res.status(400).json({
error: 'phone_number and message are required'
});
}
if (!isValidPhoneNumber(phone_number)) {
return res.status(400).json({
error: 'Invalid phone number format. Use E.164 format (e.g., +12345678901)'
});
}
const result = await sendSMS(phone_number, message);
if (result.success) {
res.json({
success: true,
message: 'SMS sent successfully',
data: result
});
} else {
res.status(500).json({
success: false,
error: result.error
});
}
} catch (error: any) {
res.status(500).json({
error: 'Failed to send SMS',
details: error.message
});
}
});
// Endpoint 2: Send bulk SMS from CSV
app.post('/api/send-bulk', upload.single('file'), async (req, res) => {
try {
const { message, delay = 500 } = req.body;
if (!req.file) {
return res.status(400).json({ error: 'CSV file is required' });
}
if (!message) {
return res.status(400).json({ error: 'Message is required' });
}
const filePath = req.file.path;
const contacts: Contact[] = [];
const results: any[] = [];
// Read and parse CSV file
await new Promise((resolve, reject) => {
fs.createReadStream(filePath)
.pipe(csv())
.on('data', (row) => {
if (row.phone_number) {
contacts.push(row);
}
})
.on('end', resolve)
.on('error', reject);
});
if (contacts.length === 0) {
fs.unlinkSync(filePath);
return res.status(400).json({ error: 'No valid phone numbers found in CSV' });
}
// Process each contact with delay
for (let i = 0; i < contacts.length; i++) {
const contact = contacts[i];
// Validate phone number
if (!isValidPhoneNumber(contact.phone_number)) {
results.push({
success: false,
to: contact.phone_number,
error: 'Invalid phone number format'
});
continue;
}
// Personalize message
let personalizedMessage = message;
if (contact.name) {
personalizedMessage = personalizedMessage.replace(/{name}/g, contact.name);
}
// Send SMS
const result = await sendSMS(contact.phone_number, personalizedMessage);
results.push(result);
// Add delay between messages (except for last one)
if (i < contacts.length - 1 && delay > 0) {
await new Promise(resolve => setTimeout(resolve, delay));
}
}
// Clean up uploaded file
fs.unlinkSync(filePath);
// Calculate summary
const successCount = results.filter(r => r.success).length;
const failureCount = results.filter(r => !r.success).length;
res.json({
success: true,
summary: {
total: contacts.length,
sent: successCount,
failed: failureCount
},
results: results
});
} catch (error: any) {
if (req.file) {
fs.unlinkSync(req.file.path);
}
res.status(500).json({
error: 'Failed to process bulk SMS',
details: error.message
});
}
});
// Endpoint 3: Check service health
app.get('/api/health', (req, res) => {
res.json({
status: 'running',
service: 'Twilio Bulk SMS API',
timestamp: new Date().toISOString()
});
});
// Start server
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
console.log('Available endpoints:');
console.log(` POST http://localhost:${port}/api/send-single`);
console.log(` POST http://localhost:${port}/api/send-bulk`);
console.log(` GET http://localhost:${port}/api/health`);
});
Step 5: Create a Simple HTML Test Page
<!-- test.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Twilio SMS Tester</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1, h2 {
color: #333;
}
.endpoint {
background: #f8f9fa;
padding: 15px;
margin: 15px 0;
border-left: 4px solid #007bff;
border-radius: 4px;
}
code {
background: #e9ecef;
padding: 2px 6px;
border-radius: 3px;
}
pre {
background: #2d2d2d;
color: #fff;
padding: 15px;
border-radius: 5px;
overflow-x: auto;
}
.test-section {
margin: 30px 0;
padding: 20px;
background: #fff;
border: 1px solid #ddd;
border-radius: 5px;
}
input, textarea {
width: 100%;
padding: 10px;
margin: 10px 0;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
button {
background: #007bff;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #0056b3;
}
.response {
margin-top: 15px;
padding: 15px;
background: #f8f9fa;
border-radius: 4px;
white-space: pre-wrap;
}
</style>
</head>
<body>
<div class="container">
<h1>Twilio Bulk SMS API Tester</h1>
<div class="endpoint">
<h2>API Endpoints</h2>
<p><strong>Health Check:</strong> <code>GET http://localhost:3000/api/health</code></p>
<p><strong>Single SMS:</strong> <code>POST http://localhost:3000/api/send-single</code></p>
<p><strong>Bulk SMS:</strong> <code>POST http://localhost:3000/api/send-bulk</code></p>
</div>
<div class="test-section">
<h2>Test Single SMS</h2>
<input type="text" id="phoneNumber" placeholder="Phone number (e.g., +12345678901)" value="+15005550006">
<textarea id="message" rows="4" placeholder="Enter your message">Hello, this is a test message!</textarea>
<button onclick="sendSingleSMS()">Send Single SMS</button>
<div id="singleResponse" class="response"></div>
</div>
<div class="test-section">
<h2>Test Bulk SMS</h2>
<p>Create a CSV file with this format:</p>
<pre>phone_number,name
+15005550006,John Doe
+15005550007,Jane Smith</pre>
<input type="file" id="csvFile" accept=".csv">
<textarea id="bulkMessage" rows="4" placeholder="Enter message template">Hello {name}, this is a bulk test message!</textarea>
<input type="number" id="delay" placeholder="Delay between messages (ms)" value="500">
<button onclick="sendBulkSMS()">Send Bulk SMS</button>
<div id="bulkResponse" class="response"></div>
</div>
<div class="test-section">
<h2>Sample API Requests</h2>
<h3>Single SMS (cURL)</h3>
<pre>curl -X POST http://localhost:3000/api/send-single \
-H "Content-Type: application/json" \
-d '{
"phone_number": "+15005550006",
"message": "Hello from Twilio!"
}'</pre>
<h3>Bulk SMS (cURL)</h3>
<pre>curl -X POST http://localhost:3000/api/send-bulk \
-F "[email protected]" \
-F "message=Hello {name}, this is a test message" \
-F "delay=500"</pre>
</div>
</div>
<script>
function sendSingleSMS() {
const phone = document.getElementById('phoneNumber').value;
const message = document.getElementById('message').value;
const responseDiv = document.getElementById('singleResponse');
responseDiv.innerHTML = 'Sending...';
fetch('http://localhost:3000/api/send-single', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
phone_number: phone,
message: message
})
})
.then(response => response.json())
.then(data => {
responseDiv.innerHTML = JSON.stringify(data, null, 2);
})
.catch(error => {
responseDiv.innerHTML = 'Error: ' + error.message;
});
}
function sendBulkSMS() {
const fileInput = document.getElementById('csvFile');
const message = document.getElementById('bulkMessage').value;
const delay = document.getElementById('delay').value;
const responseDiv = document.getElementById('bulkResponse');
if (!fileInput.files || fileInput.files.length === 0) {
alert('Please select a CSV file');
return;
}
responseDiv.innerHTML = 'Processing...';
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('message', message);
formData.append('delay', delay);
fetch('http://localhost:3000/api/send-bulk', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
responseDiv.innerHTML = JSON.stringify(data, null, 2);
})
.catch(error => {
responseDiv.innerHTML = 'Error: ' + error.message;
});
}
</script>
</body>
</html>
Step 6: Create Sample CSV File
# contacts.csv phone_number,name +15005550006,John Doe +15005550007,Jane Smith +15005550008,Bob Wilson
Step 7: Create Package.json Scripts
Update your package.json file:
{
"name": "twilio-bulk-sms",
"version": "1.0.0",
"main": "dist/index.js",
"scripts": {
"dev": "ts-node-dev --respawn src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"test": "echo \"Open test.html in browser\" && exit 0"
},
"dependencies": {
"express": "^4.18.2",
"twilio": "^4.19.0",
"dotenv": "^16.3.1",
"csv-parser": "^3.0.0",
"multer": "^1.4.5-lts.1",
"cors": "^2.8.5"
},
"devDependencies": {
"typescript": "^5.2.2",
"@types/node": "^20.8.0",
"@types/express": "^4.17.20",
"@types/cors": "^2.8.13",
"@types/multer": "^1.4.7",
"ts-node-dev": "^2.0.0"
}
}
Running the Application
Step 8: Start the Server
# Development mode with auto-reload npm run dev # Or build and run production npm run build npm start
You should see:
Server running on http://localhost:3000 Available endpoints: POST http://localhost:3000/api/send-single POST http://localhost:3000/api/send-bulk GET http://localhost:3000/api/health
Testing with Postman
Endpoint 1: Health Check
- Method: GET
- URL:
http://localhost:3000/api/health - Response: JSON status of the service
Endpoint 2: Send Single SMS
- Method: POST
- URL:
http://localhost:3000/api/send-single - Headers:
Content-Type: application/json - Body (raw JSON):
{
"phone_number": "+15005550006",
"message": "Hello, this is a test message!"
}
Endpoint 3: Send Bulk SMS
- Method: POST
- URL:
http://localhost:3000/api/send-bulk - Body (form-data):
- Key:
file, Value: [Select your CSV file] - Key:
message, Value:Hello {name}, this is a test! - Key:
delay, Value:500(optional, default 500ms)
Testing in Browser
- Open
test.htmlin your browser - Fill in the form fields
- Click the buttons to test the endpoints
- View responses in the result boxes
Sample API Responses
Successful Single SMS Response:
{
"success": true,
"message": "SMS sent successfully",
"data": {
"success": true,
"sid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"to": "+15005550006",
"status": "queued"
}
}
Bulk SMS Response:
{
"success": true,
"summary": {
"total": 3,
"sent": 2,
"failed": 1
},
"results": [
{
"success": true,
"sid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"to": "+15005550006",
"status": "queued"
},
{
"success": true,
"sid": "SMyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"to": "+15005550007",
"status": "queued"
},
{
"success": false,
"to": "+15005550008",
"error": "The number +15005550008 is unverified."
}
]
}
Error Handling
The API handles common errors:
- Invalid phone number format
- Missing required fields
- File upload errors
- Twilio authentication errors
- Network timeouts
Important Notes
- Use Twilio Test Numbers: For testing without charges, use:
- +15005550006 (always succeeds)
- +15005550007 (always succeeds)
- +15005550008 (always fails)
- Phone Number Format: Must be in E.164 format (e.g., +12345678901)
- Rate Limiting: The delay parameter helps avoid Twilio rate limits
- Security:
- Never commit
.envfile to version control - Use environment variables for credentials
- Validate all inputs
This solution provides a minimal, functional API for sending bulk SMS with Twilio. The code is simple, well-structured, and includes everything needed to get started quickly.

























Leave a Comment
Your email address will not be published. Required fields are marked with *