Frequently Asked Questions
Navigate to your RedwoodJS api directory using your terminal, then run 'yarn workspace api add @sinch/sdk-core'. This command adds the Sinch Node.js SDK to your project's API side, allowing you to interact with the Sinch SMS API.
Integrate the Sinch SMS API into your RedwoodJS application. This involves setting up your project, configuring your database with Prisma, creating necessary services and GraphQL endpoints, and then leveraging the Sinch Node.js SDK to send messages via the API. This setup allows you to manage contacts and broadcast messages efficiently.
RedwoodJS is the core framework for building the bulk SMS application. It provides structure for the backend API using GraphQL and services, connects to a database via Prisma, and offers a frontend framework with React. This allows for a streamlined development process.
Sinch provides a reliable and powerful SMS API along with an official Node.js SDK. The API allows for efficient bulk messaging by enabling sending to multiple recipients in a single API call, simplifying the integration and improving performance.
Prisma acts as the Object-Relational Mapper (ORM) connecting your RedwoodJS application to your chosen database (PostgreSQL or SQLite). It simplifies database interactions by defining models (like Contact and Broadcast) in a schema file, allowing you to query and manage data using JavaScript.
RedwoodJS uses a .env file in the project root. Create or open this file and add your Sinch credentials like Project ID, Key ID, Key Secret, and your Sinch virtual number. Make sure to add .env to your .gitignore file to protect your secrets.
Implement a try-catch block around the sinchClient.sms.batches.send API call. Inside the catch block, update the broadcast status to 'FAILED', log detailed error messages including the Sinch API response if available, and throw an error to notify the user.
Fetching all contacts at once using db.contact.findMany() is not scalable. Implement background jobs with Redwood's exec command or a task queue like BullMQ. The job should process contacts in batches, ensuring the application doesn't timeout when sending to thousands of recipients.
Create GraphQL SDL and corresponding service functions to handle contact creation. Implement input validation, specifically for phone numbers using E.164 formatting, and use Prisma to save the contact data to the database.
Login to the Sinch Customer Dashboard. Note your Project ID, navigate to Access Keys, and generate a new key pair (Key ID and Key Secret). Save the Key Secret securely as it's only displayed once. Find your provisioned Sinch phone number under Numbers > Your Virtual Numbers.
Use the BroadcastRecipient model when you need detailed tracking of individual message statuses within a broadcast. This join table helps manage retries, provide detailed reporting, and understand specific delivery failures, especially for large-scale SMS campaigns.
In this implementation, 'SENT' signifies that the Sinch API has accepted the batch send request. It doesn't guarantee delivery to the recipient. To track actual delivery, you must configure Sinch Delivery Report Webhooks.
Yes, the current design allows retrying a 'FAILED' broadcast by calling the sendBroadcast mutation again. For robust automated retries against transient issues, consider setting up a background job queue system with exponential backoff.
Use background jobs, database batching, and select specific data fields in queries. Instead of pulling all contacts at once, which can lead to timeouts, process them in smaller groups in the background and use Prisma's 'select' feature to only retrieve necessary data.
The guide emphasizes storing API keys securely as environment variables, validating phone number format using E.164, using Redwood's @requireAuth directive for access control, and handling errors robustly to prevent information leakage. It also recommends using additional measures like rate limiting for production deployments.
Building Bulk SMS Broadcast Systems with Sinch, RedwoodJS, and Node.js
This comprehensive guide shows you how to build a production-ready bulk SMS broadcasting system using the Sinch SMS API integrated with RedwoodJS. You'll learn to implement contact management, batch messaging, GraphQL APIs, Prisma database modeling, error handling, and deployment strategies for scalable SMS campaigns.
The final application enables administrators to manage contact lists and send custom broadcast messages to thousands of recipients efficiently via Sinch's batch API. This solves the critical business need for targeted mass communication through SMS – perfect for alerts, notifications, marketing campaigns, and emergency broadcasts.
This tutorial targets developers familiar with JavaScript and full-stack frameworks who want to integrate enterprise-grade bulk SMS capabilities into RedwoodJS applications. You'll need Node.js v18+, Yarn, a Sinch account with API credentials, and a provisioned Sinch virtual number to send messages.
Core Technologies:
System Architecture:
By completing this guide, you'll build a RedwoodJS application with:
Note: This guide emphasizes backend API implementation with RedwoodJS Services and GraphQL. Section 11 provides frontend structure overview, though detailed React UI code is beyond scope.
1. RedwoodJS Project Setup and Sinch Configuration
Initialize your RedwoodJS project and configure the necessary environment variables for Sinch.
1.1. Create RedwoodJS Project
Open your terminal and run:
Follow the prompts. Choose TypeScript if preferred, though examples here use JavaScript for broader accessibility. Select your preferred database (PostgreSQL recommended for production, SQLite for simplicity during development).
1.2. Install Sinch SDK
Navigate to the API workspace and add the Sinch Node.js SDK:
1.3. Configure Environment Variables
RedwoodJS uses a
.env
file in the project root for environment variables. Create or open it and add your Sinch credentials:How to Obtain Sinch Credentials:
SINCH_FROM_NUMBER
). Ensure it's SMS-enabled.Security: Never commit your
.env
file (or any file containing secrets) to version control. Ensure.env
is listed in your.gitignore
file (Redwood adds this by default). Use your deployment provider's secure environment variable management for production.1.4. Initialize Database
If you haven't already, set up your database connection string in
.env
and run the initial migration command:This ensures your database is ready for the schemas we'll define next.
2. Database Schema Design with Prisma for SMS Broadcasting
Model your contacts and broadcasts in the database.
2.1. Define Prisma Schema
Open
api/db/schema.prisma
and define the models:Contact
: Stores the essential information for each recipient. Using E.164 format forphoneNumber
is crucial for international compatibility.Broadcast
: Tracks the message content and the overall status of the broadcast job.BroadcastRecipient
(Optional but Recommended): This join table allows tracking the status of each individual recipient within a broadcast. This is vital for retries, reporting, and understanding failures, especially with large lists. If you only need overall status, you could simplify and remove this model. We'll include it for robustness.2.2. Apply Database Migrations
Run the migration command again to apply these schema changes to your database:
This updates your database schema and generates the corresponding Prisma Client types.
3. GraphQL API Layer: SDL Schema and Service Implementation
Define the GraphQL interface and implement the backend logic in Redwood Services.
3.1. Define GraphQL Schema (SDL)
Create SDL files to define the types, queries, and mutations for managing contacts and broadcasts.
api/src/graphql/contacts.sdl.ts
:api/src/graphql/broadcasts.sdl.ts
:@requireAuth
: This Redwood directive ensures only authenticated users can access these operations. We'll set up basic auth later. If your app doesn't need user login (e.g., internal tool), you can remove this, but securing API endpoints is generally recommended.createBroadcast
just saves the message content.sendBroadcast
actually triggers the interaction with Sinch.3.2. Implement Services
Generate the corresponding service files:
Now, implement the logic within these services.
api/src/services/contacts/contacts.ts
:api/src/services/broadcasts/broadcasts.ts
:SinchClient
is instantiated using credentials from environment variables. (Recommendation: Move tolib
for larger projects).SINCH_FROM_NUMBER
, and atry…catch
block around thesinchClient.sms.batches.send
call. Failures update the broadcast status toFAILED
and log detailed errors.logger
for informative messages. Logs Sinch API errors if available.sinchClient.sms.batches.send
, which is designed for sending the same message to multiple recipients efficiently.Broadcast
status (PENDING
→SENDING
→SENT
/FAILED
). TheSENT
status indicates acceptance by Sinch, not final delivery.BroadcastRecipient
Creation (Optional): Creates records linking contacts to the broadcast, storing the Sinchbatch_id
.recipientCount
): Dynamically calculates the recipient count for a broadcast query using the join table.findMany()
without batching for large lists.4. Error Handling, Logging, and Retry Mechanisms
try...catch
around the critical Sinch API call.FAILED
) on errors.logger.info
,logger.warn
,logger.error
within services. Redwood configures Pino logger by default.FAILED
broadcast by calling thesendBroadcast
mutation again.sendBroadcast
mutation: Instead of sending directly, queue a background job (see Section 7).exec
command with Faktory/Temporal) that supports automatic retries with backoff.FAILED
.5. Security Best Practices for SMS Broadcasting
dbAuth
is a simple starting point:@requireAuth
directive added to the SDLs in Section 3 now enforces that only logged-in users can perform contact/broadcast operations.ADMIN
,USER
) using Redwood's RBAC features (@requireAuth(roles: 'ADMIN')
) to restrict who can send broadcasts. See Redwood RBAC Docs.createContact
andcreateBroadcast
).createContact
TODO).createBroadcast
.rate-limiter-flexible
and Redis to limit requests per user or IP to your GraphQL endpoint, especially thesendBroadcast
mutation.SINCH_FROM_NUMBER
format.6. Handling SMS Special Cases and Compliance
+
followed by country code and number, no spaces or dashes) for all phone numbers stored and sent to Sinch. Validate on input (see Section 5).createBroadcast
). The Sinch API handles segmentation, but costs are per segment.subscribed
boolean field (defaulting totrue
) to theContact
model. Modify thefindMany
query insendBroadcast
to filter contacts (where: { subscribed: true }
). Implement a mechanism (e.g., handling replies like ""STOP"" via Sinch Inbound SMS webhooks - outside the scope of this sending guide) to update thesubscribed
status tofalse
. (Note: Schema modification and query updates are not shown in the provided code but would be necessary steps.)Contact
model has@unique
onphoneNumber
to prevent duplicates. Handle potential errors duringcreateContact
if a number already exists (Prisma will throw an error; catch it and provide a user-friendly message).7. Performance Optimization and Background Job Processing
select
in Prisma queries (findMany
,findUnique
) to fetch only the necessary fields (as done insendBroadcast
for contacts).@@index([field])
inschema.prisma
) to frequently queried fields (e.g.,status
onBroadcast
). The@unique
onphoneNumber
already creates an index.sms.batches.send
), which is efficient for the API call itself. The bottleneck for large lists is often the database query (db.contact.findMany()
).sendBroadcast
implementation fetching all contacts at once is not scalable.sendBroadcast
mutation: Instead of calling Sinch directly, it should:QUEUED
orPENDING
.broadcastId
.exec
: Useyarn rw exec <script-name>
with a task queue like Faktory or Temporal. See the RedwoodJS documentation for background jobs/workers.broadcastId
.skip
andtake
).SENDING
,SENT
,FAILED
) and potentially individualBroadcastRecipient
statuses.8. Monitoring, Observability, and Analytics
sendBroadcast
service function execution, especially the database query and the Sinch API call times.logger.error
(including Sinch API error details) are captured or shipped to your logging platform.SENT
vsFAILED
status in your DB).9. Troubleshooting Common Sinch Integration Issues
401 Unauthorized
from Sinch: IncorrectSINCH_PROJECT_ID
,SINCH_KEY_ID
, orSINCH_KEY_SECRET
. Double-check values in.env
or production environment variables. Ensure the key is active in the Sinch dashboard.403 Forbidden
from Sinch: The API key might lack permissions for the SMS API, or theSINCH_FROM_NUMBER
might not be provisioned correctly, enabled for the destination country, or properly formatted (needs E.164). Check Sinch dashboard settings (API keys, Number configuration, Allowed Countries).400 Bad Request
from Sinch: Invalid phone number format in theto
list (ensure all are E.164), message too long, missing required parameters (from
,to
,body
), or invalidSINCH_FROM_NUMBER
format. Check the error details logged from the Sinch SDK/API response.DATABASE_URL
is correct and the database server is running and accessible. Check firewall rules.sendBroadcast
takes too long (usually due to fetching/processing large contact lists), implement background jobs (Section 7). This is the most common scaling issue.429 Too Many Requests
. Implement delays or use background job queues with rate limiting features if hitting limits.to
array. For larger campaigns, split recipients into multiple batches.send_at
by default (configurable viaexpire_at
parameter, max 3 days).yarn rw prisma migrate deploy
(notmigrate dev
) in production to apply migrations without prompts.POST /xms/v1/{service_plan_id}/batches/dry_run
) to validate batch requests without sending actual SMS messages. This returns the number of recipients, message parts, and encoding information.10. Production Deployment Strategies for RedwoodJS
Deploy your RedwoodJS application to a hosting provider that supports both static frontend (Web side) and serverless/Node.js backend (API side).
Recommended Platforms:
Deployment Checklist:
SINCH_PROJECT_ID
,SINCH_KEY_ID
,SINCH_KEY_SECRET
,SINCH_FROM_NUMBER
, andDATABASE_URL
in your platform's environment settings.DATABASE_URL
.11. Frontend Implementation Overview with React and GraphQL
While this guide focuses on backend implementation, here's a brief overview of the frontend structure:
Contact Management UI (
web/src/pages/ContactsPage/ContactsPage.tsx
):Cell
pattern (automatically handles loading/error states).contacts
) and mutations (createContact
,deleteContact
).Broadcast Management UI (
web/src/pages/BroadcastsPage/BroadcastsPage.tsx
):sendBroadcast
mutation for a selected broadcast.Example Cell Pattern for Broadcasts:
For detailed frontend implementation, refer to the RedwoodJS Tutorial which covers Cells, Forms, and GraphQL integration comprehensively.
FAQ
How do I get Sinch API credentials for my RedwoodJS bulk SMS application?
Log in to your Sinch Customer Dashboard, locate your Project ID on the homepage, then navigate to Access Keys in the left menu. Generate a new access key to receive your Key ID and Key Secret (displayed only once – save immediately to your password manager). Find your SMS-enabled virtual number under Numbers → Your Virtual Numbers. Add these credentials to your RedwoodJS
.env
file asSINCH_PROJECT_ID
,SINCH_KEY_ID
,SINCH_KEY_SECRET
, andSINCH_FROM_NUMBER
(in E.164 format like+14155552671
).What is the maximum batch size for Sinch SMS API in RedwoodJS?
Sinch's official
/batches
API endpoint supports a maximum of 1,000 recipients per batch request in theto
array parameter. For campaigns exceeding 1,000 contacts, implement database pagination using Prisma'sskip
andtake
methods to fetch contacts in chunks of 1,000. Process each chunk as a separate Sinch batch request. For production scale (10,000+ contacts), use background job queues (BullMQ with Redis) to handle batching without serverless timeout issues.How do I validate E.164 phone numbers in RedwoodJS services before sending SMS?
Install the
libphonenumber-js
validation library withyarn workspace api add libphonenumber-js
. In your RedwoodJS service function, useparsePhoneNumber()
to validate and format numbers:This ensures all phone numbers are stored in standardized E.164 format (
+[country code][number]
) for international SMS delivery compatibility.What are SMS character encoding limits with Sinch batch messaging?
Sinch supports message bodies up to 2,000 characters total. However, SMS carriers automatically split messages into segments: GSM-7 encoding (standard Latin alphabet) allows 160 characters per segment, while UCS-2 encoding (Unicode characters, emojis, accented letters) reduces this to 70 characters per segment. A 320-character GSM-7 message sends as 3 segments and is billed as 3 SMS messages. Use Sinch's dry run endpoint (
POST /xms/v1/{service_plan_id}/batches/dry_run
) to preview message segmentation and character encoding before sending production campaigns.How do I debug Sinch API authentication errors in RedwoodJS?
401 Unauthorized
responses indicate credential mismatches. Verify your.env
file contains exact values from Sinch Dashboard:SINCH_PROJECT_ID
,SINCH_KEY_ID
, andSINCH_KEY_SECRET
with no extra whitespace or quotes. Check the access key status in your Sinch Dashboard under Access Keys – ensure it's active and has SMS API permissions enabled. The@sinch/sdk-core
automatically handles Bearer token authentication, so verify your credentials grant access to the SMS service specifically (not just other Sinch products like Voice or Verification).How can I prevent serverless function timeouts when sending bulk SMS in RedwoodJS?
Serverless platforms (Vercel, Netlify) enforce 10–60 second execution limits per function invocation. For broadcasts exceeding a few hundred contacts:
yarn workspace api add bullmq redis
sendBroadcast
mutation to enqueue a background job instead of sending directlyskip
/take
)This architecture decouples broadcast triggering from actual SMS sending, preventing timeouts while processing large contact lists. The GraphQL mutation returns immediately with "QUEUED" status while the worker handles Sinch API calls asynchronously.
Why use the BroadcastRecipient join table in the Prisma schema?
The
BroadcastRecipient
model enables granular per-recipient tracking within each broadcast campaign. It stores individual delivery status (PENDING
,SENT
,FAILED
), Sinch batch IDs for webhook correlation, delivery timestamps, and specific failure reasons. This is essential for:Without this join table, you only track overall broadcast status, losing visibility into which specific contacts succeeded or failed.
How do I configure Sinch delivery reports in RedwoodJS to track SMS delivery?
Set the
delivery_report
parameter when callingsinchClient.sms.batches.send
in your service:none
: No delivery reports (default, fastest)summary
: Single webhook callback with aggregate statisticsfull
: Single webhook with array of per-recipient delivery statusesper_recipient
: Individual webhook per message status change (use cautiously for large batches – generates many callbacks)Configure a webhook URL in your Sinch Dashboard (Service Plan settings) pointing to a RedwoodJS function endpoint like
/api/sinch-delivery-webhook
. Create the function to parse incomingDeliveryReport
payloads and updateBroadcastRecipient
records with final delivery status. See Sinch Delivery Reports API Reference for webhook payload schemas.How do I implement STOP keyword opt-out handling for SMS compliance in RedwoodJS?
Add a
subscribed
boolean field to your PrismaContact
model:Run
yarn rw prisma migrate dev
to apply the schema change. Update yoursendBroadcast
service to filter:db.contact.findMany({ where: { subscribed: true } })
. Implement a separate inbound SMS webhook handler (using Sinch Inbound SMS API) to detect "STOP", "UNSUBSCRIBE", or "QUIT" keywords and execute:db.contact.update({ where: { phoneNumber }, data: { subscribed: false } })
. This ensures TCPA (US), GDPR (EU), and PDPA (Singapore) compliance by honoring opt-out requests automatically.What are the best practices for testing Sinch SMS integration in RedwoodJS before production?
Follow this 6-step testing strategy:
POST /batches/dry_run
endpoint withper_recipient: true
to preview message segmentation without sending@sinch/sdk-core
:SINCH_PROJECT_ID
values for dev/staging to prevent accidental production sendslogger.info
output shows correct API parameters before enabling real sendsThis staged approach catches configuration errors, validates message formatting, and ensures proper error handling before exposing the system to customers.