Build RedwoodJS Marketing Campaigns with MessageBird and Node.js
This guide provides a comprehensive, step-by-step walkthrough for building a feature within a RedwoodJS application to send marketing campaign messages via the MessageBird API using Node.js on the backend. We'll cover everything from initial project setup to deployment and verification.
By the end of this tutorial, you will have a RedwoodJS application with a GraphQL API endpoint capable of accepting campaign details (message content, recipient list) and utilizing the MessageBird SDK to dispatch SMS messages. This solves the common need for applications to programmatically send bulk or targeted SMS campaigns for marketing or notifications.
Project Overview and Goals
We aim to create a robust system within a RedwoodJS application that enables sending SMS marketing messages through MessageBird.
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:
- RedwoodJS: A full-stack, serverless-friendly framework for React frontends and GraphQL backends (using Node.js and Prisma). Chosen for its integrated structure, developer experience, and opinionated defaults that accelerate development.
- Node.js: The runtime environment for the RedwoodJS API side.
- MessageBird: The third-party SMS API provider. Chosen for its reliable messaging services and developer-friendly APIs/SDKs.
- Prisma: The ORM used by RedwoodJS for database interactions.
- GraphQL: The query language for the API layer, intrinsic to RedwoodJS.
- Jest: The testing framework integrated with RedwoodJS.
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 (v18.x or later recommended – check RedwoodJS docs for specifics)
- Yarn (v1.15 or later)
- A MessageBird Account (Sign up at MessageBird)
- Access to a terminal or command prompt.
- Basic understanding of RedwoodJS, GraphQL, and JavaScript/TypeScript.
Expected Outcome:
A functional RedwoodJS application endpoint that can receive recipient numbers and a message, send SMS messages via MessageBird, and provide feedback on the operation's success or failure.
1. Setting up the Project
Let's start by creating a new RedwoodJS application and configuring the necessary environment.
-
Create RedwoodJS App: Open your terminal and run the RedwoodJS create command:
yarn create redwood-app ./redwood-messagebird-campaigns
Follow the prompts (choosing JavaScript or TypeScript). For this guide, we'll assume JavaScript, but the steps are very similar for TypeScript.
-
Navigate to Project Directory:
cd redwood-messagebird-campaigns
-
Install MessageBird SDK: Install the MessageBird Node.js SDK specifically in the
api
workspace.yarn workspace api add messagebird
-
Environment Variables: RedwoodJS uses
.env
files for environment variables. The.env.defaults
file is committed to git and holds default (non-sensitive) values. The.env
file (which should be in your.gitignore
) holds sensitive keys.-
Create the
.env
file in the project root:touch .env
-
Add your MessageBird API Key and optionally a default originator to
.env
. We'll retrieve the API key in Section 4.# .env MESSAGEBIRD_API_KEY=YOUR_LIVE_API_KEY MESSAGEBIRD_DEFAULT_ORIGINATOR=YourSenderID # Optional: Your registered sender ID or number
-
It's good practice to add placeholders to
.env.defaults
so others know what's needed:# .env.defaults MESSAGEBIRD_API_KEY=replace_with_live_api_key_in_env MESSAGEBIRD_DEFAULT_ORIGINATOR= # Optional: Default sender ID or number
-
Ensure
.env
is listed in your root.gitignore
file (Redwood typically adds this by default). TheMESSAGEBIRD_API_KEY
is your secret credential. TheMESSAGEBIRD_DEFAULT_ORIGINATOR
is the default sender ID or number if not provided in the request (see Section 4.3).
-
-
Project Structure: RedwoodJS has a specific structure:
api/
: Backend code (GraphQL API, services, database schema).web/
: Frontend React code.scripts/
: Utility scripts.
Our primary focus will be within the
api/
directory, specificallyapi/src/graphql
,api/src/services
, and potentiallyapi/db
. This structure separates concerns, making the application easier to manage.
2. Implementing Core Functionality (Service Layer)
The core logic for interacting with MessageBird will reside in a RedwoodJS service. Services are where your business logic lives on the API side.
-
Generate Service: Use the RedwoodJS CLI to generate a service file for handling marketing campaigns.
yarn rw g service marketingCampaigns
This creates
api/src/services/marketingCampaigns/marketingCampaigns.js
(and related files like tests). -
Implement Sending Logic: Open
api/src/services/marketingCampaigns/marketingCampaigns.js
and add the logic to send messages.// 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' // Import and initialize the MessageBird client // It 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 if you have auth setup const { recipients, message, originator } = input // Basic Validation 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 the originator: use input, fallback to env var, then to a 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 { // Use the MessageBird SDK to send the message(s) // Assuming the current MessageBird SDK returns a Promise directly. const result = await messagebird.messages.create(params) logger.info({ response: result }, 'MessageBird response received') // You might want to process the response further // e.g., check individual message statuses if needed, store results, etc. // The 'result' object contains details about the batch submission. // Individual message statuses might 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 a more specific error message if available const errorMessage = error.errors ? error.errors[0].description : error.message // Return a structured error response for the GraphQL layer return { success: false, messageBirdId: null, status: `Failed to send campaign: MessageBird API Error: ${errorMessage}`, } // Or re-throw a GraphQL-friendly error // throw new Error(`Failed to send campaign: MessageBird API Error: ${errorMessage}`) } } // Note: You might add other service functions here later (e.g., getCampaignStatus)
Why this approach?
- Service Layer: Encapsulates business logic, making the code modular and testable.
- Environment Variables: Securely handles the API key and allows a default originator.
- SDK Usage: Leverages the official MessageBird SDK for easier API interaction.
- Async/Await: Uses modern JavaScript for handling the asynchronous SDK call.
- Basic Validation: Prevents sending empty requests.
- Logging: Uses Redwood's built-in logger for visibility.
- Error Handling: Includes
try...catch
for robustness and provides informative error messages based on the SDK's potential error structure.
3. Building the API Layer (GraphQL)
Now, expose the service function via a GraphQL mutation.
-
Define GraphQL Schema (SDL): Create/edit the GraphQL schema definition file for marketing campaigns.
yarn rw g sdl marketingCampaigns
Open
api/src/graphql/marketingCampaigns.sdl.js
and define the input type and mutation.# 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 if authentication is set up and required # @requireAuth } `
Explanation:
SendCampaignInput
: Defines the structure of the data needed to call the mutation. Using input types keeps mutations clean.CampaignStatus
: Defines the structure of the response returned by the mutation.sendMarketingCampaign
: The mutation itself, linking to thesendMarketingCampaign
function in our service (Redwood maps this automatically by name convention). It takes the input and returns the status.@skipAuth
/@requireAuth
: Redwood's directive for controlling access. Use@requireAuth
once you have authentication implemented.@skipAuth
makes it publicly accessible (use with caution in production).
-
Testing the Endpoint (GraphQL Playground): RedwoodJS comes with a GraphQL Playground.
-
Start the development server:
yarn rw dev
-
Navigate to
http://localhost:8910/graphql
. -
Use the following mutation (replace placeholders):
mutation 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 } }
-
Execute the mutation. You should see a response like:
{ ""data"": { ""sendMarketingCampaign"": { ""success"": true, ""messageBirdId"": ""mbid_xxxxxxxxxxxxxxxxxxxx"", ""status"": ""Campaign submitted to MessageBird with 2 messages."" } } }
Or an error response if something went wrong:
{ ""data"": { ""sendMarketingCampaign"": { ""success"": false, ""messageBirdId"": null, ""status"": ""Failed to send campaign: MessageBird API Error: authentication failed"" } } }
-
4. Integrating with MessageBird (Configuration Details)
Ensuring the MessageBird SDK is correctly configured is crucial.
-
Obtaining the API Key:
- Log in to your MessageBird Dashboard.
- Navigate to the ""Developers"" section in the left-hand menu.
- Click on ""API access"".
- You will see your Live API Key. This is the key you need. Do not use the Test API Key unless you are specifically testing features that support it (basic SMS sending usually requires the Live key).
- Click the ""Copy"" icon next to the Live API Key.
-
Storing the API Key Securely:
-
As done in Step 1.4, paste the copied Live API Key into your
.env
file in the project root:# .env MESSAGEBIRD_API_KEY=YOUR_COPIED_LIVE_API_KEY # MESSAGEBIRD_DEFAULT_ORIGINATOR=YourSenderID # Optional
-
Never commit your
.env
file or your API key directly into your code or version control (Git). Redwood's default.gitignore
should already include.env
.
-
-
Understanding Environment Variables:
MESSAGEBIRD_API_KEY
: (Required) This is your secret credential for authenticating with the MessageBird API. The SDK (require('messagebird').initClient()
) automatically looks for this environment variable. Format: A string likelive_xxxxxxxxxxxxxxxxxxxx
. Obtain from: MessageBird Dashboard > Developers > API access.MESSAGEBIRD_DEFAULT_ORIGINATOR
: (Optional) You can set a default sender ID or phone number here if you don't want to specify it in every API call. If not set here and not provided in the mutation input, a generic default like ""Campaign"" might be used, which could impact deliverability. Format: A string (e.g., ""MyCompany"", ""+15551234567""). Obtain/Configure in: MessageBird Dashboard > Numbers or Sender IDs.
-
Fallback Mechanisms: The current code doesn't explicitly implement complex fallback mechanisms for MessageBird outages. For critical systems, consider:
- Retries: Implement retry logic (see next section).
- Alternative Providers: Abstract the messaging service and have the ability to switch to another provider if MessageBird experiences significant downtime (more complex setup).
- Monitoring: Closely monitor MessageBird's status page and API error rates.
5. Error Handling, Logging, and Retry Mechanisms
Robust applications handle failures gracefully.
-
Error Handling Strategy:
- Service Layer: Catch errors from the MessageBird SDK (
try...catch
). Log detailed errors usinglogger.error()
. Return structured error information or throw specific GraphQL errors (UserInputError
,AuthenticationError
, etc.) for the API layer. The current implementation returns a structured error within theCampaignStatus
object. - API Layer (GraphQL): Redwood automatically handles errors thrown from services and formats them for the GraphQL response. If the service returns a structured error object (like
CampaignStatus
), that object is returned in thedata
field. If the service throws an error, it appears in theerrors
field of the GraphQL response. - MessageBird Errors: The SDK passes error objects. Inspect
error.errors
(an array) for specific API validation issues (e.g., invalid recipient number, insufficient balance). Log these details. The current error handling attempts to extract the first error description.
- Service Layer: Catch errors from the MessageBird SDK (
-
Logging:
- RedwoodJS uses
pino
for logging. Uselogger.info()
,logger.warn()
,logger.error()
, etc., within your service. - Log key events: initiation of a campaign send, parameters used (excluding sensitive data if necessary), success responses from MessageBird, and detailed error information.
- In production, configure log levels and destinations appropriately (e.g., send logs to a log aggregation service). Redwood's
api/src/lib/logger.js
can be customized.
- RedwoodJS uses
-
Retry Mechanisms (Basic Example): For transient network issues or temporary MessageBird problems, a simple retry can help. You can use libraries like
async-retry
or implement a basic loop.-
Install
async-retry
:yarn workspace api add async-retry
-
Modify the service function:
// 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' // Import 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 the SDK call in the retry logic const result = await retry( async (bail, attempt) => { // bail is a function to stop retrying immediately (e.g., for auth errors) logger.info(`Attempt ${attempt} to send campaign via MessageBird`) try { // Assuming the SDK call returns a promise const response = await messagebird.messages.create(params) logger.info({ response, attempt }, 'MessageBird response received on attempt') return response // Resolve the promise for retry logic } catch (err) { logger.error({ err, attempt }, 'MessageBird API error on attempt') // Check for specific non-retryable errors. // Note: Error structure might vary. Check SDK docs or log the error object. // This checks common patterns for authentication issues. const isAuthError = (err.statusCode === 401) || (err.message && err.message.toLowerCase().includes('authentication failed')) || (err.errors && err.errors.some(e => e.code === 20 /* auth error code */)); if (isAuthError) { // Don't retry on authentication failure bail(new Error('Authentication failed with MessageBird. Check API Key.')) return // Important: return after calling bail } // For other errors, throw to trigger retry throw err; // Re-throw the original error to let `async-retry` handle it } }, { 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}`, }; } }
Testing Errors:
- Temporarily provide an invalid
MESSAGEBIRD_API_KEY
in.env
to test authentication errors (and thebail
condition). - Pass invalid recipient numbers in the GraphQL mutation input to test MessageBird validation errors (which should likely trigger retries unless the error is immediate/non-transient).
- Simulate network issues if possible (less straightforward in local dev).
-
6. Creating a Database Schema (Optional)
Storing campaign information can be useful for tracking history and status.
-
Define Prisma Schema: Open
api/db/schema.prisma
and add aCampaign
model.// api/db/schema.prisma datasource db { provider = ""sqlite"" // Or ""postgresql"", ""mysql"" url = env(""DATABASE_URL"") } generator client { provider = ""prisma-client-js"" } // Add this Campaign model model Campaign { id Int @id @default(autoincrement()) messageContent String originator String? // Sender ID used recipientCount Int messageBirdId String? @unique // Store the ID from MessageBird response status String // e.g., ""Submitting"", ""Submitted"", ""Failed"", ""Completed"" (if using webhooks) 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[] // Relation to campaigns // }
- ERD: This simple schema has one model,
Campaign
. If linked to aUser
, it would be a one-to-many relationship (one User can have many Campaigns).
- ERD: This simple schema has one model,
-
Database Migration: Apply the schema changes to your database.
yarn rw prisma migrate dev
Enter a name for the migration when prompted (e.g.,
add campaign model
). This creates/updates your database tables. -
Update Service to Save Campaign: Modify the
sendMarketingCampaign
service to create aCampaign
record.// 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 attempting to send 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 the 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, // Return internal ID too } } catch (error) { logger.error({ error, campaignId: campaignRecord?.id }, 'Failed to send marketing campaign') // Update campaign record on failure (if it was 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, used for all database operations. This approach ensures campaign attempts are logged even if the MessageBird call fails.
- Data Access: Redwood's
7. Adding Security Features
Security is paramount, especially when dealing with APIs and user data.
-
Input Validation & Sanitization:
- The service already includes basic validation (checking for empty recipients/message).
- Phone Numbers: Ensure recipients are in a valid format (E.164 is recommended:
+
followed by country code and number). You might add a regex check or use a library (likelibphonenumber-js
) for stricter validation. - Message Content: Be mindful of message length limits (standard SMS is 160 GSM-7 characters, longer messages are split). Consider sanitizing input to prevent injection attacks if the message content could ever be displayed elsewhere, though less critical for SMS sending itself.
- 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
) once you have RedwoodJS authentication set up (e.g.,yarn rw setup auth dbAuth
). This ensures only logged-in users can trigger campaigns. - Add
@requireAuth
to the GraphQL mutation definition (marketingCampaigns.sdl.js
) instead of@skipAuth
. - You might add role-based access control if needed (e.g., only 'admin' or 'marketer' roles can send campaigns, checking
context.currentUser.roles
).
- Uncomment
-
API Key Security:
- Reiterate: Never commit API keys. Use environment variables (
.env
) and ensure.env
is in.gitignore
. - Use distinct, restrictive API keys per environment (development, staging, production) if possible via MessageBird settings.
- Reiterate: Never commit API keys. Use environment variables (
-
Rate Limiting:
- Prevent abuse by limiting how often a user or IP address can call the
sendMarketingCampaign
mutation. - Consider using Redwood Shield (
yarn rw setup graphql-shield
) or a middleware approach (if using custom server file) to implement rate limiting (e.g., usingrate-limiter-flexible
). MessageBird also has its own API rate limits.
- Prevent abuse by limiting how often a user or IP address can call the
-
Common Vulnerabilities:
- Insecure Direct Object References (IDOR): If you add features to view campaign status by ID, ensure users can only view their own campaigns (check
userId
againstcontext.currentUser.id
in the service logic). - Denial of Service (DoS): Rate limiting helps mitigate this. Avoid overly complex operations triggered by a single API call. Be mindful of costs associated with sending large numbers of messages.
- Insecure Direct Object References (IDOR): If you add features to view campaign status by ID, ensure users can only view their own campaigns (check
8. Handling Special Cases
Real-world messaging involves nuances.
-
Phone Number Formatting (E.164):
- MessageBird strongly recommends the E.164 format (
+14155551234
). While it might correctly interpret local formats for some regions, relying on E.164 is safer for international delivery. - Consider adding a pre-processing step in your service or frontend to normalize numbers to E.164 if possible using libraries like
libphonenumber-js
.
- MessageBird strongly recommends the E.164 format (
-
Character Limits & Encoding:
- Standard SMS: 160 characters (GSM-7 encoding).
- Unicode SMS (e.g., emojis): 70 characters (UCS-2 encoding).
- Longer messages are automatically split (concatenated SMS) by carriers/MessageBird, consuming more credits per effective message sent. Inform users about potential costs. MessageBird's API response (
result.recipients.totalSentCount
vsresult.recipients.totalCount
) might give clues, and webhooks provide more detail.
-
Opt-Out Handling / Compliance:
- Crucial for Marketing: Provide recipients an easy way to opt out (e.g., reply STOP). MessageBird can manage opt-out lists automatically for specific numbers/sender IDs. Check their documentation on compliance features (like STOP keyword handling).
- Your application logic should respect opt-outs. Before sending, you might need to check an internal suppression list or query MessageBird's opt-out list if using their feature. Failure to handle opt-outs can lead to legal issues (e.g., TCPA, GDPR) and carrier filtering.
-
Sender ID (Originator):
- Alphanumeric Sender IDs (e.g., ""MyCompany"") are supported 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. Using unregistered Alphanumeric IDs where not permitted will likely lead to filtering or failure.
- Using a phone number (VMN, TFN, etc.) as the originator often allows 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
,expired
arrive later. - For detailed, real-time delivery statuses, you need to configure MessageBird webhooks (DLR - Delivery Reports). This involves:
- Creating a new webhook endpoint in your RedwoodJS app (using a Function:
yarn rw g function messageStatus
). This function needs to handle POST requests from MessageBird, validate them, and update your database (e.g., update theCampaign
status). - Configuring the webhook URL in your MessageBird dashboard.
- Securing the webhook endpoint (e.g., checking MessageBird's signature).
- Creating a new 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