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.
This guide details how to build a robust system within a RedwoodJS application to send bulk SMS messages using the Sinch SMS API. We'll cover everything from project setup and database modeling to secure Sinch integration, background job considerations, error handling, and deployment.
The final application will enable users (likely administrators) to manage a list of contacts and send custom broadcast messages to all of them efficiently and reliably via Sinch. This solves the common business need for targeted mass communication via SMS for alerts, notifications, or marketing updates.
This guide is intended for developers familiar with JavaScript and full-stack frameworks, looking to integrate reliable bulk SMS capabilities into their RedwoodJS application. Prerequisites include Node.js (v18+ recommended), Yarn, a Sinch account with API credentials, and a provisioned Sinch phone number.
Core Technologies:
System Architecture:
By the end of this guide, you will have a functional RedwoodJS application capable of:
Note: This guide focuses primarily on the backend API implementation. While Section 11 outlines a potential frontend structure, detailed UI code is not provided.
1. Project Setup and Configuration
Let's initialize our 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 will 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 and Data Layer (Prisma)
We need to model our 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. API Layer: GraphQL SDL and Services
Now, let's 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. Adding Security Features
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 Special Cases
+
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. Implementing Performance Optimizations
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. Adding 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 and Caveats
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.