MessageBird Bulk SMS Broadcasting with Vue 3, Vite, and Node.js: Complete Tutorial
Build a production-ready bulk SMS and WhatsApp broadcast system with MessageBird API, Vue 3, Vite, and Node.js. Includes error handling, retry logic, and database schema examples.
This comprehensive guide shows you how to build a production-ready web application for sending bulk SMS and WhatsApp messages using the MessageBird API. You'll create a modern Vue 3 frontend with Vite for optimal performance and a secure Node.js/Express backend that handles the MessageBird integration.
This application solves the common need to send announcements, notifications, or marketing messages to multiple recipients efficiently. By the end, you'll have a functional system with recipient management, message tracking, error handling, and the foundation for scaling to thousands of messages.
What You'll Build:
Vue 3 Frontend: Modern reactive UI with form validation and real-time feedback
Node.js Backend API: Secure endpoint with input validation and MessageBird integration
Bulk Messaging Logic: Efficient batch processing with individual recipient status tracking
Error Handling: Robust retry mechanisms and detailed logging for production reliability
Technologies Used:
Vite: A modern frontend build tool providing a fast development server and optimized builds.
Vue 3: A progressive JavaScript framework for building user interfaces (Composition API).
Node.js: A JavaScript runtime for building the backend API.
Express: A minimal and flexible Node.js web application framework for the API.
MessageBird: The communication platform API used for sending SMS/WhatsApp messages.
dotenv: A module to load environment variables from a .env file.
Your MessageBird Live API Key (found in Dashboard > Developers > API access).
Basic understanding of Vue.js, Node.js, and REST APIs.
Note: MessageBird SDK v4.0.1 (last updated 2021–2022) – you may encounter limited updates or community support.
1. How to Set Up Your Vue and Node.js Project for Bulk Messaging
Set up both the frontend (Vue/Vite) and the backend (Node.js/Express) projects.
Frontend Setup (Vite + Vue)
Create Vite Project: Open your terminal and run the Vite scaffolding command. Choose vue and vue-ts or vue (JavaScript) as you prefer. This guide uses JavaScript.
Project Structure (Frontend): Vite creates a standard structure. You'll primarily work within the src directory:
Code
sent-messagebird-broadcast/
├── public/
├── src/
│ ├── assets/
│ ├── components/ # Add your form component here
│ ├── App.vue # Main App component
│ └── main.js # App entry point
├── index.html
├── package.json
└── vite.config.js
Run Development Server:
Bash
npm run dev
This starts the Vite development server, typically at http://localhost:5173. You should see the default Vue welcome page.
Backend Setup (Node.js + Express)
Create Backend Directory: Inside your main project folder (sent-messagebird-broadcast), create a directory for the backend API.
Bash
mkdir backend
cd backend
Initialize Node.js Project:
Bash
npm init -y
This creates a package.json file.
Install Backend Dependencies: Install Express for the server, dotenv for environment variables, the MessageBird SDK, and cors for handling cross-origin requests from the frontend.
Bash
npm install express dotenv @messagebird/api cors
Project Structure (Backend):
Code
sent-messagebird-broadcast/
├── backend/
│ ├── node_modules/
│ ├── .env # Store API keys here (DO NOT COMMIT)
│ ├── server.js # Your main API server file
│ └── package.json
├── src/ # Frontend code
└── ... (other frontend files)
Create .env File: Create a file named .env in the backend directory. Add your MessageBird Live API Key. Important: Add .env to your .gitignore file to avoid committing secrets.
Text
# Replace with your actual MessageBird Live API key obtained from the dashboard
MESSAGEBIRD_API_KEY=YOUR_LIVE_API_KEY_HERE
# Optional: Replace with your registered phone number (E.164 format) or Alphanumeric Sender ID
MESSAGEBIRD_ORIGINATOR=YOUR_SENDER_ID_OR_NUMBER
Create Basic Server (server.js):
JavaScript
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const messagebird = require('@messagebird/api')(process.env.MESSAGEBIRD_API_KEY);
const app = express();
const port = process.env.PORT || 3001; // Use environment port or default// Middleware
app.use(cors()); // Enable CORS for requests from frontend
app.use(express.json()); // Parse JSON request bodies// Basic health check route
app.get('/health', (req, res) => {
res.status(200).send('OK');
});
// Placeholder for our broadcast route
app.post('/api/broadcast', (req, res) => {
// Logic will go here in Section 3 & 4console.log('Received broadcast request:', req.body);
res.status(200).json({ message: 'Broadcast request received (placeholder)' });
});
// Start the server
app.listen(port, () => {
console.log(`Backend server listening on port ${port}`);
if (!process.env.MESSAGEBIRD_API_KEY) {
console.warn('WARNING: MESSAGEBIRD_API_KEY environment variable not set.');
}
if (!process.env.MESSAGEBIRD_ORIGINATOR) {
console.warn('WARNING: MESSAGEBIRD_ORIGINATOR environment variable not set. Using default.');
}
});
Run Backend Server: Open a new terminal window, navigate to the backend directory, and run:
Bash
node server.js
You should see Backend server listening on port 3001.
Why: This code uses v-model for two-way data binding between the textareas and reactive refs (recipientsInput, messageBody). The handleBroadcastSubmit function prevents default form submission, parses recipients, performs basic validation, and uses fetch to send data to your backend API. It handles loading states and displays feedback messages based on the API response.
Use the Component in App.vue: Replace the content of src/App.vue to include your new form.
If you check your browser (where the frontend npm run dev is running), you should now see the broadcast form. Submitting it will currently log a message in the backend console and show a placeholder success message in the frontend, as the backend logic isn't fully implemented yet.
3. How to Build the MessageBird API Integration Layer
Enhance the backend POST /api/broadcast endpoint to handle the request properly, validate input, and prepare for MessageBird integration.
Update server.js: Modify the /api/broadcast route handler in backend/server.js.
JavaScript
// ... (keep existing require statements and middleware setup) ...// Updated broadcast route
app.post('/api/broadcast', async (req, res) => { // Mark as asyncconst { recipients, message } = req.body;
// --- 1. Input Validation ---if (!recipients || !Array.isArray(recipients) || recipients.length === 0) {
return res.status(400).json({ success: false, error: 'Recipients array is missing or empty.' });
}
if (!message || typeof message !== 'string' || message.trim().length === 0) {
return res.status(400).json({ success: false, error: 'Message is missing or empty.' });
}
if (message.length > 1600) { // Example length limitreturn res.status(400).json({ success: false, error: 'Message exceeds maximum length of 1600 characters.' });
}
// Basic E.164 format check (can be improved with regex)const invalidRecipients = recipients.filter(r =>typeof r !== 'string' || !r.startsWith('+') || r.length < 10);
if (invalidRecipients.length > 0) {
return res.status(400).json({
success: false,
error: `Invalid recipient format found. Ensure all numbers start with '+' and are in E.164 format. Problematic entries: ${invalidRecipients.slice(0, 5).join(', ')}...`
});
}
// --- 2. Authentication/Authorization (Placeholder) ---// In a real app, you'd verify user identity/permissions here.// For now, we assume the request is authorized if it reaches here.console.log('User authorized (placeholder). Proceeding with broadcast.');
// --- 3. MessageBird Interaction (Implemented in Section 4) ---try {
console.log(`Attempting to send message "${message}" to ${recipients.length} recipients.`);
// Add MessageBird sending logic here in the next step// For now, simulate successconst results = recipients.map(r => ({ recipient: r, status: 'submitted', id: `fake_msg_id_${Math.random().toString(16).slice(2)}` }));
console.log('Placeholder: MessageBird processing would happen here.');
// --- 4. Respond to Frontend ---
res.status(200).json({
success: true,
message: `Broadcast request submitted for ${recipients.length} recipients.`,
details: results // Provide some detail back
});
} catch (error) {
console.error('Error during broadcast processing:', error);
// This catch block will handle errors from the MessageBird sending logic later
res.status(500).json({ success: false, error: 'An internal server error occurred while processing the broadcast.' });
}
});
// ... (keep existing health check route and app.listen) ...
Why: We added robust input validation checks for the recipients array and the message string, including format checks (basic E.164 check) and length limits. We added a placeholder comment for where authentication/authorization logic would go in a real-world application. The main logic block is wrapped in a try...catch to handle potential errors during processing (especially when we add MessageBird calls). We simulate a successful response structure that the frontend expects.
Testing the Endpoint: You can test this endpoint using curl or a tool like Postman.
Curl Example: (Run from your terminal)
Bash
curl -X POST http://localhost:3001/api/broadcast \
-H "Content-Type: application/json" \
-d '{
"recipients": ["+14155550100", "+14155550101"],
"message": "Hello from the API test!"
}'
Expected Response (JSON):
JSON
{"success":true,"message":"Broadcast request submitted for 2 recipients.","details":[{"recipient":"+14155550100","status":"submitted","id":"fake_msg_id_..."},{"recipient":"+14155550101","status":"submitted","id":"fake_msg_id_..."}]}
Test edge cases like sending empty recipients, an empty message, or malformed numbers to ensure the validation works.
4. How to Send Bulk SMS and WhatsApp Messages with MessageBird
Now, integrate the MessageBird SDK into your backend API to actually send the messages.
Navigate to Developers > API access (or similar path).
Find your Live API key. Copy this key.
Ensure this key is securely stored in your backend/.env file as MESSAGEBIRD_API_KEY.
Decide on your Originator (Sender ID). This can be:
A phone number you own (purchased through MessageBird or verified).
An Alphanumeric Sender ID (e.g., "MyCompany") – note that replies are not possible with Alphanumeric IDs, and they require pre-registration in some countries.
Add your chosen Originator to the backend/.env file as MESSAGEBIRD_ORIGINATOR.
Implement Sending Logic in server.js: Replace the placeholder comment in the try block of the /api/broadcast route with the MessageBird sending logic.
JavaScript
// ... (require statements, middleware, validation) ...
app.post('/api/broadcast', async (req, res) => {
const { recipients, message } = req.body;
// --- Input Validation (Keep as before) ---// ...// --- Authentication/Authorization (Keep placeholder) ---// ...const originator = process.env.MESSAGEBIRD_ORIGINATOR;
if (!originator) {
console.error('MessageBird Originator (sender ID/number) is not configured in .env');
return res.status(500).json({ success: false, error: 'Server configuration error: Missing sender originator.' });
}
try {
console.log(`Sending message via MessageBird from ${originator} to ${recipients.length} recipients.`);
const messagePromises = recipients.map(recipient => {
returnnewPromise((resolve, reject) => {
const params = {
originator: originator,
recipients: [recipient], // Send one API call per recipient for individual status trackingbody: message,
// Optional: Add type for WhatsApp, reportUrl for status updates etc.// type: 'sms', // Default is sms// reportUrl: 'YOUR_WEBHOOK_URL' // See Section 10
};
messagebird.messages.create(params, (err, response) => {
if (err) {
console.error(`Failed to send message to ${recipient}:`, err);
// Resolve even on error to not fail the entire batch, but include error statusresolve({ recipient: recipient, status: 'failed', error: err.errors || err.message || 'Unknown error' });
} else {
// Log successful submissionconsole.log(`Message submitted for ${recipient}, ID: ${response.id}`);
resolve({ recipient: recipient, status: 'submitted', id: response.id, details: response });
}
});
});
});
// Wait for all message submissions to complete (or fail individually)const results = awaitPromise.all(messagePromises);
const successfulSubmissions = results.filter(r => r.status === 'submitted').length;
const failedSubmissions = results.filter(r => r.status === 'failed').length;
console.log(`Broadcast attempt complete. Success: ${successfulSubmissions}, Failed: ${failedSubmissions}`);
// Respond with overall status and detailed results
res.status(200).json({
success: true, // Indicate the API call succeeded, even if some messages failed submissionmessage: `Broadcast submitted. ${successfulSubmissions} successful, ${failedSubmissions} failed submissions.`,
details: results
});
} catch (error) { // Catch errors during the Promise.all or other logicconsole.error('Critical error during broadcast processing:', error);
res.status(500).json({ success: false, error: 'An internal server error occurred while processing the broadcast.' });
}
});
// ... (health check, app.listen) ...
Why:
We retrieve the MESSAGEBIRD_ORIGINATOR from environment variables.
We iterate through the validated recipients array.
For each recipient, we create parameters (params) including the originator, the single recipient, and the message body. Sending one API call per recipient allows for individual status tracking and error handling, which is generally better for bulk sends than putting all recipients in one API call (though MessageBird supports that too).
We use messagebird.messages.create(params, callback) to initiate the sending.
Crucially, we wrap each messagebird.messages.create call in a Promise. This allows us to manage the asynchronous nature of these calls. The promise resolves even if the MessageBird API returns an error for a specific number, capturing the failure details. This prevents one failed number from stopping the entire batch.
Promise.all(messagePromises) waits for all the individual send attempts to complete (either successfully submitted or failed).
We collect the results array, containing the status for each recipient.
Finally, we send a 200 OK response back to the frontend, including a summary message and the detailed results array. The frontend can then interpret this data.
Restart Backend: Stop (Ctrl+C) and restart your backend server (node server.js) to apply the changes.
Test: Use the frontend application to send a message to one or two real phone numbers (including your own) that you can verify. Check your phone and the MessageBird Dashboard logs (Logging > Messages) to confirm messages are sent. Observe the backend console logs and the feedback message in the frontend.
5. How to Handle Errors and Implement Retry Logic for Bulk Messaging
Production systems need robust error handling and logging.
Backend Enhancements:
Improved Logging: Replace console.log / console.error with a more structured logger like winston or pino for production. For this guide, we'll stick to console, but highlight its limitations.
Why: Structured logging (e.g., JSON format) makes logs easier to parse, filter, and analyze with log management tools (like Datadog, Splunk, ELK stack). It allows adding context (request IDs, user IDs) to log entries.
Specific Error Handling: The current try...catch around Promise.all handles errors during the mapping or Promise.all itself. The individual promise wrappers handle errors from the MessageBird API for each message.
Distinguish between:
Submission Errors: MessageBird API rejects the request immediately (invalid number, API key error, insufficient balance). Our current code logs these per recipient.
Delivery Errors: MessageBird accepts the message, but it later fails to deliver (handset off, number blocked). These require Webhooks for reliable tracking (See Section 10).
Network/Server Errors: Problems connecting to MessageBird or internal server issues.
Retry Mechanisms (Conceptual): For transient errors (e.g., temporary network issues connecting to MessageBird), implement a retry strategy.
Strategy: Use libraries like async-retry or implement manually with exponential backoff (wait longer between retries).
Implementation Sketch (Conceptual - not added to main code for brevity):
JavaScript
// Conceptual retry logic for a single message sendconst { retry } = require('async-retry'); // npm install async-retryasyncfunctionsendMessageWithRetry(params) {
awaitretry(async bail => {
// bail is a function to call if the error is not retryablereturnnewPromise((resolve, reject) => {
messagebird.messages.create(params, (err, response) => {
if (err) {
// Decide if the error is retryable (e.g., network error, rate limit)// or permanent (e.g., invalid number, auth error)if (isRetryableError(err)) {
console.warn(`Retrying message to ${params.recipients[0]} due to: ${err.message}`);
returnreject(err); // Reject to trigger retry
} else {
console.error(`Non-retryable error for ${params.recipients[0]}:`, err);
// Bail out - don't retry, but resolve the outer promise with failurereturnbail(newError(`Permanent failure for ${params.recipients[0]}: ${err.message}`));
}
}
resolve({ /* success details */ });
});
});
}, {
retries: 3, // Number of retriesfactor: 2, // Exponential backoff factorminTimeout: 1000, // Initial delay msonRetry: (error, attempt) => {
console.log(`Attempt ${attempt} failed for ${params.recipients[0]}. Retrying...`);
}
}).catch(finalError => {
// This catch handles the case where all retries failed, or bail() was calledconsole.error(`All retries failed for ${params.recipients[0]}:`, finalError);
// Resolve the outer promise with failure statusreturnPromise.resolve({ recipient: params.recipients[0], status: 'failed', error: finalError.message });
});
}
// In the main loop, call sendMessageWithRetry instead of the direct Promise wrapper// const messagePromises = recipients.map(recipient => sendMessageWithRetry({ /* params */ }));
Why: Retries improve resilience against temporary glitches without manual intervention. Exponential backoff prevents overwhelming the service during widespread issues.
Frontend Enhancements:
Clearer Feedback: The current feedback is okay, but could be more detailed based on the details array returned from the backend.
vue
<div v-if="feedbackMessage" :class="['feedback', feedbackStatus]">
<p>{{ feedbackMessage }}</p>
<ul v-if="detailedResults.length > 0" class="detailed-results">
<li v-for="result in detailedResults" :key="result.recipient || result.id">
{{ result.recipient }}:
<strong :class="result.status === 'submitted' ? 'status-success' : 'status-error'">
{{ result.status }}
</strong>
<small v-if="result.status === 'failed'"> - {{ result.error?.message || result.error || 'Failed' }}</small>
<small v-if="result.status === 'submitted'"> - ID: {{ result.id }}</small>
</li>
</ul>
</div>
import { ref, computed } from 'vue'; // Import computed
// ... existing refs ...
const detailedResults = ref([]); // Add ref for detailed results
// Inside handleBroadcastSubmit, in the success block:
if (result.success) {
feedbackMessage.value = result.message || `Broadcast submitted successfully.`;
feedbackStatus.value = 'success';
detailedResults.value = result.details || []; // Store detailed results
} else {
throw new Error(result.error || 'An unknown error occurred on the server.');
}
// Inside handleBroadcastSubmit, in the catch block:
feedbackMessage.value = `Failed to send broadcast: ${error.message}`;
feedbackStatus.value = 'error';
detailedResults.value = []; // Clear details on error
// Inside handleBroadcastSubmit, at the beginning:
detailedResults.value = []; // Clear details before new submission
Add corresponding styles for .detailed-results, .status-success, .status-error.
Why: Shows per-recipient status, giving the user more insight into partial failures or providing message IDs for successful submissions.
6. Should You Use a Database for Bulk SMS Broadcasting?
While not strictly required for basic sending, storing data enhances the application:
Recipient Management: Store lists of recipients for reuse.
Message History: Log sent messages, their content, recipients, and status.
Audit Trails: Track who sent what and when.
Status Tracking: Update message status via webhooks.
Conceptual Schema (using Prisma as an example ORM):
Prisma
// prisma/schema.prisma
datasource db {
provider = "postgresql" // or mysql, sqlite
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
passwordHash String // Store hashed passwords only!
createdAt DateTime @default(now())
broadcasts Broadcast[]
}
model RecipientList {
id Int @id @default(autoincrement())
name String
recipients Recipient[]
createdAt DateTime @default(now())
// Optional: Link to a User if lists are user-specific
// userId Int?
// user User? @relation(fields: [userId], references: [id])
}
model Recipient {
id Int @id @default(autoincrement())
phoneNumber String @unique // E.164 format
firstName String?
lastName String?
createdAt DateTime @default(now())
lists RecipientList[] // Many-to-many relationship
broadcastMessages BroadcastMessageLog[]
}
model Broadcast {
id Int @id @default(autoincrement())
userId Int
user User @relation(fields: [userId], references: [id])
message String
status String // 'pending', 'inprogress', 'completed', 'failed'
sentAt DateTime @default(now())
createdAt DateTime @default(now())
messages BroadcastMessageLog[]
}
model BroadcastMessageLog {
id Int @id @default(autoincrement())
broadcastId Int
broadcast Broadcast @relation(fields: [broadcastId], references: [id])
recipientId Int
recipient Recipient @relation(fields: [recipientId], references: [id])
messagebirdId String? // ID from MessageBird API
status String // 'sent', 'delivered', 'failed'
error String?
sentAt DateTime @default(now())
deliveredAt DateTime?
}
Frequently Asked Questions (FAQ)
How do I send bulk SMS messages with MessageBird and Vue.js?
To send bulk SMS with MessageBird and Vue.js, create a Vue 3 frontend form to collect recipients and message content, then connect it to a Node.js/Express backend that uses the MessageBird SDK to send messages. This tutorial provides complete implementation details including error handling and status tracking.
What is the MessageBird API rate limit for bulk sending?
MessageBird's SMS API rate limits vary by account type and region. Generally, you can send multiple messages per second, but it's recommended to implement batching with delays between batches (starting with 50 messages per batch with 1-second delays) to avoid throttling. Check your MessageBird Dashboard for specific limits.
How do I format phone numbers for MessageBird SMS API?
Phone numbers must be in E.164 format, which includes the country code preceded by a plus sign (e.g., +14155552671 for US numbers, +442071838750 for UK numbers). The format is: +[country code][area code][phone number] with no spaces, dashes, or parentheses.
Can I use MessageBird to send WhatsApp messages in bulk?
Yes, MessageBird supports WhatsApp Business API for bulk messaging. You need to set up a WhatsApp Business Account, register your sender, and modify the message parameters to include type: 'whatsapp'. WhatsApp has additional requirements including message templates and opt-in consent.
What's the difference between MessageBird Live and Test API keys?
Test API keys allow you to test your integration without sending real messages or incurring charges. Live API keys are required to actually send SMS/WhatsApp messages to recipients. Test keys return mock responses, while Live keys interact with the actual MessageBird messaging infrastructure.
How do I handle failed message deliveries with MessageBird?
Implement webhook endpoints to receive delivery status callbacks from MessageBird. Set the reportUrl parameter when creating messages to receive real-time updates about message delivery status, including failures. Store these statuses in a database for tracking and potential retry logic.
Is the MessageBird Node.js SDK production-ready?
The MessageBird SDK (@messagebird/api v4.0.1) hasn't been updated since 2021-2022, which means limited ongoing support. However, it remains functional for production use. Consider implementing comprehensive error handling, testing, and monitoring to ensure reliability in your specific use case.
How much does it cost to send bulk SMS with MessageBird?
MessageBird SMS pricing varies by destination country and volume. Typical rates range from $0.04-$0.15 per SMS in North America and Europe, with different rates for international destinations. Check the MessageBird pricing page for current rates specific to your target countries.
Can I use an alphanumeric sender ID with MessageBird?
Yes, MessageBird supports alphanumeric sender IDs (up to 11 characters) like "MyCompany" for branding purposes. However, availability varies by country—the United States doesn't support alphanumeric IDs, while many European and Asian countries do. Register your sender ID in the MessageBird Dashboard before use.
How do I implement retry logic for failed MessageBird API calls?
Use libraries like async-retry with exponential backoff to retry transient errors (network timeouts, rate limits) while avoiding retries for permanent failures (invalid recipients, authentication errors). Implement logic to categorize errors and only retry those that are likely to succeed on subsequent attempts.
Frequently asked questions
Use the provided Vue frontend application to enter recipient phone numbers and your message. The app interacts with a Node.js backend that uses the MessageBird API to send the messages. Each recipient receives an individual SMS, allowing for better status tracking.
Vite is a modern frontend build tool that offers a fast development server and optimized builds. It's used in this MessageBird integration project to streamline the Vue.js frontend development process.
The Node.js backend handles secure interaction with the MessageBird API, including API key management and sending logic. It acts as an intermediary between the Vue frontend and MessageBird to enhance security and manage the complexities of bulk messaging.
Use an Alphanumeric Sender ID when you want to brand your messages with your company name. Keep in mind, replies are not possible with these IDs, and they might require pre-registration depending on the country.
The tutorial suggests a database schema for storing recipients, message logs, and user data. Although not implemented in the example, this is highly recommended for production to enable features like recipient management and tracking message status.
For the frontend, navigate to the 'sent-messagebird-broadcast' directory and run 'npm install'. For the backend, navigate to the 'backend' directory and run 'npm install express dotenv @messagebird/api cors'.
The '.env' file in the backend directory stores sensitive information like your MessageBird API key and originator. It's crucial to add this file to your '.gitignore' to prevent committing these secrets to version control.
The backend code includes error handling at both individual message and batch levels. It distinguishes between submission, delivery, and network errors, though delivery errors require webhooks for full tracking.
While the API might support longer messages, the example application sets a limit of 1600 characters per message. This limit is implemented in both the frontend and backend for consistency.
Run 'npm run dev' in the frontend project's root directory. In a separate terminal, navigate to the 'backend' directory and run 'node server.js'.
The article does not specify a limit. MessageBird's API documentation should detail limits on recipients per API call. This application sends one message per recipient, not a single message to all.
The article provides a conceptual example using the 'async-retry' library for exponential backoff. This is recommended for handling transient errors and increasing the reliability of message delivery.
Although the frontend only accepts direct input, the article suggests a database schema with RecipientList and Recipient models, indicating how list management could be implemented.
The E.164 format (+14155552671) ensures consistent and internationally recognized phone number formatting, which is essential for successful message delivery via the MessageBird API.
This comprehensive guide shows you how to build a production-ready web application for sending bulk SMS and WhatsApp messages using the MessageBird API. You'll create a modern Vue 3 frontend with Vite for optimal performance and a secure Node.js/Express backend that handles the MessageBird integration.
This application solves the common need to send announcements, notifications, or marketing messages to multiple recipients efficiently. By the end, you'll have a functional system with recipient management, message tracking, error handling, and the foundation for scaling to thousands of messages.
What You'll Build:
Technologies Used:
.envfile.System Architecture:
Prerequisites:
1. How to Set Up Your Vue and Node.js Project for Bulk Messaging
Set up both the frontend (Vue/Vite) and the backend (Node.js/Express) projects.
Frontend Setup (Vite + Vue)
Create Vite Project: Open your terminal and run the Vite scaffolding command. Choose
vueandvue-tsorvue(JavaScript) as you prefer. This guide uses JavaScript.Navigate and Install Dependencies:
cd sent-messagebird-broadcast npm installProject Structure (Frontend): Vite creates a standard structure. You'll primarily work within the
srcdirectory:Run Development Server:
This starts the Vite development server, typically at
http://localhost:5173. You should see the default Vue welcome page.Backend Setup (Node.js + Express)
Create Backend Directory: Inside your main project folder (
sent-messagebird-broadcast), create a directory for the backend API.mkdir backend cd backendInitialize Node.js Project:
This creates a
package.jsonfile.Install Backend Dependencies: Install Express for the server,
dotenvfor environment variables, the MessageBird SDK, andcorsfor handling cross-origin requests from the frontend.Project Structure (Backend):
Create
.envFile: Create a file named.envin thebackenddirectory. Add your MessageBird Live API Key. Important: Add.envto your.gitignorefile to avoid committing secrets.Create Basic Server (
server.js):require('dotenv').config(); const express = require('express'); const cors = require('cors'); const messagebird = require('@messagebird/api')(process.env.MESSAGEBIRD_API_KEY); const app = express(); const port = process.env.PORT || 3001; // Use environment port or default // Middleware app.use(cors()); // Enable CORS for requests from frontend app.use(express.json()); // Parse JSON request bodies // Basic health check route app.get('/health', (req, res) => { res.status(200).send('OK'); }); // Placeholder for our broadcast route app.post('/api/broadcast', (req, res) => { // Logic will go here in Section 3 & 4 console.log('Received broadcast request:', req.body); res.status(200).json({ message: 'Broadcast request received (placeholder)' }); }); // Start the server app.listen(port, () => { console.log(`Backend server listening on port ${port}`); if (!process.env.MESSAGEBIRD_API_KEY) { console.warn('WARNING: MESSAGEBIRD_API_KEY environment variable not set.'); } if (!process.env.MESSAGEBIRD_ORIGINATOR) { console.warn('WARNING: MESSAGEBIRD_ORIGINATOR environment variable not set. Using default.'); } });Run Backend Server: Open a new terminal window, navigate to the
backenddirectory, and run:You should see
Backend server listening on port 3001.Now you have both frontend and backend development servers running. Learn more about SMS delivery status tracking with Node.js and Express or explore building two-way SMS messaging systems.
2. How to Create a Vue Form for Bulk Message Broadcasting
Create the Vue component for users to enter recipients and their message.
Create Component File: In the frontend project, create
src/components/BroadcastForm.vue.Implement the Form: Add the following code to
BroadcastForm.vue. This uses Vue 3's Composition API (setupscript).v-modelfor two-way data binding between the textareas and reactive refs (recipientsInput,messageBody). ThehandleBroadcastSubmitfunction prevents default form submission, parses recipients, performs basic validation, and usesfetchto send data to your backend API. It handles loading states and displays feedback messages based on the API response.Use the Component in
App.vue: Replace the content ofsrc/App.vueto include your new form.If you check your browser (where the frontend
npm run devis running), you should now see the broadcast form. Submitting it will currently log a message in the backend console and show a placeholder success message in the frontend, as the backend logic isn't fully implemented yet.3. How to Build the MessageBird API Integration Layer
Enhance the backend
POST /api/broadcastendpoint to handle the request properly, validate input, and prepare for MessageBird integration.Update
server.js: Modify the/api/broadcastroute handler inbackend/server.js.// ... (keep existing require statements and middleware setup) ... // Updated broadcast route app.post('/api/broadcast', async (req, res) => { // Mark as async const { recipients, message } = req.body; // --- 1. Input Validation --- if (!recipients || !Array.isArray(recipients) || recipients.length === 0) { return res.status(400).json({ success: false, error: 'Recipients array is missing or empty.' }); } if (!message || typeof message !== 'string' || message.trim().length === 0) { return res.status(400).json({ success: false, error: 'Message is missing or empty.' }); } if (message.length > 1600) { // Example length limit return res.status(400).json({ success: false, error: 'Message exceeds maximum length of 1600 characters.' }); } // Basic E.164 format check (can be improved with regex) const invalidRecipients = recipients.filter(r => typeof r !== 'string' || !r.startsWith('+') || r.length < 10); if (invalidRecipients.length > 0) { return res.status(400).json({ success: false, error: `Invalid recipient format found. Ensure all numbers start with '+' and are in E.164 format. Problematic entries: ${invalidRecipients.slice(0, 5).join(', ')}...` }); } // --- 2. Authentication/Authorization (Placeholder) --- // In a real app, you'd verify user identity/permissions here. // For now, we assume the request is authorized if it reaches here. console.log('User authorized (placeholder). Proceeding with broadcast.'); // --- 3. MessageBird Interaction (Implemented in Section 4) --- try { console.log(`Attempting to send message "${message}" to ${recipients.length} recipients.`); // Add MessageBird sending logic here in the next step // For now, simulate success const results = recipients.map(r => ({ recipient: r, status: 'submitted', id: `fake_msg_id_${Math.random().toString(16).slice(2)}` })); console.log('Placeholder: MessageBird processing would happen here.'); // --- 4. Respond to Frontend --- res.status(200).json({ success: true, message: `Broadcast request submitted for ${recipients.length} recipients.`, details: results // Provide some detail back }); } catch (error) { console.error('Error during broadcast processing:', error); // This catch block will handle errors from the MessageBird sending logic later res.status(500).json({ success: false, error: 'An internal server error occurred while processing the broadcast.' }); } }); // ... (keep existing health check route and app.listen) ...recipientsarray and themessagestring, including format checks (basic E.164 check) and length limits. We added a placeholder comment for where authentication/authorization logic would go in a real-world application. The main logic block is wrapped in atry...catchto handle potential errors during processing (especially when we add MessageBird calls). We simulate a successful response structure that the frontend expects.Testing the Endpoint: You can test this endpoint using
curlor a tool like Postman.Curl Example: (Run from your terminal)
curl -X POST http://localhost:3001/api/broadcast \ -H "Content-Type: application/json" \ -d '{ "recipients": ["+14155550100", "+14155550101"], "message": "Hello from the API test!" }'Expected Response (JSON):
{ "success": true, "message": "Broadcast request submitted for 2 recipients.", "details": [ { "recipient": "+14155550100", "status": "submitted", "id": "fake_msg_id_..." }, { "recipient": "+14155550101", "status": "submitted", "id": "fake_msg_id_..." } ] }Test edge cases like sending empty recipients, an empty message, or malformed numbers to ensure the validation works.
4. How to Send Bulk SMS and WhatsApp Messages with MessageBird
Now, integrate the MessageBird SDK into your backend API to actually send the messages.
Get MessageBird Credentials:
backend/.envfile asMESSAGEBIRD_API_KEY.backend/.envfile asMESSAGEBIRD_ORIGINATOR.Implement Sending Logic in
server.js: Replace the placeholder comment in thetryblock of the/api/broadcastroute with the MessageBird sending logic.// ... (require statements, middleware, validation) ... app.post('/api/broadcast', async (req, res) => { const { recipients, message } = req.body; // --- Input Validation (Keep as before) --- // ... // --- Authentication/Authorization (Keep placeholder) --- // ... const originator = process.env.MESSAGEBIRD_ORIGINATOR; if (!originator) { console.error('MessageBird Originator (sender ID/number) is not configured in .env'); return res.status(500).json({ success: false, error: 'Server configuration error: Missing sender originator.' }); } try { console.log(`Sending message via MessageBird from ${originator} to ${recipients.length} recipients.`); const messagePromises = recipients.map(recipient => { return new Promise((resolve, reject) => { const params = { originator: originator, recipients: [recipient], // Send one API call per recipient for individual status tracking body: message, // Optional: Add type for WhatsApp, reportUrl for status updates etc. // type: 'sms', // Default is sms // reportUrl: 'YOUR_WEBHOOK_URL' // See Section 10 }; messagebird.messages.create(params, (err, response) => { if (err) { console.error(`Failed to send message to ${recipient}:`, err); // Resolve even on error to not fail the entire batch, but include error status resolve({ recipient: recipient, status: 'failed', error: err.errors || err.message || 'Unknown error' }); } else { // Log successful submission console.log(`Message submitted for ${recipient}, ID: ${response.id}`); resolve({ recipient: recipient, status: 'submitted', id: response.id, details: response }); } }); }); }); // Wait for all message submissions to complete (or fail individually) const results = await Promise.all(messagePromises); const successfulSubmissions = results.filter(r => r.status === 'submitted').length; const failedSubmissions = results.filter(r => r.status === 'failed').length; console.log(`Broadcast attempt complete. Success: ${successfulSubmissions}, Failed: ${failedSubmissions}`); // Respond with overall status and detailed results res.status(200).json({ success: true, // Indicate the API call succeeded, even if some messages failed submission message: `Broadcast submitted. ${successfulSubmissions} successful, ${failedSubmissions} failed submissions.`, details: results }); } catch (error) { // Catch errors during the Promise.all or other logic console.error('Critical error during broadcast processing:', error); res.status(500).json({ success: false, error: 'An internal server error occurred while processing the broadcast.' }); } }); // ... (health check, app.listen) ...MESSAGEBIRD_ORIGINATORfrom environment variables.recipientsarray.params) including the originator, the single recipient, and the message body. Sending one API call per recipient allows for individual status tracking and error handling, which is generally better for bulk sends than putting all recipients in one API call (though MessageBird supports that too).messagebird.messages.create(params, callback)to initiate the sending.messagebird.messages.createcall in aPromise. This allows us to manage the asynchronous nature of these calls. The promiseresolveseven if the MessageBird API returns an error for a specific number, capturing the failure details. This prevents one failed number from stopping the entire batch.Promise.all(messagePromises)waits for all the individual send attempts to complete (either successfully submitted or failed).resultsarray, containing the status for each recipient.200 OKresponse back to the frontend, including a summary message and the detailedresultsarray. The frontend can then interpret this data.Restart Backend: Stop (
Ctrl+C) and restart your backend server (node server.js) to apply the changes.Test: Use the frontend application to send a message to one or two real phone numbers (including your own) that you can verify. Check your phone and the MessageBird Dashboard logs (Logging > Messages) to confirm messages are sent. Observe the backend console logs and the feedback message in the frontend.
For more advanced implementations, check out our guide on implementing SMS OTP and 2FA with MessageBird or learn about handling MessageBird delivery status webhooks.
5. How to Handle Errors and Implement Retry Logic for Bulk Messaging
Production systems need robust error handling and logging.
Backend Enhancements:
Improved Logging: Replace
console.log/console.errorwith a more structured logger likewinstonorpinofor production. For this guide, we'll stick toconsole, but highlight its limitations.Specific Error Handling: The current
try...catcharoundPromise.allhandles errors during the mapping orPromise.allitself. The individual promise wrappers handle errors from the MessageBird API for each message.Retry Mechanisms (Conceptual): For transient errors (e.g., temporary network issues connecting to MessageBird), implement a retry strategy.
async-retryor implement manually with exponential backoff (wait longer between retries).// Conceptual retry logic for a single message send const { retry } = require('async-retry'); // npm install async-retry async function sendMessageWithRetry(params) { await retry(async bail => { // bail is a function to call if the error is not retryable return new Promise((resolve, reject) => { messagebird.messages.create(params, (err, response) => { if (err) { // Decide if the error is retryable (e.g., network error, rate limit) // or permanent (e.g., invalid number, auth error) if (isRetryableError(err)) { console.warn(`Retrying message to ${params.recipients[0]} due to: ${err.message}`); return reject(err); // Reject to trigger retry } else { console.error(`Non-retryable error for ${params.recipients[0]}:`, err); // Bail out - don't retry, but resolve the outer promise with failure return bail(new Error(`Permanent failure for ${params.recipients[0]}: ${err.message}`)); } } resolve({ /* success details */ }); }); }); }, { retries: 3, // Number of retries factor: 2, // Exponential backoff factor minTimeout: 1000, // Initial delay ms onRetry: (error, attempt) => { console.log(`Attempt ${attempt} failed for ${params.recipients[0]}. Retrying...`); } }).catch(finalError => { // This catch handles the case where all retries failed, or bail() was called console.error(`All retries failed for ${params.recipients[0]}:`, finalError); // Resolve the outer promise with failure status return Promise.resolve({ recipient: params.recipients[0], status: 'failed', error: finalError.message }); }); } // In the main loop, call sendMessageWithRetry instead of the direct Promise wrapper // const messagePromises = recipients.map(recipient => sendMessageWithRetry({ /* params */ }));Frontend Enhancements:
Clearer Feedback: The current feedback is okay, but could be more detailed based on the
detailsarray returned from the backend.Add corresponding styles for
.detailed-results,.status-success,.status-error.6. Should You Use a Database for Bulk SMS Broadcasting?
While not strictly required for basic sending, storing data enhances the application:
Conceptual Schema (using Prisma as an example ORM):
Frequently Asked Questions (FAQ)
How do I send bulk SMS messages with MessageBird and Vue.js?
To send bulk SMS with MessageBird and Vue.js, create a Vue 3 frontend form to collect recipients and message content, then connect it to a Node.js/Express backend that uses the MessageBird SDK to send messages. This tutorial provides complete implementation details including error handling and status tracking.
What is the MessageBird API rate limit for bulk sending?
MessageBird's SMS API rate limits vary by account type and region. Generally, you can send multiple messages per second, but it's recommended to implement batching with delays between batches (starting with 50 messages per batch with 1-second delays) to avoid throttling. Check your MessageBird Dashboard for specific limits.
How do I format phone numbers for MessageBird SMS API?
Phone numbers must be in E.164 format, which includes the country code preceded by a plus sign (e.g., +14155552671 for US numbers, +442071838750 for UK numbers). The format is: +[country code][area code][phone number] with no spaces, dashes, or parentheses.
Can I use MessageBird to send WhatsApp messages in bulk?
Yes, MessageBird supports WhatsApp Business API for bulk messaging. You need to set up a WhatsApp Business Account, register your sender, and modify the message parameters to include
type: 'whatsapp'. WhatsApp has additional requirements including message templates and opt-in consent.What's the difference between MessageBird Live and Test API keys?
Test API keys allow you to test your integration without sending real messages or incurring charges. Live API keys are required to actually send SMS/WhatsApp messages to recipients. Test keys return mock responses, while Live keys interact with the actual MessageBird messaging infrastructure.
How do I handle failed message deliveries with MessageBird?
Implement webhook endpoints to receive delivery status callbacks from MessageBird. Set the
reportUrlparameter when creating messages to receive real-time updates about message delivery status, including failures. Store these statuses in a database for tracking and potential retry logic.Is the MessageBird Node.js SDK production-ready?
The MessageBird SDK (@messagebird/api v4.0.1) hasn't been updated since 2021-2022, which means limited ongoing support. However, it remains functional for production use. Consider implementing comprehensive error handling, testing, and monitoring to ensure reliability in your specific use case.
How much does it cost to send bulk SMS with MessageBird?
MessageBird SMS pricing varies by destination country and volume. Typical rates range from $0.04-$0.15 per SMS in North America and Europe, with different rates for international destinations. Check the MessageBird pricing page for current rates specific to your target countries.
Can I use an alphanumeric sender ID with MessageBird?
Yes, MessageBird supports alphanumeric sender IDs (up to 11 characters) like "MyCompany" for branding purposes. However, availability varies by country—the United States doesn't support alphanumeric IDs, while many European and Asian countries do. Register your sender ID in the MessageBird Dashboard before use.
How do I implement retry logic for failed MessageBird API calls?
Use libraries like
async-retrywith exponential backoff to retry transient errors (network timeouts, rate limits) while avoiding retries for permanent failures (invalid recipients, authentication errors). Implement logic to categorize errors and only retry those that are likely to succeed on subsequent attempts.