code examples
code examples
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.
How to Build Bulk SMS and WhatsApp Messaging with MessageBird, Vue, and Node.js
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
.envfile.
System Architecture:
+-----------------+ +--------------------+ +------------------+
| Vue Frontend |----->| Node.js Backend API|----->| MessageBird API |
| (Vite/Vue) | | (Express) | | (SMS/WhatsApp) |
| - Broadcast Form| | - Validate Request | +------------------+
| - API Call | | - Use API Key |
+-----------------+ | - Loop & Send |
| - Handle Responses |
+--------------------+
|
V
+------------------+
| Database (Optional)|
| - Store Recipients |
| - Log Messages |
+------------------+
Prerequisites:
- Node.js 20.19+ and npm (or yarn) installed. Vite 7 requires Node.js 20.19+, 22.12+. Download Node.js
- A MessageBird account. Sign up for MessageBird
- 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
vueandvue-tsorvue(JavaScript) as you prefer. This guide uses JavaScript.bashnpm create vite@latest sent-messagebird-broadcast --template vue -
Navigate and Install Dependencies:
bashcd sent-messagebird-broadcast npm install -
Project Structure (Frontend): Vite creates a standard structure. You'll primarily work within the
srcdirectory: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:
bashnpm run devThis 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.bashmkdir backend cd backend -
Initialize Node.js Project:
bashnpm init -yThis 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.bashnpm install express dotenv @messagebird/api cors -
Project Structure (Backend):
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
.envFile: Create a file named.envin thebackenddirectory. Add your MessageBird Live API Key. Important: Add.envto your.gitignorefile to avoid committing secrets.plaintext# 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):javascriptrequire('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:bashnode server.jsYou 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).vue<template> <div class="broadcast-form"> <h2>Send Bulk Message</h2> <form @submit.prevent="handleBroadcastSubmit"> <div class="form-group"> <label for="recipients">Recipients (Phone numbers, one per line):</label> <textarea id="recipients" v-model="recipientsInput" rows="10" placeholder="e.g., +14155552671 +442071838750" required ></textarea> <small>Enter numbers in E.164 format (e.g., +14155552671).</small> </div> <div class="form-group"> <label for="message">Message:</label> <textarea id="message" v-model="messageBody" rows="5" placeholder="Enter your broadcast message here..." required maxlength="1600" ></textarea> <small>{{ messageBody.length }} / 1600 characters</small> </div> <button type="submit" :disabled="isLoading"> {{ isLoading ? 'Sending...' : 'Send Broadcast' }} </button> </form> <div v-if="feedbackMessage" :class="['feedback', feedbackStatus]"> {{ feedbackMessage }} </div> </div> </template> <script setup> import { ref } from 'vue'; const recipientsInput = ref(''); const messageBody = ref(''); const isLoading = ref(false); const feedbackMessage = ref(''); const feedbackStatus = ref(''); // 'success' or 'error' // Function to parse recipients from textarea const parseRecipients = (input) => { return input .split('\n') // Split by newline .map(line => line.trim()) // Trim whitespace .filter(line => line.length > 0); // Remove empty lines }; const handleBroadcastSubmit = async () => { isLoading.value = true; feedbackMessage.value = ''; feedbackStatus.value = ''; const recipients = parseRecipients(recipientsInput.value); if (recipients.length === 0) { feedbackMessage.value = 'Please enter at least one recipient.'; feedbackStatus.value = 'error'; isLoading.value = false; return; } if (!messageBody.value.trim()) { feedbackMessage.value = 'Message body cannot be empty.'; feedbackStatus.value = 'error'; isLoading.value = false; return; } try { // API Endpoint URL - Ensure your backend runs on port 3001 const apiUrl = 'http://localhost:3001/api/broadcast'; const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ recipients: recipients, message: messageBody.value.trim(), }), }); const result = await response.json(); if (!response.ok) { // Handle HTTP errors (e.g., 4xx, 5xx) throw new Error(result.error || `Server responded with ${response.status}`); } // Assuming backend returns { success: true, message: '...', details: [...] } on success // And { success: false, error: '...' } on failure if (result.success) { feedbackMessage.value = `Broadcast submitted successfully. Status: ${result.message || 'Processing initiated.'}`; feedbackStatus.value = 'success'; // Optionally clear the form on success // recipientsInput.value = ''; // messageBody.value = ''; } else { throw new Error(result.error || 'An unknown error occurred on the server.'); } } catch (error) { console.error('Error sending broadcast:', error); feedbackMessage.value = `Failed to send broadcast: ${error.message}`; feedbackStatus.value = 'error'; } finally { isLoading.value = false; } }; </script> <style scoped> .broadcast-form { max-width: 600px; margin: 2rem auto; padding: 2rem; border: 1px solid #ccc; border-radius: 8px; background-color: #f9f9f9; } .form-group { margin-bottom: 1.5rem; } label { display: block; margin-bottom: 0.5rem; font-weight: bold; } textarea { width: 100%; padding: 0.75rem; border: 1px solid #ccc; border-radius: 4px; font-size: 1rem; box-sizing: border-box; /* Include padding and border in element's total width */ } textarea:focus { border-color: #007bff; outline: none; } small { display: block; margin-top: 0.25rem; font-size: 0.8rem; color: #666; } button { padding: 0.75rem 1.5rem; background-color: #007bff; color: white; border: none; border-radius: 4px; font-size: 1rem; cursor: pointer; transition: background-color 0.2s ease; } button:hover { background-color: #0056b3; } button:disabled { background-color: #ccc; cursor: not-allowed; } .feedback { margin-top: 1.5rem; padding: 1rem; border-radius: 4px; text-align: center; } .feedback.success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .feedback.error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } </style>- Why: This code uses
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.
- Why: This code uses
-
Use the Component in
App.vue: Replace the content ofsrc/App.vueto include your new form.vue<template> <div id="app"> <header> <h1>MessageBird Bulk Broadcaster</h1> </header> <main> <BroadcastForm /> </main> </div> </template> <script setup> import BroadcastForm from './components/BroadcastForm.vue'; </script> <style> /* Add some basic global styles if needed */ #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: #2c3e50; margin-top: 60px; display: flex; flex-direction: column; align-items: center; } header { margin-bottom: 2rem; } main { width: 100%; max-width: 700px; /* Limit form width */ padding: 0 1rem; } </style>
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/broadcastroute handler inbackend/server.js.javascript// ... (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) ...- Why: We added robust input validation checks for the
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.
- Why: We added robust input validation checks for the
-
Testing the Endpoint: You can test this endpoint using
curlor a tool like Postman.Curl Example: (Run from your terminal)
bashcurl -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.
-
Get MessageBird Credentials:
- Log in to your MessageBird Dashboard.
- 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/.envfile asMESSAGEBIRD_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/.envfile asMESSAGEBIRD_ORIGINATOR.
-
Implement Sending Logic in
server.js: Replace the placeholder comment in thetryblock of the/api/broadcastroute 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 => { 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) ...- Why:
- We retrieve the
MESSAGEBIRD_ORIGINATORfrom environment variables. - We iterate through the validated
recipientsarray. - 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.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).- We collect the
resultsarray, containing the status for each recipient. - Finally, we send a
200 OKresponse back to the frontend, including a summary message and the detailedresultsarray. The frontend can then interpret this data.
- We retrieve the
- Why:
-
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.- 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...catcharoundPromise.allhandles errors during the mapping orPromise.allitself. 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.
- Distinguish between:
-
Retry Mechanisms (Conceptual): For transient errors (e.g., temporary network issues connecting to MessageBird), implement a retry strategy.
- Strategy: Use libraries like
async-retryor 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 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 */ }));- Why: Retries improve resilience against temporary glitches without manual intervention. Exponential backoff prevents overwhelming the service during widespread issues.
- Strategy: Use libraries like
Frontend Enhancements:
-
Clearer Feedback: The current feedback is okay, but could be more detailed based on the
detailsarray 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 submissionAdd 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/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
How to run the development servers for frontend and backend?
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'.
How to send bulk SMS messages using MessageBird?
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.
What is Vite and why is it used in this project?
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.
Why is a Node.js backend used with a Vue frontend?
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.
When should I use an Alphanumeric Sender ID with MessageBird?
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.
Can I store recipient lists and message history?
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.
How to install required dependencies for this project?
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'.
What is the purpose of the .env file?
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.
How to handle errors when sending bulk messages?
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.
What is the character limit for messages sent via MessageBird?
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.
What is the maximum number of recipients for a single broadcast?
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.
How to integrate retry mechanisms for failed messages?
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.
How to implement recipient list management in the application?
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.
Why does the application use the E.164 number format?
The E.164 format (+14155552671) ensures consistent and internationally recognized phone number formatting, which is essential for successful message delivery via the MessageBird API.