code examples
code examples
RedwoodJS SMS Marketing Campaigns with MessageBird: Complete Tutorial
Build production-ready SMS marketing campaigns with RedwoodJS and MessageBird. Step-by-step guide covering GraphQL API setup, bulk messaging, delivery tracking, and compliance for Node.js developers.
Build RedwoodJS Marketing Campaigns with MessageBird and Node.js
Build a production-ready SMS marketing campaign system using RedwoodJS, MessageBird SMS API, and Node.js. This comprehensive tutorial covers everything from initial RedwoodJS project setup through GraphQL API implementation, bulk SMS delivery, error handling, and production deployment. Perfect for developers building marketing automation, customer notifications, or promotional messaging systems.
Important Platform Update (2025): MessageBird rebranded as Bird in February 2024. The legacy MessageBird platform (dashboard.messagebird.com) shuts down March 31, 2025. For new implementations after this date, use the Bird next-gen platform (app.bird.com), though the MessageBird API and Node.js SDK remain functional during the transition. See Bird's migration documentation for details.
You'll build a RedwoodJS application with a GraphQL API endpoint that accepts campaign details (message content, recipient list) and uses the MessageBird Node.js SDK to dispatch SMS messages. This solves the common need to programmatically send bulk or targeted SMS campaigns for marketing or notifications.
Project Overview and Goals
<!-- DEPTH: Section needs practical context - missing estimated time-to-completion, skill level requirements, and real-world use case examples (Priority: High) --> <!-- GAP: Missing cost estimation for running campaigns and expected message throughput capabilities (Type: Substantive) -->Create a robust SMS marketing system within a RedwoodJS application that sends bulk SMS messages through MessageBird's API.
Key Goals:
- Setup: Initialize a RedwoodJS project configured for MessageBird integration
- API Layer: Create a GraphQL mutation to trigger SMS campaigns
- Integration: Integrate the MessageBird Node.js SDK into a RedwoodJS service
- Data Handling: Define a basic data model to track campaigns (optional but recommended)
- Security: Secure the API endpoint and handle API keys properly
- Error Handling: Implement basic error handling for API calls
- Deployment: Outline steps to deploy the application
- Verification: Provide methods to test and verify the implementation
Technologies Used:
| Technology | Purpose | Why Use It |
|---|---|---|
| RedwoodJS | Full-stack framework for React frontends and GraphQL backends | Integrated structure, excellent developer experience, opinionated defaults accelerate development |
| Node.js | Runtime environment for the RedwoodJS API side | Powers the backend GraphQL layer |
| MessageBird | SMS API provider | Reliable messaging services with developer-friendly APIs and SDKs |
| Prisma | ORM for database interactions | Built into RedwoodJS, type-safe database access |
| GraphQL | Query language for the API layer | Native to RedwoodJS, enables flexible data queries |
| Jest | Testing framework | Integrated with RedwoodJS for unit and integration tests |
System Architecture:
graph LR
A[User/Client Web App (React)] -- GraphQL Mutation --> B(RedwoodJS GraphQL API);
B -- Calls Service --> C{RedwoodJS Service (Node.js)};
C -- Uses SDK --> D[MessageBird API];
C -- CRUD Ops --> E[(Database via Prisma)];
D -- Sends SMS --> F[Recipient Phone];
E -- Stores Campaign Data --> C;Prerequisites:
- Node.js (v20 or v22 LTS): Node.js v18 reaches end-of-life in April 2025. Use v20 LTS ("Iron", maintenance until April 2026) or v22 LTS ("Jod", active support until April 2027). RedwoodJS v8+ requires Node.js v20.x minimum. Check your version:
node -v. Install from nodejs.org. - Yarn (v1.15 or later, or Yarn v4): RedwoodJS supports both Yarn Classic and Yarn Berry. Check installation:
yarn -v. - MessageBird/Bird Account: Sign up at messagebird.com or bird.com. For new projects after March 31, 2025, use the Bird next-gen platform.
- Access to a terminal or command prompt
- Basic understanding of RedwoodJS, GraphQL, and JavaScript/TypeScript
What You'll Build:
Create a functional RedwoodJS application endpoint that receives recipient numbers and a message, sends SMS messages via MessageBird, and provides feedback on success or failure.
How to Set Up Your RedwoodJS Project for SMS Integration
Initialize your RedwoodJS project, install the MessageBird SDK, and configure environment variables for SMS marketing campaigns.
-
Create RedwoodJS App: Open your terminal and run the RedwoodJS create command:
bashyarn create redwood-app ./redwood-messagebird-campaignsFollow the prompts (choosing JavaScript or TypeScript). This guide assumes JavaScript, but the steps work similarly for TypeScript.
-
Navigate to Project Directory:
bashcd redwood-messagebird-campaigns -
Install MessageBird SDK: Install the MessageBird Node.js SDK in the
apiworkspace:bashyarn workspace api add messagebird
-
Configure Environment Variables: RedwoodJS uses
.envfiles for environment variables. The.env.defaultsfile commits to git and holds default (non-sensitive) values. The.envfile (listed in.gitignore) holds sensitive keys.-
Create the
.envfile in the project root:bashtouch .env -
Add your MessageBird API Key and optionally a default originator to
.env. Retrieve the API key in Section 4.plaintext# .env # For projects created after March 31, 2025, obtain from Bird platform (app.bird.com > Settings > API Keys) # For legacy projects, obtain from MessageBird Dashboard (dashboard.messagebird.com > Developers > API access) MESSAGEBIRD_API_KEY=YOUR_LIVE_API_KEY MESSAGEBIRD_DEFAULT_ORIGINATOR=YourSenderID # Optional: Your registered sender ID or number -
Add placeholders to
.env.defaultsso team members know what's needed:plaintext# .env.defaults MESSAGEBIRD_API_KEY=replace_with_live_api_key_in_env MESSAGEBIRD_DEFAULT_ORIGINATOR= # Optional: Default sender ID or number -
Verify
.envappears in your root.gitignorefile (Redwood adds this by default).MESSAGEBIRD_API_KEYis your secret credential.MESSAGEBIRD_DEFAULT_ORIGINATORis the default sender ID or number when not provided in requests (see Section 4.3).
-
-
Understand Project Structure: RedwoodJS organizes code into:
api/: Backend code (GraphQL API, services, database schema)web/: Frontend React codescripts/: Utility scripts
Focus on the
api/directory, specificallyapi/src/graphql,api/src/services, andapi/db. This structure separates concerns and simplifies maintenance.
How to Implement the MessageBird Service Layer
Implement the core logic for interacting with MessageBird within a RedwoodJS service. Services contain your business logic on the API side.
-
Generate Service: Use the RedwoodJS CLI to generate a service file:
bashyarn rw g service marketingCampaignsThis creates
api/src/services/marketingCampaigns/marketingCampaigns.jsand related test files. -
Implement Sending Logic: Open
api/src/services/marketingCampaigns/marketingCampaigns.jsand add the logic:javascript// api/src/services/marketingCampaigns/marketingCampaigns.js import { requireAuth } from 'src/lib/auth' // Import Redwood's auth import { logger } from 'src/lib/logger' import { UserInputError } from '@redwoodjs/graphql-server' // Initialize the MessageBird client // Automatically reads the API key from process.env.MESSAGEBIRD_API_KEY const messagebird = require('messagebird').initClient(process.env.MESSAGEBIRD_API_KEY) export const sendMarketingCampaign = async ({ input }) => { // Ensure the user is authenticated (optional but recommended) // requireAuth() // Uncomment when auth is configured const { recipients, message, originator } = input // Validate input if (!recipients || recipients.length === 0) { throw new UserInputError('Recipients list cannot be empty.') } if (!message || message.trim() === '') { throw new UserInputError('Message content cannot be empty.') } // Determine originator: use input, fallback to env var, then generic default const effectiveOriginator = originator || process.env.MESSAGEBIRD_DEFAULT_ORIGINATOR || 'Campaign' if (!originator && !process.env.MESSAGEBIRD_DEFAULT_ORIGINATOR) { logger.warn('Originator not provided in input or environment, using generic default "Campaign". This may affect deliverability.') } else if (!originator && process.env.MESSAGEBIRD_DEFAULT_ORIGINATOR) { logger.info(`Using default originator from environment: ${process.env.MESSAGEBIRD_DEFAULT_ORIGINATOR}`) } const params = { originator: effectiveOriginator, recipients: recipients, // Array of phone numbers (E.164 format recommended) body: message, } logger.info({ recipients: params.recipients, originator: params.originator }, 'Attempting to send campaign via MessageBird') try { // Send message via MessageBird SDK (returns a Promise) const result = await messagebird.messages.create(params) logger.info({ response: result }, 'MessageBird response received') // Process the response // The result object contains batch submission details // Individual message statuses arrive later via webhooks (advanced setup) return { success: true, messageBirdId: result.id, // ID of the message batch status: `Campaign submitted to MessageBird with ${result.recipients.totalSentCount} messages.`, } } catch (error) { logger.error({ error }, 'Failed to send marketing campaign') // Extract specific error message const errorMessage = error.errors ? error.errors[0].description : error.message // Return structured error response for GraphQL layer return { success: false, messageBirdId: null, status: `Failed to send campaign: MessageBird API Error: ${errorMessage}`, } // Alternative: re-throw GraphQL-friendly error // throw new Error(`Failed to send campaign: MessageBird API Error: ${errorMessage}`) } } // Add other service functions here (e.g., getCampaignStatus)
**Why This Approach?**
* **Service Layer:** Encapsulates business logic for modularity and testability
* **Environment Variables:** Securely handles API keys and allows default originator
* **SDK Usage:** Leverages official MessageBird SDK for easier API interaction
* **Async/Await:** Uses modern JavaScript for asynchronous SDK calls
* **Input Validation:** Prevents sending empty requests
* **Logging:** Uses Redwood's built-in logger for visibility
* **Error Handling:** Includes `try...catch` for robustness with informative error messages
How to Build the GraphQL API for SMS Campaigns
Expose the service function via a GraphQL mutation to enable SMS campaign sending through your RedwoodJS API.
-
Define GraphQL Schema (SDL): Generate the GraphQL schema definition file:
bashyarn rw g sdl marketingCampaignsOpen
api/src/graphql/marketingCampaigns.sdl.jsand define the input type and mutation:graphql# api/src/graphql/marketingCampaigns.sdl.js export const schema = gql` """ Input required to send a marketing campaign. """ input SendCampaignInput { "List of recipient phone numbers in E.164 format (e.g., +14155552671)." recipients: [String!]! "The content of the SMS message." message: String! "Sender ID or phone number registered with MessageBird (optional, falls back to env default)." originator: String } """ Response after attempting to send a campaign. """ type CampaignStatus { "Indicates if the submission to MessageBird was successful." success: Boolean! "The ID assigned by MessageBird to this message batch (if successful)." messageBirdId: String "A status message describing the outcome." status: String! } type Mutation { """ Sends an SMS marketing campaign via MessageBird. """ sendMarketingCampaign(input: SendCampaignInput!): CampaignStatus! @skipAuth # Use @requireAuth instead of @skipAuth when authentication is configured # @requireAuth } `Schema Explanation:
SendCampaignInput: Defines the data structure for the mutation. Input types keep mutations clean.CampaignStatus: Defines the response structure returned by the mutation.sendMarketingCampaign: The mutation linking to the service function (Redwood maps this automatically by naming convention). Takes input and returns status.@skipAuth/@requireAuth: Redwood directives for access control. Use@requireAuthwhen authentication is implemented.@skipAuthmakes it publicly accessible (use cautiously in production).
-
Test the Endpoint (GraphQL Playground): RedwoodJS includes a GraphQL Playground.
-
Start the development server:
bashyarn rw dev -
Navigate to
http://localhost:8910/graphql -
Execute this mutation (replace placeholders):
graphqlmutation SendTestCampaign { sendMarketingCampaign(input: { recipients: ["+1XXXXXXXXXX", "+44YYYYYYYYYY"] # Use valid E.164 numbers message: "Hello from our RedwoodJS Campaign!" # originator: "YourSenderID" # Optional: Provide here or set MESSAGEBIRD_DEFAULT_ORIGINATOR in .env }) { success messageBirdId status } } -
Expect a success response:
json{ "data": { "sendMarketingCampaign": { "success": true, "messageBirdId": "mbid_xxxxxxxxxxxxxxxxxxxx", "status": "Campaign submitted to MessageBird with 2 messages." } } }Or an error response:
json{ "data": { "sendMarketingCampaign": { "success": false, "messageBirdId": null, "status": "Failed to send campaign: MessageBird API Error: authentication failed" } } }
-
How to Configure MessageBird API Credentials
Configure the MessageBird SDK correctly for proper SMS functionality. Understanding E.164 phone number formatting is essential for international SMS delivery.
- Obtain the API Key:
- Log in to your MessageBird Dashboard (or Bird platform after March 31, 2025)
- Navigate to "Developers" in the left menu
- Click "API access"
- Copy your Live API Key (Don't use the Test API Key for actual SMS sending – it requires the Live key)
- Click the "Copy" icon next to the Live API Key
-
Store the API Key Securely:
-
Paste the Live API Key into your
.envfile in the project root:plaintext# .env MESSAGEBIRD_API_KEY=YOUR_COPIED_LIVE_API_KEY # MESSAGEBIRD_DEFAULT_ORIGINATOR=YourSenderID # Optional -
Never commit your
.envfile or API key to version control (Git). Redwood's default.gitignoreincludes.env.
-
-
Understand Environment Variables:
Variable Required Description Format Obtain From MESSAGEBIRD_API_KEYYes Secret credential for MessageBird API authentication. SDK automatically reads this variable. String like live_xxxxxxxxxxxxxxxxxxxxMessageBird Dashboard > Developers > API access (or Bird platform > Settings > API Keys after March 31, 2025) MESSAGEBIRD_DEFAULT_ORIGINATORNo Default sender ID or phone number when not specified in API calls. If unset and not provided in mutation input, uses generic default like "Campaign", which may impact deliverability. String (e.g., "MyCompany", "+15551234567") MessageBird Dashboard > Numbers or Sender IDs
- Consider Fallback Mechanisms:
The current code doesn't implement complex fallback mechanisms for MessageBird outages. For critical systems:
- Retries: Implement retry logic (see next section)
- Alternative Providers: Abstract the messaging service to switch providers during significant MessageBird downtime (complex setup)
- Monitoring: Monitor MessageBird's status page and API error rates closely
How to Handle Errors and Implement Retry Logic
Handle SMS delivery failures gracefully in your RedwoodJS application with proper error handling and retry mechanisms.
-
Error Handling Strategy:
- Service Layer: Catch MessageBird SDK errors with
try...catch. Log detailed errors usinglogger.error(). Return structured error information or throw specific GraphQL errors (UserInputError,AuthenticationError, etc.). The current implementation returns a structured error within theCampaignStatusobject. - API Layer (GraphQL): Redwood automatically handles errors thrown from services and formats them for GraphQL responses. If the service returns a structured error object (like
CampaignStatus), that object appears in thedatafield. If the service throws an error, it appears in theerrorsfield. - MessageBird Errors: The SDK returns error objects. Inspect
error.errors(an array) for specific API validation issues (e.g., invalid recipient number, insufficient balance). Log these details. Current error handling extracts the first error description.
- Service Layer: Catch MessageBird SDK errors with
-
Configure Logging:
- RedwoodJS uses
pinofor logging. Uselogger.info(),logger.warn(),logger.error()within your service. - Log key events: campaign send initiation, parameters used (exclude sensitive data if necessary), MessageBird success responses, and detailed error information.
- In production, configure log levels and destinations appropriately (e.g., send logs to a log aggregation service). Customize Redwood's
api/src/lib/logger.jsas needed.
- RedwoodJS uses
-
Implement Retry Mechanisms (Basic Example): For transient network issues or temporary MessageBird problems, implement retries using libraries like
async-retry.-
Install
async-retry:bashyarn workspace api add async-retry -
Modify the service function:
javascript// api/src/services/marketingCampaigns/marketingCampaigns.js import { requireAuth } from 'src/lib/auth' import { logger } from 'src/lib/logger' import { UserInputError } from '@redwoodjs/graphql-server' import retry from 'async-retry' const messagebird = require('messagebird').initClient(process.env.MESSAGEBIRD_API_KEY) export const sendMarketingCampaign = async ({ input }) => { // ... (input validation and originator logic) ... const params = { originator: effectiveOriginator, recipients: recipients, body: message, } logger.info({ recipients: params.recipients, originator: params.originator }, 'Attempting to send campaign via MessageBird') try { // Wrap SDK call in retry logic const result = await retry( async (bail, attempt) => { // bail stops retrying immediately (e.g., for auth errors) logger.info(`Attempt ${attempt} to send campaign via MessageBird`) try { // SDK call returns a promise const response = await messagebird.messages.create(params) logger.info({ response, attempt }, 'MessageBird response received on attempt') return response } catch (err) { logger.error({ err, attempt }, 'MessageBird API error on attempt') // Check for non-retryable errors const isAuthError = (err.statusCode === 401) || (err.message && err.message.toLowerCase().includes('authentication failed')) || (err.errors && err.errors.some(e => e.code === 20)); if (isAuthError) { // Don't retry authentication failures bail(new Error('Authentication failed with MessageBird. Check API Key.')) return } // Throw to trigger retry for other errors throw err } }, { retries: 2, // Number of retries (total attempts = retries + 1) factor: 2, // Exponential backoff factor minTimeout: 1000, // Initial timeout in ms onRetry: (error, attempt) => { logger.warn(`Retrying campaign send (attempt ${attempt}) due to error: ${error.message}`) }, } ) // Process result and return success logger.info({ response: result }, 'MessageBird response received after retries') return { success: true, messageBirdId: result.id, status: `Campaign submitted to MessageBird with ${result.recipients.totalSentCount} messages.`, }; } catch (error) { logger.error({ error }, 'Failed to send marketing campaign after retries') const errorMessage = error.errors ? error.errors[0].description : error.message return { success: false, messageBirdId: null, status: `Failed to send campaign after retries: MessageBird API Error: ${errorMessage}`, }; } }
-
**Test Error Handling:**
* Temporarily provide an invalid `MESSAGEBIRD_API_KEY` in `.env` to test authentication errors and the `bail` condition
* Pass invalid recipient numbers in the GraphQL mutation to test MessageBird validation errors
* Simulate network issues if possible (less straightforward in local dev)
How to Track Campaign Data with Prisma Database
Store SMS campaign information to track history and delivery status using Prisma ORM in RedwoodJS.
-
Define Prisma Schema: Open
api/db/schema.prismaand add aCampaignmodel:prisma// api/db/schema.prisma datasource db { provider = "sqlite" // Or "postgresql", "mysql" url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" } // Campaign model model Campaign { id Int @id @default(autoincrement()) messageContent String originator String? // Sender ID used recipientCount Int messageBirdId String? @unique // MessageBird response ID status String // e.g., "Submitting", "Submitted", "Failed", "Completed" submittedAt DateTime @default(now()) // Add relation to User model if you have authentication // userId Int? // user User? @relation(fields: [userId], references: [id]) } // Example User model (if using dbAuth) // model User { // id Int @id @default(autoincrement()) // email String @unique // hashedPassword String // salt String // resetToken String? // resetTokenExpiresAt DateTime? // campaigns Campaign[] // }Database Provider Requirements: Prisma v6.x (included with RedwoodJS v8+) requires PostgreSQL 9.6 minimum. For production, use PostgreSQL 12+ for security patches and performance improvements. SQLite is suitable for development only.
* **ERD:** This schema has one model, `Campaign`. When linked to `User`, it creates a one-to-many relationship (one User has many Campaigns).
2. Run Database Migration: Apply schema changes to your database:
```bash
yarn rw prisma migrate dev
```
Enter a migration name when prompted (e.g., `add campaign model`). This creates/updates your database tables.
3. Update Service to Save Campaign:
Modify sendMarketingCampaign to create a Campaign record:
```javascript
// api/src/services/marketingCampaigns/marketingCampaigns.js
import { db } from 'src/lib/db' // Import Redwood's Prisma client
import { requireAuth } from 'src/lib/auth'
import { logger } from 'src/lib/logger'
import { UserInputError } from '@redwoodjs/graphql-server'
import retry from 'async-retry'
const messagebird = require('messagebird').initClient(process.env.MESSAGEBIRD_API_KEY)
export const sendMarketingCampaign = async ({ input }) => {
// ... validation and originator logic ...
const params = {
originator: effectiveOriginator,
recipients: recipients,
body: message,
}
let campaignRecord = null
try {
// Create initial campaign record before sending
campaignRecord = await db.campaign.create({
data: {
messageContent: params.body,
originator: params.originator,
recipientCount: params.recipients.length,
status: 'Submitting',
// userId: context.currentUser?.id // Add if using auth and relation
},
})
logger.info({ campaignId: campaignRecord.id }, 'Created initial campaign record')
// Retry logic wrapping MessageBird call
const result = await retry(
async (bail, attempt) => {
logger.info(`Attempt ${attempt} to send campaign via MessageBird`)
try {
const response = await messagebird.messages.create(params)
logger.info({ response, attempt }, 'MessageBird response received on attempt')
return response
} catch (err) {
logger.error({ err, attempt }, 'MessageBird API error on attempt')
const isAuthError = (err.statusCode === 401) ||
(err.message && err.message.toLowerCase().includes('authentication failed')) ||
(err.errors && err.errors.some(e => e.code === 20));
if (isAuthError) {
bail(new Error('Authentication failed with MessageBird. Check API Key.'))
return
}
throw err;
}
},
{
retries: 2,
factor: 2,
minTimeout: 1000,
onRetry: (error, attempt) => {
logger.warn(`Retrying campaign send (attempt ${attempt}) due to error: ${error.message}`)
},
}
);
// Update campaign record on success
const updatedCampaign = await db.campaign.update({
where: { id: campaignRecord.id },
data: {
status: 'Submitted',
messageBirdId: result.id,
},
})
logger.info({ campaignId: updatedCampaign.id, messageBirdId: result.id }, 'Updated campaign record to Submitted')
return {
success: true,
messageBirdId: result.id,
status: `Campaign submitted to MessageBird with ${result.recipients.totalSentCount} messages.`,
campaignId: campaignRecord.id,
}
} catch (error) {
logger.error({ error, campaignId: campaignRecord?.id }, 'Failed to send marketing campaign')
// Update campaign record on failure (if created)
if (campaignRecord) {
try {
await db.campaign.update({
where: { id: campaignRecord.id },
data: { status: 'Failed' },
})
logger.info({ campaignId: campaignRecord.id }, 'Updated campaign record to Failed')
} catch (dbError) {
logger.error({ dbError, campaignId: campaignRecord.id }, 'Failed to update campaign status to Failed after send error')
}
}
const errorMessage = error.errors ? error.errors[0].description : error.message
return {
success: false,
messageBirdId: null,
status: `Failed to send campaign: MessageBird API Error: ${errorMessage}`,
campaignId: campaignRecord?.id,
}
}
}
```
* **Data Access:** Redwood's `db` object is the Prisma client instance for all database operations. This approach logs campaign attempts even when MessageBird calls fail.
How to Secure Your SMS Campaign API
Implement security measures for APIs and user data in your RedwoodJS SMS marketing system.
- Input Validation & Sanitization:
- The service includes basic validation (checking for empty recipients/message)
- Phone Numbers: Ensure recipients use valid format (E.164 recommended:
+followed by country code and number). Add regex checks or use libraries likelibphonenumber-jsfor stricter validation - Message Content: Respect message length limits (standard SMS is 160 GSM-7 characters; longer messages split). Sanitize input to prevent injection attacks if message content displays elsewhere, though less critical for SMS sending
- Originator: If allowing user input, validate against allowed sender IDs/numbers in your MessageBird account or a predefined list
-
Authentication & Authorization:
- Uncomment
requireAuth()in the service (marketingCampaigns.js) when RedwoodJS authentication is configured (e.g.,yarn rw setup auth dbAuth). This ensures only logged-in users trigger campaigns - Add
@requireAuthto the GraphQL mutation definition (marketingCampaigns.sdl.js) instead of@skipAuth - Implement role-based access control if needed (e.g., only 'admin' or 'marketer' roles send campaigns by checking
context.currentUser.roles)
- Uncomment
-
API Key Security:
- Never commit API keys. Use environment variables (
.env) and ensure.envappears in.gitignore - Use distinct, restrictive API keys per environment (development, staging, production) via MessageBird settings
- Never commit API keys. Use environment variables (
-
Rate Limiting:
- Prevent abuse by limiting how often a user or IP address calls the
sendMarketingCampaignmutation - Use Redwood Shield (
yarn rw setup graphql-shield) or middleware (if using custom server file) to implement rate limiting (e.g., usingrate-limiter-flexible). MessageBird also enforces API rate limits
- Prevent abuse by limiting how often a user or IP address calls the
- Common Vulnerabilities:
- Insecure Direct Object References (IDOR): When adding features to view campaign status by ID, ensure users only view their own campaigns (check
userIdagainstcontext.currentUser.idin service logic) - Denial of Service (DoS): Rate limiting helps mitigate this. Avoid overly complex operations triggered by single API calls. Monitor costs associated with sending large message volumes
- Insecure Direct Object References (IDOR): When adding features to view campaign status by ID, ensure users only view their own campaigns (check
How to Handle SMS Special Cases and Compliance
<!-- DEPTH: Section needs more comprehensive compliance guidance covering regional regulations beyond US and EU (Priority: High) -->Address real-world SMS messaging nuances and compliance requirements for marketing campaigns.
-
Phone Number Formatting (E.164):
- MessageBird strongly recommends E.164 format (
+14155551234). While it may interpret local formats for some regions, E.164 is safer for international delivery - Add a pre-processing step in your service or frontend to normalize numbers to E.164 using libraries like
libphonenumber-js
- MessageBird strongly recommends E.164 format (
-
Character Limits & Encoding:
SMS Type Encoding Character Limit Notes Standard SMS GSM-7 160 characters Most common for English text Unicode SMS UCS-2 70 characters Required for emojis and non-Latin characters Concatenated SMS Varies Multiple segments Longer messages automatically split, consuming more credits per message Longer messages split automatically (concatenated SMS) by carriers/MessageBird, consuming more credits per effective message sent. Inform users about potential costs. MessageBird's API response (
result.recipients.totalSentCountvsresult.recipients.totalCount) provides clues; webhooks provide more detail.
- Opt-Out Handling / Compliance:
- Crucial for Marketing: Provide recipients an easy opt-out method (e.g., reply STOP). MessageBird manages opt-out lists automatically for specific numbers/sender IDs. Check their documentation on compliance features (like STOP keyword handling)
- Your application logic must respect opt-outs. Before sending campaigns, check an internal suppression list or query MessageBird's opt-out list if using their feature. Failure to handle opt-outs leads to legal issues (e.g., TCPA, GDPR) and carrier filtering
- Sender ID (Originator):
- Alphanumeric Sender IDs (e.g., "MyCompany") work in many countries but not all (e.g., US/Canada typically require pre-registered 10DLC, Toll-Free, or Short Codes). Check MessageBird's country-specific regulations. Unregistered Alphanumeric IDs where not permitted likely result in filtering or failure
- Using a phone number (VMN, TFN, etc.) as originator often enables two-way communication and is required in some regions
- Delivery Status (Webhooks – Advanced):
- The basic API call confirms submission to MessageBird, not final delivery to the handset. Statuses like
delivered,failed,expiredarrive later - For detailed, real-time delivery statuses, configure MessageBird webhooks (DLR – Delivery Reports):
- Create a webhook endpoint in your RedwoodJS app (using a Function:
yarn rw g function messageStatus). This function handles POST requests from MessageBird, validates them, and updates your database (e.g., updateCampaignstatus) - Configure the webhook URL in your MessageBird dashboard
- Secure the webhook endpoint (e.g., verify MessageBird's signature)
- Create a webhook endpoint in your RedwoodJS app (using a Function:
- The basic API call confirms submission to MessageBird, not final delivery to the handset. Statuses like
Frequently Asked Questions About RedwoodJS SMS Integration
What's the difference between MessageBird and Bird platforms?
MessageBird rebranded to Bird in February 2024. The legacy MessageBird platform (dashboard.messagebird.com) shuts down March 31, 2025. For new implementations after this date, use the Bird next-gen platform (app.bird.com) to access enhanced features and consolidated APIs. The MessageBird Node.js SDK and API endpoints remain compatible during the transition, but migrate your account and obtain new API keys from Bird's platform before the deadline.
What Node.js and RedwoodJS versions do I need?
RedwoodJS v8+ requires Node.js v20.x minimum. Node.js v18 reached end-of-life in April 2025. Use Node.js v20 LTS ("Iron", maintenance until April 2026) or v22 LTS ("Jod", active support until April 2027) for production deployments. Check your version with node -v and upgrade if necessary. RedwoodJS v8+ includes Prisma v6.x, which requires PostgreSQL 9.6+ (12+ recommended for production).
How do I send bulk SMS campaigns with RedwoodJS?
Create a GraphQL mutation (sendMarketingCampaign) that accepts an array of phone numbers and message content. Use the MessageBird Node.js SDK in your RedwoodJS service layer to send messages via messagebird.messages.create(). Pass recipients as an array in E.164 format (e.g., ["+14155551234", "+442071234567"]). Store campaign metadata in your Prisma database to track status and delivery. Implement rate limiting to prevent abuse and control costs.
How do I handle failed SMS deliveries in RedwoodJS?
Implement retry logic using the async-retry library in your RedwoodJS service. Configure exponential backoff (2-3 retries with increasing delays). Check MessageBird error codes to distinguish between retryable errors (network issues) and non-retryable errors (authentication failures, invalid numbers). Store delivery attempts in your database with status tracking ("Submitting", "Submitted", "Failed"). Configure MessageBird webhooks for real-time delivery status updates beyond initial submission confirmation.
Can I track SMS campaign analytics in my RedwoodJS app?
Yes. Create a Prisma Campaign model to store campaign metadata (message content, recipient count, submission time, MessageBird ID, status). Update the status field based on MessageBird webhook callbacks for detailed delivery tracking. Query your database using RedwoodJS services to generate analytics reports. Track metrics like total sent, delivery rate, failure reasons, and campaign performance over time. Use GraphQL queries to expose analytics data to your React frontend.
How much does MessageBird SMS cost for marketing campaigns?
MessageBird (now Bird) SMS pricing varies by destination country. US SMS typically costs $0.0075 – $0.0125 per message. International rates range from $0.02 – $0.15+ per message depending on destination. Longer messages exceeding 160 characters (GSM-7) or 70 characters (Unicode) split into multiple segments, consuming more credits. Check Bird's pricing page for current rates and consider enterprise pricing for high-volume campaigns (10,000+ messages/month).
<!-- GAP: Missing information about volume discounts and pricing tiers for different campaign sizes (Type: Substantive) -->How do I secure MessageBird API keys in RedwoodJS?
Store API keys in the .env file at your project root and add .env to your .gitignore. RedwoodJS uses @fastify/env for environment validation – define required keys in your envSchema.js. Never commit API keys to version control. Use distinct keys per environment (development, staging, production). For production, use secret management services like AWS Secrets Manager or HashiCorp Vault. Rotate API keys periodically (every 90 days) through Bird's dashboard.
Can I schedule SMS campaigns for future delivery with MessageBird?
Yes. MessageBird supports scheduling messages up to 30 days in advance using the scheduledDatetime parameter in the messages.create() call. Pass an ISO 8601 timestamp (e.g., "2025-12-25T10:00:00Z"). For campaigns beyond 24 hours, implement a two-stage system: store the scheduled time in your Prisma database immediately, then use a cron job or background worker to send the actual SMS when the time approaches. This reduces API load and allows cancellations or updates before sending.
How do I comply with SMS marketing regulations (TCPA, GDPR)?
Provide recipients an easy opt-out mechanism (e.g., reply STOP). MessageBird manages opt-out lists automatically – check their compliance documentation. Before sending campaigns, query your internal suppression list or MessageBird's opt-out list. Store user consent records in your database with timestamps and consent method. For US campaigns, register sender IDs through 10DLC or Toll-Free numbers. For EU campaigns under GDPR, obtain explicit consent and honor data deletion requests. Failure to comply results in legal penalties and carrier filtering.
<!-- GAP: Missing information about CASL (Canada), PECR (UK), and other regional compliance requirements (Type: Substantive) -->How do I test MessageBird integration locally in RedwoodJS?
Use MessageBird's Test API key for local development (obtain from dashboard.messagebird.com > Developers > API access). The Test key enables API testing without sending actual SMS or incurring charges. Start your RedwoodJS dev server with yarn rw dev and access the GraphQL Playground at http://localhost:8910/graphql. Execute test mutations with sample phone numbers. MessageBird returns simulated responses for testing. For production testing, use your own verified phone numbers with the Live API key to confirm actual SMS delivery before launching campaigns.