code examples
code examples
How to Build a Bulk SMS Broadcasting App with RedwoodJS and MessageBird API
Complete guide to building a full-stack bulk SMS application with RedwoodJS, Prisma ORM, and MessageBird API. Learn database setup, GraphQL mutations, React forms, and production deployment.
Build a Bulk SMS Broadcasting App with RedwoodJS and MessageBird
Learn how to build a production-ready bulk SMS broadcasting application using RedwoodJS and MessageBird's SMS API. This comprehensive tutorial walks you through creating a full-stack Node.js application that manages contacts and sends SMS messages to large audiences efficiently using GraphQL, Prisma ORM, and React.
This guide covers everything you need to know about building SMS broadcast functionality with RedwoodJS: initial project setup, database modeling with Prisma, creating Redwood services, GraphQL mutations, React frontend development, secure API key management, error handling, and production deployment. By the end, you'll have a working MessageBird integration ready to send bulk SMS campaigns at scale.
Project Overview and Goals
Goal: Build a web application that sends bulk SMS messages to multiple contacts simultaneously using the MessageBird SMS API and RedwoodJS framework.
Problem Solved: Manually sending SMS to large groups is inefficient and error-prone. This bulk SMS application automates message delivery and provides an intuitive interface for SMS broadcasting campaigns.
Technologies Used:
- RedwoodJS: Full-stack JavaScript/TypeScript framework combining React frontend, GraphQL API backend, and Prisma database layer with excellent developer experience.
- Node.js: JavaScript runtime powering the RedwoodJS API server and MessageBird SDK integration.
- Prisma ORM: Modern database toolkit for Node.js and TypeScript providing type-safe database access, migrations, and schema management.
- MessageBird SMS API: Cloud communications platform offering reliable SMS delivery worldwide via their Node.js SDK.
- PostgreSQL/SQLite/MySQL: Database options for storing contact information (Prisma supports all major databases).
System Architecture:
+-------------+ +-----------------+ +-----------------+ +-----------------+ +------------+
| User | ----> | Redwood Web UI | ----> | Redwood API | ----> | MessageBird API | ----> | Recipients |
| (Browser) | | (React/Form) | | (GraphQL/Node.js) | | (SMS Gateway) | | (Phones) |
+-------------+ +-----------------+ | | | +-----------------+ +------------+
| v |
| +-------------+ |
| | Prisma/DB | |
| | (Contacts) | |
| +-------------+ |
+-----------------+
Expected Outcome: A RedwoodJS application with:
- A database table to store contacts (name, phone number)
- A Redwood service to handle the logic of fetching contacts and sending SMS via MessageBird
- A GraphQL mutation endpoint to trigger the bulk broadcast
- A simple web page with a form to input the message and initiate the broadcast
Prerequisites:
- Node.js: Version 20.x or later
- Yarn: Version 1.22.21 or later
- MessageBird Account: Sign up at MessageBird and obtain an API Access Key
- Database: Access to a PostgreSQL, SQLite, or MySQL database. SQLite is used by default for development in RedwoodJS
1. Setting Up the RedwoodJS Project
Create a new RedwoodJS project and install the MessageBird Node.js SDK to enable SMS functionality.
-
Create RedwoodJS App: Open your terminal and run the RedwoodJS create command. This guide uses JavaScript, but TypeScript (
--typescript) is also fully supported.bashyarn create redwood-app redwood-messagebird-broadcast -
Navigate to Project Directory:
bashcd redwood-messagebird-broadcast -
Install Dependencies: RedwoodJS installs most dependencies during creation. Ensure everything is up to date.
bashyarn install -
Install MessageBird SDK: Install the MessageBird Node.js SDK for the API side of your application. Use
yarn workspaceto add it correctly.bashyarn workspace api add messagebird -
Configure Environment Variables:
Never hardcode sensitive information like API keys. RedwoodJS uses
.envfiles for environment variables. Create a.envfile in the project root:plaintext# .env # Replace YOUR_LIVE_API_KEY *entirely* with your actual key from the MessageBird dashboard. # Do not include quotes around the key itself. Correct format: MESSAGEBIRD_ACCESS_KEY=YourActualKeyValue MESSAGEBIRD_ACCESS_KEY=YOUR_LIVE_API_KEY # Optional: Set a default originator (sender ID). # Replace YourSenderID with your registered MessageBird number (E.164 format, e.g., +12025550181) # or an alphanumeric sender ID (max 11 chars, e.g., MyCompany). Check country regulations. # Do not include quotes around the sender ID itself. Correct format: MESSAGEBIRD_ORIGINATOR=YourActualSenderID MESSAGEBIRD_ORIGINATOR=YourSenderIDMESSAGEBIRD_ACCESS_KEY: Your live API key obtained from the MessageBird Dashboard (Developers → API access). ReplaceYOUR_LIVE_API_KEYwith your actual key. The line should look likeMESSAGEBIRD_ACCESS_KEY=live_xxxxxxxxxxxxxxxxxxxxxxx.MESSAGEBIRD_ORIGINATOR: The sender ID recipients will see. This can be a purchased MessageBird number (in E.164 format, e.g.,+12025550181) or an alphanumeric string (max 11 characters, e.g.,MyCompany). Note: Alphanumeric sender IDs have restrictions in some countries (like the US). Check MessageBird documentation and country regulations. ReplaceYourSenderIDwith your actual value.
Security: Add
.envto your.gitignorefile (it should be there by default) to prevent accidentally committing secrets. -
Start Development Server: Verify the basic setup by starting the development server.
bashyarn redwood devYour browser should open to
http://localhost:8910, displaying the RedwoodJS welcome page.
Project Structure: RedwoodJS organizes code into specific directories:
api/: Contains the backend code (GraphQL API, services, database schema)web/: Contains the frontend code (React components, pages, CSS)
2. Creating the Database Schema with Prisma
Define a Prisma schema to store contacts for your SMS broadcasts with proper E.164 phone number formatting.
-
Define Prisma Schema: Open
api/db/schema.prismaand define aContactmodel. Replace any existing example models.prisma// api/db/schema.prisma datasource db { provider = "sqlite" // Or "postgresql" or "mysql" url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" } model Contact { id Int @id @default(autoincrement()) name String? phoneNumber String @unique // E.164 format recommended (e.g., +14155552671) createdAt DateTime @default(now()) }phoneNumber: Marked as unique to avoid duplicates. Store numbers in E.164 format (including the+and country code) for international compatibility with MessageBird.name: An optional field for personalization.
-
Apply Database Migrations:
Prisma Migrate uses the schema to create and apply SQL migrations to your database.
bashyarn rw prisma migrate dev- You'll be prompted to name the migration (e.g.,
create contact model). - This command creates the migration file and applies it to your development database (an SQLite file by default).
- You'll be prompted to name the migration (e.g.,
-
Seed the Database (Optional but Recommended): Add some sample contacts to test your application. Redwood provides a seeding mechanism. Create or edit
api/db/seed.js:javascript// api/db/seed.js import { db } from './db' // IMPORTANT: Replace these placeholder numbers with REAL, VALID phone numbers // in E.164 format (e.g. +14155552671). Include your own phone number // for testing purposes so you can verify message delivery. const CONTACTS_TO_CREATE = [ { name: 'Test User One', phoneNumber: '+15551234567' }, // REPLACE with a real number { name: 'Test User Two', phoneNumber: '+15559876543' }, // REPLACE with another real number // Add more contacts as needed for testing ] export default async () => { try { console.log(`Seeding database with ${CONTACTS_TO_CREATE.length} contacts...`) console.log('Please ensure the phone numbers in seed.js are valid E.164 format for testing.') // Using Promise.all to execute all promises concurrently await Promise.all( CONTACTS_TO_CREATE.map((contactData) => { // Basic validation check (does not guarantee deliverability) if (!/^\+[1-9]\d{1,14}$/.test(contactData.phoneNumber)) { console.warn(`Skipping invalid phone number format: ${contactData.phoneNumber}. Use E.164 format (e.g., +14155552671).`) return Promise.resolve(); // Skip this contact } return db.contact.upsert({ where: { phoneNumber: contactData.phoneNumber }, update: contactData, // Update if phone number exists create: contactData, // Create if it doesn't }) }) ) console.log(`Finished seeding contacts.`) } catch (error) { console.error('Error during database seeding:', error) } }- Replace the placeholder phone numbers (
+1555...) with valid E.164 format numbers you can test with. Use your own phone number as one of the contacts. - The
upsertoperation prevents errors if you run the seed command multiple times with the same phone numbers.
Run the seed command:
bashyarn rw prisma db seed - Replace the placeholder phone numbers (
3. Implementing the SMS Broadcasting Service
Create a RedwoodJS service to handle SMS sending logic using the MessageBird API with proper error handling and batch processing.
-
Generate the Service: Generate the service file using the Redwood CLI.
bashyarn rw g service messageBroadcasterThis creates
api/src/services/messageBroadcaster/messageBroadcaster.jsand its corresponding test file. -
Implement the Broadcast Logic: Open
api/src/services/messageBroadcaster/messageBroadcaster.jsand add the logic.javascript// api/src/services/messageBroadcaster/messageBroadcaster.js import { db } from 'src/lib/db' import { logger } from 'src/lib/logger' import { initClient } from 'messagebird' // Import MessageBird SDK // Initialize MessageBird Client // Ensure MESSAGEBIRD_ACCESS_KEY is set in your .env file const messagebird = initClient(process.env.MESSAGEBIRD_ACCESS_KEY) // Note: Check current MessageBird API documentation for recipient limits per request. // This value might change over time. const MAX_RECIPIENTS_PER_REQUEST = 50 export const sendBulkSms = async ({ messageBody }) => { logger.info('Initiating bulk SMS broadcast...') if (!process.env.MESSAGEBIRD_ACCESS_KEY) { logger.error('MessageBird API Key (MESSAGEBIRD_ACCESS_KEY) is not configured.') throw new Error('SMS service is not configured.') } if (!messageBody || messageBody.trim() === '') { logger.warn('Attempted to send empty message.') throw new Error('Message body cannot be empty.') } const originator = process.env.MESSAGEBIRD_ORIGINATOR || 'MessageBird' // Fallback originator try { // 1. Fetch all contacts from the database const contacts = await db.contact.findMany({ select: { phoneNumber: true }, // Only select necessary field }) if (!contacts || contacts.length === 0) { logger.warn('No contacts found in the database to send SMS to.') return { success: true, message: 'No contacts to send to.', sentCount: 0 } } const recipients = contacts.map((contact) => contact.phoneNumber) logger.info(`Found ${recipients.length} contacts. Preparing to send...`) // 2. Prepare the MessageBird payload & handle chunking let totalSent = 0 let failedBatches = 0 for (let i = 0; i < recipients.length; i += MAX_RECIPIENTS_PER_REQUEST) { const recipientChunk = recipients.slice(i, i + MAX_RECIPIENTS_PER_REQUEST) const params = { originator: originator, recipients: recipientChunk, body: messageBody, } const batchNum = i / MAX_RECIPIENTS_PER_REQUEST + 1 logger.debug(`Sending batch ${batchNum} to ${recipientChunk.length} recipients.`) try { // 3. Send the message using MessageBird SDK const response = await new Promise((resolve, reject) => { messagebird.messages.create(params, (err, response) => { if (err) { // Log detailed error from MessageBird logger.error({ err }, `MessageBird API error during batch ${batchNum} send`) return reject(err) } resolve(response) }) }) // Optional: Log success details (be mindful of sensitive data) // logger.debug({ response }, `MessageBird API success response for batch ${batchNum}`) totalSent += recipientChunk.length // Assume success for the batch if no error thrown } catch (batchError) { failedBatches++ logger.error(`Failed to send batch ${batchNum} starting at index ${i}. Error: ${batchError.message}`) // Consider adding specific error handling or retry logic here } } // End of batch loop const totalBatches = Math.ceil(recipients.length / MAX_RECIPIENTS_PER_REQUEST) logger.info(`Bulk SMS broadcast attempt finished. Sent to approximately ${totalSent} recipients across ${totalBatches} batches. ${failedBatches} batches failed.`) if (failedBatches > 0) { return { success: false, message: `Broadcast attempted, but ${failedBatches} out of ${totalBatches} batches failed. Approximately ${totalSent} messages may have been sent. Check logs for details.`, sentCount: totalSent, } } return { success: true, message: `Successfully initiated broadcast to ${totalSent} recipients.`, sentCount: totalSent, } } catch (error) { logger.error({ error }, 'Error during bulk SMS broadcast process') // Return a generic error to the client throw new Error('Failed to send bulk SMS broadcast. Check API logs for details.') } }- Initialization: Initialize the MessageBird client using the key from
.env - Input Validation: Check for the API key and non-empty message body
- Fetch Contacts: Retrieve all phone numbers from the
Contacttable using Prisma - Chunking: Implement basic chunking based on
MAX_RECIPIENTS_PER_REQUEST. Check MessageBird docs for current limits - API Call: Use
messagebird.messages.createwithin a Promise wrapper for async/await syntax - Error Handling: Include
try...catchblocks for database errors and MessageBird API errors. Log errors using Redwood's logger - Response: Return a success status, a message, and the count of messages attempted
- Initialization: Initialize the MessageBird client using the key from
4. Building the GraphQL API Layer
Define a GraphQL mutation endpoint that allows the frontend to trigger bulk SMS sends through your MessageBird service.
-
Generate GraphQL Definition (SDL): Generate the SDL file for your broadcaster using the Redwood CLI. Define a custom mutation rather than full CRUD.
bashyarn rw g sdl messageBroadcaster --no-crudThis creates
api/src/graphql/messageBroadcaster.sdl.js. -
Define the Mutation: Open
api/src/graphql/messageBroadcaster.sdl.jsand define thesendBulkSmsmutation.graphql// api/src/graphql/messageBroadcaster.sdl.js export const schema = gql` type BroadcastResult { success: Boolean! message: String! sentCount: Int! } type Mutation { """ Initiates a bulk SMS broadcast to all contacts. Requires authentication in production environments. """ sendBulkSms(messageBody: String!): BroadcastResult! @skipAuth # WARNING: Development only! # !! IMPORTANT !! # For any real application, especially production, you MUST replace @skipAuth # with @requireAuth after setting up authentication (see Security section). # Leaving @skipAuth allows anyone to trigger SMS sends. # Example for production: # sendBulkSms(messageBody: String!): BroadcastResult! @requireAuth } `BroadcastResult: Defines the shape of the data returned by the mutationsendBulkSmsMutation: Takes a non-nullablemessageBodystring as input and returns aBroadcastResult@skipAuth/@requireAuth: The@skipAuthdirective makes the mutation publicly accessible for easy testing during development. Replace this with@requireAuthbefore deploying to any non-local environment. Failure to do so creates a major security vulnerability. See the Security section for implementing authentication
-
Link Service to SDL (Resolver): The resolver connects the GraphQL mutation to the service function. Redwood automatically maps mutations in the SDL to functions with the same name in the corresponding service file (
api/src/services/messageBroadcaster/messageBroadcaster.js). Since the service function and mutation are both namedsendBulkSms, Redwood handles the connection automatically. No extra resolver code is needed.
5. Creating the React Frontend Interface
Build a React form component that sends bulk SMS messages through your GraphQL API with real-time feedback.
-
Generate the Page: Use the Redwood CLI to generate a page component.
bashyarn rw g page Broadcast /broadcastThis creates
web/src/pages/BroadcastPage/BroadcastPage.jsand sets up the route/broadcast. -
Implement the Broadcast Form: Open
web/src/pages/BroadcastPage/BroadcastPage.jsand add the form and mutation logic.javascript// web/src/pages/BroadcastPage/BroadcastPage.js import { useState } from 'react' import { MetaTags, useMutation } from '@redwoodjs/web' import { toast, Toaster } from '@redwoodjs/web/toast' import { Form, TextAreaField, Submit, FieldError } from '@redwoodjs/forms' // GraphQL Mutation Definition (must match SDL) const SEND_BULK_SMS_MUTATION = gql` mutation SendBulkSmsMutation($messageBody: String!) { sendBulkSms(messageBody: $messageBody) { success message sentCount } } ` const BroadcastPage = () => { const [messageBody, setMessageBody] = useState('') const [characterCount, setCharacterCount] = useState(0) const [sendSms, { loading, error }] = useMutation(SEND_BULK_SMS_MUTATION, { onCompleted: (data) => { const result = data.sendBulkSms if (result.success) { toast.success( `${result.message} (Approx. ${result.sentCount} messages)` ) setMessageBody('') // Clear form on success setCharacterCount(0) } else { // Display potentially partial failures as warnings/errors toast.error(`Broadcast issue: ${result.message}`, { duration: 8000 }) } }, onError: (error) => { // Catches GraphQL/network level errors toast.error(`Error sending broadcast: ${error.message}`) console.error('GraphQL Mutation Error:', error) }, }) const onSubmit = (data) => { console.log('Form Submitted:', data) sendSms({ variables: { messageBody: data.messageBody } }) } const handleInputChange = (event) => { const body = event.target.value setMessageBody(body) setCharacterCount(body.length) } // Basic SMS segment calculation (approximate). // NOTE: This is a simplified calculation. Real-world segment calculation // depends heavily on character encoding (GSM-7 vs. UCS-2 for special chars/emojis) // and concatenation headers. Using non-standard characters significantly reduces // characters per segment (often to 70). This estimate may not be accurate for billing. const calculateSegments = (count) => { if (count === 0) return 0 // Very basic GSM-7 estimate if (count <= 160) return 1 // Very rough multi-part estimate (assumes 153 chars per subsequent segment) return Math.ceil(count / 153) } const segments = calculateSegments(characterCount) return ( <> <MetaTags title="Broadcast SMS" description="Send Bulk SMS Page" /> <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} /> <h1>Send Broadcast SMS</h1> <p> Enter the message you want to send to all contacts stored in the database. </p> <Form onSubmit={onSubmit} className="rw-form-wrapper"> <TextAreaField name="messageBody" value={messageBody} onChange={handleInputChange} placeholder="Type your SMS message here..." validation={{ required: true, minLength: 1 }} className="rw-input" // Basic Redwood input class errorClassName="rw-input rw-input-error" // Customize height/width using CSS classes // e.g., in web/src/index.css or a dedicated CSS module. /> <FieldError name="messageBody" className="rw-field-error" /> <div style={{ marginTop: '10px', marginBottom: '10px', color: '#555' }}> Character Count: {characterCount} (Approx. {segments} segment{segments !== 1 ? 's' : ''}) <p style={{color: 'orange', fontSize: '0.9em', marginTop: '5px'}}> <strong>Note:</strong> Segment calculation is approximate. Messages over 160 characters (or fewer with special characters/emojis) will be split and billed as multiple SMS. </p> </div> <Submit disabled={loading || messageBody.trim() === ''} className="rw-button rw-button-blue"> {loading ? 'Sending...' : 'Send Broadcast'} </Submit> {error && ( <div className="rw-form-error" style={{ marginTop: '15px' }}> <p><strong>Broadcast Failed (Network/GraphQL Error):</strong></p> <pre>{error.message}</pre> </div> )} </Form> </> ) } export default BroadcastPageSEND_BULK_SMS_MUTATION: Defines the GraphQL mutation query. Usesgqltag.useMutationHook: Manages mutation state (loading,error).onCompleted/onError: Handles success/error feedback usingtoast.FormComponents: Uses Redwood form helpers.- Character Count/Segments: Provides a basic estimate with a strong caveat emphasizing the approximation and billing implications.
- State: Manages message body and count.
- Styling: Uses default Redwood styles.
6. MessageBird API Integration Guide
Follow these essential steps to properly configure MessageBird's SMS API in your RedwoodJS application:
- API Key: Store securely in
.envasMESSAGEBIRD_ACCESS_KEY. Access in the service viaprocess.env.MESSAGEBIRD_ACCESS_KEY. Ensure the correct.envsyntax (KEY=VALUE) is used. - SDK Initialization:
initClient(process.env.MESSAGEBIRD_ACCESS_KEY)in the service file (messageBroadcaster.js). - Sending Messages: Use
messagebird.messages.create(params, callback)within the service. - Originator: Configure via
MESSAGEBIRD_ORIGINATORin.envor default in the service. Ensure the correct.envsyntax (KEY=VALUE) is used. Ensure the chosen originator is valid and complies with regulations in target countries. - Recipient Format: Store phone numbers in the database in E.164 format (e.g.,
+14155552671) for reliable delivery.
Getting the API Key:
- Log in to your MessageBird Dashboard.
- Navigate to the "Developers" section in the left-hand menu.
- Click on "API access".
- You might see existing keys or need to "Add access key".
- Use a Live access key for sending actual messages. Copy this key.
- Paste the key into your
.envfile as the value forMESSAGEBIRD_ACCESS_KEY, ensuring the format isMESSAGEBIRD_ACCESS_KEY=YourActualKeyValue.
7. Implementing Error Handling, Logging, and Retries
Build robust applications with solid error handling and logging.
- Error Handling:
- Service Level: The
messageBroadcasterservice usestry...catchblocks. Catch specific errors (missing key, empty message) early. Log MessageBird API errors. Throw generic errors upwards. Log batch failures individually. - API Level (GraphQL): Redwood catches service errors and formats them as GraphQL errors.
- Frontend Level:
useMutationhook'sonErrorcatches GraphQL/network errors.onCompletedchecks thesuccessflag for application-level feedback.
- Service Level: The
- Logging:
- Use Redwood's logger (
src/lib/logger.js) in the service (logger.info,logger.warn,logger.error,logger.debug). - Logs go to the console in development. Configure production log targets.
- Use Redwood's logger (
- Retry Mechanisms (Conceptual):
- The current implementation logs batch failures but doesn't retry.
- For Production: Implement retries, especially for transient errors.
- Simple Retry: Add a retry loop with backoff within the batch
catchblock in the service. - Background Jobs: For high reliability and large lists, use a background job queue (e.g., Redwood's experimental queue, BullMQ+Redis). This avoids HTTP timeouts and allows robust retry and tracking. (Beyond this guide's scope).
- Simple Retry: Add a retry loop with backoff within the batch
8. Adding Security Features
Secure your application.
- Authentication & Authorization:
- Problem: The
sendBulkSmsmutation currently uses@skipAuth, making it public. - Solution: Implement RedwoodJS authentication.
- Run
yarn rw setup auth <provider>(e.g.,dbAuth,Auth0). Follow setup instructions. - CRITICAL: Protect the API endpoint: Change
@skipAuthto@requireAuthinapi/src/graphql/messageBroadcaster.sdl.js. This is mandatory before any deployment. - Protect the Frontend Page: Wrap
BroadcastPagein<Private>or use role-based controls. See RedwoodJS Auth docs.
- Run
- Problem: The
- Secure API Key Management:
- Use
.env(added to.gitignore). - Set environment variables securely in deployment (do not commit production secrets).
- Use
- Input Validation:
- Frontend: Basic validation via Redwood Forms.
- Backend: Service validates
messageBody. Prisma enforcesphoneNumberuniqueness. Add server-side E.164 format validation if needed.
- Rate Limiting:
- Problem: Risk of abuse leading to high costs or API throttling.
- Solution: Implement rate limiting on the
sendBulkSmsmutation (e.g., usinggraphql-rate-limit-directiveor infrastructure-level limits).
- Cross-Site Request Forgery (CSRF) Protection:
- Enabled by default in RedwoodJS. Keep it enabled.
- Dependencies: Keep dependencies up-to-date (
yarn outdated,yarn upgrade-interactive).
9. Handling Special Cases
- Phone Number Formatting: Enforce E.164 format validation before saving contacts.
- Character Encoding & Message Length: Standard SMS (GSM-7) is 160 characters. Non-standard characters (emojis) switch to UCS-2 (70 characters per segment). Long messages are split and billed per segment. The frontend estimate is approximate; inform users.
- MessageBird Rate Limits: Be aware of MessageBird API limits. The chunking helps. For high volumes, add delays between batches or use background jobs.
- Originator Restrictions: Check country rules for alphanumeric vs. numeric sender IDs.
- Delivery Reports (Advanced): Set up MessageBird webhooks to receive delivery status updates for better tracking (requires a dedicated webhook handler endpoint in Redwood).
10. Implementing Performance Optimizations
Optimize performance for large lists.
- Database Queries:
- Current
findMany()loads all contacts. For very large lists, this uses significant memory. - Optimization: Process contacts in DB batches using Prisma
skipandtakewithin a loop, instead of loading all at once.javascript// Inside sendBulkSms service – conceptual alternative loop for DB fetching const DB_BATCH_SIZE = 1000; // How many contacts to fetch from DB at once let skip = 0; let contactsBatch; let allRecipients = []; // Collect recipients if needed, or process directly do { contactsBatch = await db.contact.findMany({ select: { phoneNumber: true }, skip: skip, take: DB_BATCH_SIZE, orderBy: { id: 'asc' } // Consistent ordering is good practice for skip/take }); if (contactsBatch.length > 0) { const batchRecipients = contactsBatch.map(c => c.phoneNumber); // Now, process 'batchRecipients' by sending them to MessageBird // (applying the MessageBird API chunking within this loop) // e.g., call a helper function: await sendToMessageBird(batchRecipients, originator, messageBody); logger.debug(`Fetched ${batchRecipients.length} contacts from DB (offset ${skip})`); skip += contactsBatch.length; } } while (contactsBatch.length === DB_BATCH_SIZE); // Make sure the MessageBird sending logic handles these smaller DB batches
- Current
- API Call Chunking: Already implemented. Ensure
MAX_RECIPIENTS_PER_REQUESTaligns with MessageBird's current limits (check their docs). - Asynchronous Processing: Use background jobs for sending to improve frontend responsiveness and reliability for large broadcasts.
- Caching: Less relevant for sending, but could apply to displaying contacts or history.
11. Adding Monitoring, Observability, and Analytics
Monitor your application in production.
- Logging: Use a centralized logging service (Datadog, Logtail, Sentry) in production.
- Error Tracking: Integrate Sentry, Bugsnag, etc., to capture and alert on exceptions.
- Health Checks: Implement a health check endpoint for your deployment platform.
- Performance Monitoring (APM): Use APM tools (Datadog, New Relic) to trace requests and find bottlenecks.
- Key Metrics: Track broadcasts, messages sent, error rates (API, MessageBird), API response times, and (Advanced) delivery rates.
- Dashboards: Visualize key metrics in your monitoring tool.
12. Troubleshooting and Caveats
Common issues:
MESSAGEBIRD_ACCESS_KEYIncorrect/Missing: Check.envand production variables. Use a Live key. Error: "authentication failed".- Invalid Phone Number Format: Use E.164 (
+1...). Error: "recipient not valid". Validate before saving. - MessageBird API Errors: Check MessageBird's API documentation for specific error codes returned in the logs. Common ones relate to invalid recipients, insufficient balance, or originator issues.
@skipAuthLeft in Production: Major security risk. Ensure@requireAuthis used after setting up authentication.- Database Connection Issues: Verify
DATABASE_URLin.envand ensure the database server is running and accessible. - Deployment Environment Variables: Ensure
MESSAGEBIRD_ACCESS_KEY,MESSAGEBIRD_ORIGINATOR, andDATABASE_URLare correctly configured in your hosting environment (Vercel, Netlify, Render, etc.).
Related Resources
For more information about building SMS applications with Node.js and RedwoodJS, check out these resources:
- MessageBird Node.js SDK Documentation - Official MessageBird Node.js integration guide
- RedwoodJS Documentation - Complete RedwoodJS framework documentation
- Prisma ORM Documentation - Learn more about Prisma database toolkit
- How to Send SMS with Node.js - Basic SMS sending tutorial for RedwoodJS
- GraphQL Best Practices - GraphQL API design principles