code examples
code examples
Building a Bulk SMS Broadcast System with RedwoodJS and Infobip
Learn how to build a robust bulk SMS broadcasting application using RedwoodJS and the Infobip API. Send targeted messages to multiple recipients with full tracking and error handling.
Building a Bulk SMS Broadcast System with RedwoodJS and Infobip
Build a robust bulk SMS broadcasting application using RedwoodJS and the Infobip API. Create a system that enables users to input phone numbers and messages, send broadcasts via Infobip, and track status.
Send targeted SMS communications – such as notifications, alerts, or marketing messages – to multiple recipients simultaneously.
Technologies Used:
- RedwoodJS: A full-stack JavaScript/TypeScript framework combining React, GraphQL, Prisma, and Jest. Chosen for rapid development, integrated tooling, and clear architectural patterns.
- Infobip: A global communication platform offering APIs for SMS and other channels. Chosen for reliable SMS delivery, comprehensive API features, and bulk messaging capabilities.
- Prisma: A next-generation Node.js and TypeScript ORM used by RedwoodJS for database access.
- PostgreSQL: A powerful, open-source object-relational database (Prisma also supports MySQL, SQLite).
- Node.js & Yarn: The runtime environment and package manager.
System Architecture:
(Note: The following diagram uses Mermaid syntax. Rendering may vary depending on the platform.)
graph LR
A[User's Browser] -- Interacts --> B(RedwoodJS Web Side - React UI);
B -- Sends GraphQL Mutation --> C(RedwoodJS API Side - GraphQL Server);
C -- Calls Service Logic --> D(RedwoodJS Service - broadcasts.js);
D -- Interacts with Database via Prisma --> E[(Database - PostgreSQL)];
D -- Makes API Request --> F(Infobip SMS API);
F -- Sends SMS --> G((Recipients));
F -- Returns Response --> D;
D -- Updates Status --> E;
C -- Returns GraphQL Response --> B;
B -- Updates UI --> A;Final Outcome:
Build a functional RedwoodJS application with:
- A UI form to create new SMS broadcasts (recipients, message).
- An API endpoint to trigger broadcasts via Infobip.
- Database storage for broadcast details and status.
- Secure handling of Infobip API credentials.
- Basic error handling and logging.
Prerequisites:
- Node.js >=20.x (Node.js 20 is Maintenance LTS, Node.js 22 is Active LTS as of January 2025; Node.js 18 reached EOL March 2025)
- Yarn (v1)
- Access to a PostgreSQL database instance (use Docker for local development)
- An active Infobip account with API access enabled
- Basic familiarity with JavaScript, React, and GraphQL concepts
1. Setting up the Project
Initialize your RedwoodJS project and configure the essentials.
-
Create RedwoodJS App: Open your terminal and run:
bashyarn create redwood-app redwood-infobip-broadcast cd redwood-infobip-broadcastChoose TypeScript if prompted (recommended, but JavaScript works too).
-
Database Configuration: Configure your database connection string in the
.envfile at the project root. Replace the placeholder with your PostgreSQL connection URL:Code# .env # Use DATABASE_URL to configure your database connection # Example: DATABASE_URL="postgresql://user:password@host:port/database?schema=public" DATABASE_URL="postgresql://postgres:your_password@localhost:5432/infobip_broadcast?schema=public"Ensure your PostgreSQL server is running and the specified database (
infobip_broadcastin this example) exists. -
Initial Migration: Apply the initial database schema:
bashyarn rw prisma migrate devEnter a migration name when prompted (e.g.,
initial_setup). -
Infobip Environment Variables: Add your Infobip credentials to the
.envfile:Code# .env (add these lines) # Infobip API Credentials (API side only) INFOBIP_API_KEY="YOUR_INFOBIP_API_KEY" INFOBIP_BASE_URL="YOUR_INFOBIP_REGION_BASE_URL" # e.g., https://xyz.api.infobip.com INFOBIP_SENDER_ID="YourSenderID" # Optional but often required/recommendedINFOBIP_API_KEY: Find this in your Infobip account under API Key management.INFOBIP_BASE_URL: Specific to your account's region (e.g.,https://<your-subdomain>.api.infobip.com). Find this in the Infobip API documentation or account settings.INFOBIP_SENDER_ID: The alphanumeric sender ID or phone number registered and approved in your Infobip account. Optional depending on your Infobip setup and destination country regulations, but recommended.
Obtaining Infobip Credentials:
- Log in to your Infobip Portal.
- Navigate to API Keys (often under account settings or developer tools). Generate a new key if needed.
- Locate your Base URL in the API documentation section or API key details. Use the correct regional URL.
- Configure Sender IDs (numeric or alphanumeric) under channel settings (e.g., SMS). Ensure they are approved for use.
2. Implementing Core Functionality
Define the database model, create the API service and GraphQL schema, and build the UI component.
-
Define Database Schema: Update the Prisma schema file (
api/db/schema.prisma) to include aBroadcastmodel.prisma// api/db/schema.prisma datasource db { provider = ""postgresql"" url = env(""DATABASE_URL"") } generator client { provider = ""prisma-client-js"" binaryTargets = ""native"" } // Define your own datamodels here and run `yarn redwood prisma migrate dev` // to create migrations for them and apply to your dev DB. model Broadcast { id Int @id @default(autoincrement()) message String recipients String[] // Store recipients as an array of strings (phone numbers) status String @default("PENDING") // e.g., PENDING, SENT, FAILED infobipBulkId String? // Store the ID returned by Infobip for the bulk request errorMessage String? // Store error messages from Infobip createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }- We use
String[]to store multiple recipient phone numbers. statustracks the broadcast progress.infobipBulkIdhelps correlate with Infobip's system.
- We use
-
Apply Schema Changes: Generate and apply the migration:
bashyarn rw prisma migrate devEnter a migration name like
add_broadcast_model. -
Generate Redwood Files: Use Redwood's generators to scaffold the necessary GraphQL SDL, service, and UI components:
bashyarn rw g sdl Broadcast # Generates: api/src/graphql/broadcasts.sdl.ts # api/src/services/broadcasts/broadcasts.ts yarn rw g page Broadcast /broadcasts # Generates: web/src/pages/BroadcastPage/BroadcastPage.tsx yarn rw g cell Broadcasts # Generates: web/src/components/BroadcastsCell/BroadcastsCell.tsx yarn rw g component BroadcastForm # Generates: web/src/components/BroadcastForm/BroadcastForm.tsx -
Implement the Broadcast Service: Modify the generated service file (
api/src/services/broadcasts/broadcasts.ts) to include logic for creating broadcasts and interacting with the Infobip API.typescript// api/src/services/broadcasts/broadcasts.ts import type { QueryResolvers, MutationResolvers } from 'types/graphql' import { fetch } from 'cross-undici-fetch' // Use cross-undici-fetch for Node.js compatibility import { db } from 'src/lib/db' import { logger } from 'src/lib/logger' // Import Redwood's logger // IMPORTANT: Verify these interfaces against the current Infobip API documentation // for the `POST /sms/2/text/advanced` endpoint (as of January 2025). // Helper function to validate E.164 format (basic example) const isValidE164 = (phoneNumber: string): boolean => { return /^\+[1-9]\d{1,14}$/.test(phoneNumber); } export const broadcasts: QueryResolvers['broadcasts'] = () => { return db.broadcast.findMany({ orderBy: { createdAt: 'desc' } }) } export const broadcast: QueryResolvers['broadcast'] = ({ id }) => { return db.broadcast.findUnique({ where: { id }, }) } export const createBroadcast: MutationResolvers['createBroadcast'] = async ({ input, }) => { logger.info({ input }, 'Attempting to create broadcast') const { message, recipients } = input // --- Input Validation --- if (!message || message.trim() === '') { throw new Error('Message content cannot be empty.') } if (!recipients || recipients.length === 0) { throw new Error('Recipients list cannot be empty.') } const invalidNumbers = recipients.filter(num => !isValidE164(num.trim())) if (invalidNumbers.length > 0) { throw new Error(`Invalid phone number format found: ${invalidNumbers.join(', ')}. Numbers must be in E.164 format (e.g., +14155552671).`) } // --- End Input Validation --- const apiKey = process.env.INFOBIP_API_KEY const baseUrl = process.env.INFOBIP_BASE_URL const senderId = process.env.INFOBIP_SENDER_ID // Optional sender if (!apiKey || !baseUrl) { logger.error('Infobip API Key or Base URL missing in environment variables.') throw new Error( 'Server configuration error: Infobip credentials not set.' ) } // 1. Create the broadcast record in our DB first (Status: PENDING) let createdBroadcast try { createdBroadcast = await db.broadcast.create({ data: { message, recipients: recipients.map(r => r.trim()), // Ensure trimmed numbers status: 'PENDING', }, }) logger.info( { broadcastId: createdBroadcast.id }, 'Broadcast record created in DB' ) } catch (dbError) { logger.error({ error: dbError }, 'Failed to create broadcast record in DB') throw new Error('Failed to initiate broadcast process.') } // 2. Prepare and send the request to Infobip const infobipDestinations: InfobipDestination[] = createdBroadcast.recipients.map( (recipient) => ({ to: recipient, }) ) const infobipRequestBody: InfobipRequestBody = { messages: [ { ...(senderId && { from: senderId }), // Include sender ID if configured destinations: infobipDestinations, text: createdBroadcast.message, }, ], } try { logger.info({ url: `${baseUrl}/sms/2/text/advanced` }, 'Sending request to Infobip') const response = await fetch(`${baseUrl}/sms/2/text/advanced`, { method: 'POST', headers: { Authorization: `App ${apiKey}`, // Use 'App' prefix for API Key v2 'Content-Type': 'application/json', Accept: 'application/json', }, body: JSON.stringify(infobipRequestBody), }) const responseBody = (await response.json()) as | InfobipSuccessResponse | InfobipErrorResponse if (!response.ok) { logger.error( { status: response.status, body: responseBody }, 'Infobip API request failed' ) const errorMessage = (responseBody as InfobipErrorResponse)?.requestError?.serviceException ?.text || `HTTP error ${response.status}` // Update DB record to FAILED await db.broadcast.update({ where: { id: createdBroadcast.id }, data: { status: 'FAILED', errorMessage: errorMessage }, }) throw new Error(`Infobip API Error: ${errorMessage}`) } // Success Case const successResponse = responseBody as InfobipSuccessResponse logger.info( { bulkId: successResponse.bulkId, messages: successResponse.messages }, 'Infobip request successful' ) // Update DB record to SENT with Infobip's bulk ID await db.broadcast.update({ where: { id: createdBroadcast.id }, data: { status: 'SENT', // Initial status, delivery reports would refine this infobipBulkId: successResponse.bulkId, errorMessage: null, }, }) // Return the updated record return await db.broadcast.findUnique({ where: { id: createdBroadcast.id } }) } catch (error) { logger.error({ error, broadcastId: createdBroadcast.id }, 'Error during Infobip API call or DB update') // Attempt to mark as FAILED if not already done try { const current = await db.broadcast.findUnique({ where: {id: createdBroadcast.id }}); if (current && current.status !== 'FAILED') { await db.broadcast.update({ where: { id: createdBroadcast.id }, data: { status: 'FAILED', errorMessage: error.message || 'Unknown error during processing', }, }) } } catch (updateError) { logger.error({ updateError, broadcastId: createdBroadcast.id }, 'Failed to update broadcast status to FAILED after error'); } // Re-throw the original error to the client throw error } } // Note: updateBroadcast and deleteBroadcast might not be strictly necessary // for sending, but the SDL generator creates them. Keep or remove as needed. // Implement them if you want to edit/delete broadcast records. // export const updateBroadcast: MutationResolvers['updateBroadcast'] = ({ // id, // input, // }) => { ... } // export const deleteBroadcast: MutationResolvers['deleteBroadcast'] = ({ id }) => { ... }- We import
fetchfromcross-undici-fetchfor reliable fetching in Node.js. - We add input validation for the message and recipient list (including E.164 format check).
- We create the DB record first with
PENDINGstatus. - We structure the API request according to Infobip's
POST /sms/2/text/advancedendpoint. Note theAuthorization: App <YOUR_API_KEY>header format. - We handle both success and error responses from Infobip.
- On success, we update the DB record status to
SENTand store thebulkId. - On failure, we update the status to
FAILEDand store the error message. - Robust
try...catchblocks and logging are used throughout.
- We import
-
Define GraphQL Schema (SDL): Update the generated SDL file (
api/src/graphql/broadcasts.sdl.ts) to match our service logic and model.typescript// api/src/graphql/broadcasts.sdl.ts export const schema = gql` type Broadcast { id: Int! message: String! recipients: [String!]! status: String! infobipBulkId: String errorMessage: String createdAt: DateTime! updatedAt: DateTime! } type Query { broadcasts: [Broadcast!]! @requireAuth broadcast(id: Int!): Broadcast @requireAuth } input CreateBroadcastInput { message: String! recipients: [String!]! # Expect an array of phone numbers } # Input for updating is less likely needed for sending, but generated # input UpdateBroadcastInput { # message: String # recipients: [String!] # status: String # infobipBulkId: String # errorMessage: String # } type Mutation { createBroadcast(input: CreateBroadcastInput!): Broadcast! @requireAuth # updateBroadcast(id: Int!, input: UpdateBroadcastInput!): Broadcast! @requireAuth # deleteBroadcast(id: Int!): Broadcast! @requireAuth } `- We define the
Broadcasttype mirroring the Prisma model. CreateBroadcastInputexpectsmessageand an array ofrecipients.- We apply
@requireAuthto protect the endpoints. Remove@requireAuthif you are testing without authentication configured.
- We define the
3. Building the API Layer (RedwoodJS GraphQL)
RedwoodJS automatically builds the GraphQL API based on your SDL files and service implementations.
- Endpoints: Your GraphQL endpoint is available at
/.redwood/functions/graphql(or/api/graphqlin production builds). - Authentication/Authorization: We added
@requireAuthto the SDL. To make this work, you need to set up authentication (e.g., usingyarn rw setup auth dbAuthor another provider). If you keep@requireAuth, the guide implicitly assumes a standard RedwoodJS authentication setup (likedbAuth) is configured. See RedwoodJS Authentication documentation for setup instructions. For initial testing without auth setup, you can temporarily remove@requireAuthfrom the SDL. - Request Validation: Basic validation (presence, E.164 format) was added directly in the
createBroadcastservice. For more complex validation, consider libraries likeyuporzodwithin the service layer.
Testing the API Endpoint:
-
Start the Dev Server:
bashyarn rw dev -
Access GraphQL Playground: Open your browser to
http://localhost:8911/graphql(or the GraphQL endpoint URL shown in the terminal). -
Run the
createBroadcastMutation: Use the Playground interface to send a mutation. Replace placeholders with real (test) E.164 numbers and your message.graphqlmutation SendTestBroadcast { createBroadcast(input: { message: ""Hello from RedwoodJS + Infobip!"", recipients: [""+14155550100"", ""+14155550101""] # Use valid E.164 numbers }) { id message recipients status infobipBulkId errorMessage createdAt } } -
Check the Response: Observe the response in the Playground. If successful, you should see the created broadcast details with
status: ""SENT""and aninfobipBulkId. If there's an error (e.g., bad API key, invalid number), theerrorsarray will contain details, and the DB record should showstatus: ""FAILED"". Check the terminal runningyarn rw devfor detailed logs from the API service. -
Check Infobip Portal: Log in to Infobip and check the SMS logs or analytics section to confirm the messages were processed (or see specific errors there).
4. Integrating with Infobip (Covered in Service Implementation)
The core integration happens within the createBroadcast service function (api/src/services/broadcasts/broadcasts.ts), detailed in Section 2, Step 4.
Key Integration Points:
- API Endpoint:
POST {INFOBIP_BASE_URL}/sms/2/text/advanced - Authentication:
Authorization: App {INFOBIP_API_KEY}header. - Request Body: JSON payload containing
messagesarray, each withdestinations(array of{ to: number }) andtext. Optionally includesfrom(sender ID). - Credentials: Securely loaded from
.envusingprocess.env. - Error Handling: Parsing Infobip's success and error responses.
Security:
- API Keys: Never commit API keys directly into code. Use environment variables (
.envlocally, platform's secrets management in production). The.gitignorefile included with RedwoodJS correctly ignores.env. - No Client-Side Exposure: Infobip credentials are only accessed on the API side, never exposed to the browser.
Fallback Mechanisms: (Advanced)
For critical broadcasts, consider:
- Retries: Implement exponential backoff retries within the service if the initial Infobip request fails due to transient network issues or temporary Infobip unavailability (using libraries like
async-retry). Be cautious not to retry on definitive failures (like invalid credentials). - Alternative Provider: In high-availability scenarios, you might have a secondary SMS provider configured, switching if Infobip returns persistent errors. This adds significant complexity.
5. Implementing Error Handling, Logging, and Retries
-
Error Handling Strategy:
- Use
try...catchblocks for database operations and API calls. - Throw specific, user-friendly errors from the service (caught by GraphQL and returned to the client).
- Update the
Broadcastrecord status (FAILED) and storeerrorMessagein the database upon failure.
- Use
-
Logging:
- RedwoodJS has a built-in Pino logger (
api/src/lib/logger.js). - Use
logger.info(),logger.warn(),logger.error()within the service to log key events, input data (carefully, avoid logging sensitive info like full recipient lists if privacy is paramount), API responses, and errors. - Logs appear in the console running
yarn rw dev. In production, configure log destinations (e.g., stdout for platform logging services, or specific transports like Datadog/Sentry). - Example Log Analysis: If a broadcast fails, check the logs for entries around the time of the request. Look for
Infobip API request failederrors, the status code, and the response body provided by Infobip. Also check for database errors (Failed to create broadcast record,Failed to update broadcast status).
- RedwoodJS has a built-in Pino logger (
-
Retry Mechanisms: (Basic Example - see Service code for placement) A simple retry loop could be added, but for robust retries, a library is better.
typescript// Inside createBroadcast, around the fetch call (Conceptual) const MAX_RETRIES = 3; let attempt = 0; let response; let responseBody; while (attempt < MAX_RETRIES) { try { response = await fetch(/*...*/); // The fetch call responseBody = await response.json(); if (response.ok) { // Success - break loop break; } else if (response.status >= 500 && attempt < MAX_RETRIES - 1) { // Server error, retryable attempt++; logger.warn(`Infobip request failed (attempt ${attempt}/${MAX_RETRIES}). Retrying...`); await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, attempt))); // Exponential backoff continue; // Retry the loop } else { // Client error (4xx) or max retries reached - throw specific error logger.error(/*...*/) // Log the final failure // ... update DB to FAILED ... throw new Error(/*...*/) // Throw based on responseBody } } catch (networkError) { if (attempt < MAX_RETRIES - 1) { attempt++; logger.warn(`Network error during Infobip request (attempt ${attempt}/${MAX_RETRIES}). Retrying...`, networkError); await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, attempt))); continue; // Retry } else { logger.error(/*...*/) // Log final network failure // ... update DB to FAILED ... throw networkError; // Re-throw after max retries } } } // ... process successful response or handle final failure outside the loop ...This is a simplified example. Libraries like
async-retryhandle this more elegantly.
6. Creating the Database Schema and Data Layer (Covered in Section 2)
- Schema Definition: The
Broadcastmodel was defined inapi/db/schema.prisma. - Migrations: Managed using
yarn rw prisma migrate dev. Prisma tracks applied migrations. - Data Access: Redwood services use the Prisma Client (
dbimported fromsrc/lib/db) for type-safe database operations (db.broadcast.create,db.broadcast.findMany,db.broadcast.update, etc.). - Optimization: For this use case, indexing on
statusorcreatedAtmight be useful if querying large numbers of broadcasts frequently. Prisma automatically handles connection pooling.
7. Adding Security Features
- Input Validation: Implemented in the service (
createBroadcast) to check for empty messages/recipients and valid E.164 phone number formats. Sanitize inputs further if necessary (e.g., stripping unexpected characters, although Prisma helps prevent SQL injection). - Authentication/Authorization: Use Redwood's built-in auth (
@requireAuthin SDL) to ensure only logged-in users can trigger broadcasts. Implement role-based access control if different user types have different permissions. - Rate Limiting: Crucial for public-facing APIs or to prevent abuse. Implement rate limiting middleware (e.g., using
graphql-shieldor custom logic in your service/GraphQL setup) to limit requests per user/IP. Infobip also enforces its own API rate limits. - CSRF Protection: RedwoodJS has built-in CSRF protection for form submissions handled via its framework components. Ensure standard security headers (like those set by Redwood's default configuration) are in place.
- Common Vulnerabilities: Redwood's structure (Prisma for DB access, GraphQL layer) helps mitigate common web vulnerabilities like SQL Injection and Mass Assignment when used correctly. Always validate and sanitize user input.
8. Handling Special Cases
- Phone Number Formatting: Strictly enforce E.164 format (
+followed by country code and number, no spaces or dashes) as required by Infobip. The validation function provides a basic check. - Large Recipient Lists: Infobip's bulk endpoint (
/sms/2/text/advanced) is designed for multiple destinations in a single API call. However, extremely large lists (e.g., >10,000) might hit request size limits or timeouts. Consider:- Chunking: Split the recipient list into smaller batches (e.g., 1000 per batch) and send multiple requests sequentially or in parallel (carefully, respecting rate limits). You'd need to manage the status across multiple
infobipBulkIds. - Infobip Documentation: Check Infobip's specific limits for the bulk endpoint.
- Chunking: Split the recipient list into smaller batches (e.g., 1000 per batch) and send multiple requests sequentially or in parallel (carefully, respecting rate limits). You'd need to manage the status across multiple
- Character Limits & Encoding: Standard SMS messages have character limits (160 for GSM-7, 70 for UCS-2). Infobip handles splitting longer messages into multiple parts automatically (concatenated SMS), but this affects cost. Be mindful of message length in the UI. Special characters might force UCS-2 encoding, reducing the limit per part. Infobip's API can sometimes preview message encoding/parts.
- Sender ID Regulations: Sender ID rules (alphanumeric vs. numeric, pre-registration requirements) vary significantly by country. Ensure your chosen
INFOBIP_SENDER_IDis valid and approved for your target countries via the Infobip portal. Sending might fail otherwise. - Delivery Reports: (Advanced) This guide focuses on sending. To get detailed delivery status (Delivered, Undelivered, Failed) for each recipient, you need to:
- Configure a webhook URL in Infobip to receive delivery reports.
- Create a new RedwoodJS function (e.g.,
api/src/functions/infobipWebhook.ts) to handle incoming POST requests from Infobip. - Parse the delivery report payload.
- Update the status of individual messages/recipients (requires a more granular data model, perhaps linking
RecipientStatustoBroadcast).
9. Building the User Interface
Now, let's wire up the frontend components generated earlier.
-
Broadcast Form Component: Implement the form to capture recipients and the message.
typescript// web/src/components/BroadcastForm/BroadcastForm.tsx import { Form, Label, TextAreaField, Submit, FieldError, FormError, } from '@redwoodjs/forms' import { useMutation } from '@redwoodjs/web' import { toast } from '@redwoodjs/web/toast' import { gql } from '@redwoodjs/web' // Import gql import { QUERY as BroadcastsQuery } from 'src/components/BroadcastsCell' // Import the cell query // Define mutation in GraphQL syntax const CREATE_BROADCAST_MUTATION = gql` mutation CreateBroadcastMutation($input: CreateBroadcastInput!) { createBroadcast(input: $input) { id # Request needed fields back } } ` interface BroadcastFormProps { onSuccess?: () => void // Optional callback after successful submission } const BroadcastForm = ({ onSuccess }: BroadcastFormProps) => { const [createBroadcast, { loading, error }] = useMutation( CREATE_BROADCAST_MUTATION, { onCompleted: () => { toast.success('Broadcast sent successfully!') onSuccess?.() // Call the callback if provided }, onError: (error) => { toast.error(`Error sending broadcast: ${error.message}`) }, // Refetch the list of broadcasts after creating a new one refetchQueries: [{ query: BroadcastsQuery }], awaitRefetchQueries: true, // Ensure refetch completes before onCompleted fires fully } ) const onSubmit = (data) => { // Convert comma/newline separated recipients string into an array const recipientsArray = data.recipients .split(/[\n,]+/) // Split by newline or comma .map((r: string) => r.trim()) // Trim whitespace .filter((r: string) => r !== '') // Remove empty entries if (recipientsArray.length === 0) { toast.error('Please enter at least one recipient phone number.'); return; } const input = { message: data.message, recipients: recipientsArray } console.log('Submitting broadcast:', input) createBroadcast({ variables: { input } }) } return ( <div className=""rw-form-wrapper""> <Form onSubmit={onSubmit} error={error}> <FormError error={error} wrapperClassName=""rw-form-error-wrapper"" titleClassName=""rw-form-error-title"" listClassName=""rw-form-error-list"" /> <Label name=""recipients"" className=""rw-label"" errorClassName=""rw-label rw-label-error""> Recipients (E.164 format, one per line or comma-separated) </Label> <TextAreaField name=""recipients"" className=""rw-input"" errorClassName=""rw-input rw-input-error"" placeholder=""+14155550100,+14155550101"" validation={{ required: true }} rows={5} /> <FieldError name=""recipients"" className=""rw-field-error"" /> <Label name=""message"" className=""rw-label"" errorClassName=""rw-label rw-label-error""> Message </Label> <TextAreaField name=""message"" className=""rw-input"" errorClassName=""rw-input rw-input-error"" validation={{ required: true }} rows={3} // Example rows attribute /> <FieldError name=""message"" className=""rw-field-error"" /> <div className=""rw-button-group""> <Submit disabled={loading} className=""rw-button rw-button-blue""> {loading ? 'Sending...' : 'Send Broadcast'} </Submit> </div> </Form> </div> ) } export default BroadcastForm
Frequently Asked Questions
How to send bulk SMS with RedwoodJS?
You can send bulk SMS messages by creating a RedwoodJS application that integrates with the Infobip API. This involves setting up a RedwoodJS project, configuring your Infobip credentials, defining a database schema for storing broadcast information, and implementing the API service to interact with Infobip's SMS API.
What is RedwoodJS used for in SMS broadcasting?
RedwoodJS is a full-stack JavaScript framework chosen for its rapid development capabilities and clear architecture. It provides the structure for building both the frontend UI and the backend API for the bulk SMS application, handling user interactions, data management, and communication with the Infobip API.
Why use Infobip for bulk SMS messaging?
Infobip is a global communication platform chosen for its reliable SMS delivery, comprehensive API features, and ability to handle bulk messaging efficiently. The Infobip API allows sending messages to multiple recipients simultaneously and tracking their status.
When should I use a bulk SMS broadcast system?
Bulk SMS systems are ideal when you need to efficiently send targeted communications to multiple recipients at once. Use cases include sending notifications, alerts, marketing messages, or other time-sensitive information to a large group.
What database is used with RedwoodJS and Infobip?
RedwoodJS typically uses PostgreSQL by default, but Prisma, its ORM, supports other databases like MySQL and SQLite. PostgreSQL is a robust and scalable option for storing broadcast details and recipient information. You can easily configure your preferred database in your RedwoodJS project settings using Prisma.
How to set up Infobip API key in RedwoodJS?
Your Infobip API key should be stored as an environment variable called `INFOBIP_API_KEY` in your `.env` file. This keeps your credentials secure and separate from your codebase. The API side of your RedwoodJS application will then access this key when interacting with the Infobip API.
What is the format for Infobip recipient phone numbers?
Infobip requires recipient phone numbers to be in E.164 format. This international standard format includes a '+' sign followed by the country code and the phone number, with no spaces or special characters. For example, a US number would be formatted as +14155552671.
How does RedwoodJS handle large recipient lists for SMS?
While Infobip's API can handle bulk messages, very large lists might require splitting into smaller batches. RedwoodJS can implement chunking to send multiple requests to Infobip, managing status across the batches. Always consult Infobip’s documentation for their recommended best practices when dealing with large lists or high-throughput messaging.
Can I track SMS delivery status with this system?
Yes, you can track delivery status by implementing a webhook endpoint in your RedwoodJS application. Configure this webhook URL in your Infobip settings to receive delivery reports. Then create a serverless function to process these reports and update the status of each message accordingly.
How to handle SMS character limits with Infobip?
Standard SMS messages have character limits (160 for GSM-7, 70 for UCS-2). Infobip automatically splits longer messages, but it impacts cost. Design your UI to be mindful of length. Special characters might require UCS-2, reducing the limit.
What is the role of Prisma in the RedwoodJS SMS system?
Prisma acts as the Object-Relational Mapper (ORM) for your RedwoodJS application. It simplifies database interactions by providing a type-safe way to query and manipulate data in your chosen database (like PostgreSQL). Prisma handles database connections and schema migrations efficiently.
Where can I find my Infobip Base URL?
Your Infobip Base URL is specific to your account region and is crucial for correct API communication. You can find this URL in the Infobip API documentation or your account settings, often labeled as Base URL, Endpoint URL or similar. It is essential to use the correct regional URL.