Frequently Asked Questions
Use a Node.js framework like Fastify with the Sinch SMS API. Create a Fastify server and an API endpoint that accepts recipient numbers and a message, then uses the Sinch API to send the SMS messages in bulk. This setup is scalable and efficient for marketing campaigns, notifications, or alerts.
Fastify is a high-performance Node.js web framework known for its speed and extensibility. It's an ideal choice for building an SMS campaign sender because of its efficiency, built-in logging, and features like schema validation, which improve security and maintainability.
Environment variables securely store sensitive credentials like your Sinch Service Plan ID and API token. This protects your API keys from being exposed in your codebase, enhancing security and preventing accidental leaks.
Create separate modules for server logic (server.js) and Sinch API interaction (sinchService.js). Use .env to manage environment variables, and consider a database integration (e.g., Prisma) for logging campaign details. This promotes modularity, security, and maintainability.
Axios is a promise-based HTTP client used to make requests to the Sinch SMS REST API. It simplifies the process of sending HTTP POST requests with the necessary headers and data to trigger SMS messages through Sinch.
A message queue (like BullMQ) is recommended for very large recipient lists or when asynchronous processing is beneficial. It offloads SMS sending to a background process, preventing blocking and improving the responsiveness of your main API.
Sinch's API has rate limits to prevent abuse. Respect these limits by breaking large recipient lists into smaller batches and submitting them sequentially or with controlled concurrency. Monitor Sinch's response codes and adjust your sending rate accordingly.
Standard GSM-7 encoding allows 160 characters per SMS segment. Longer messages are split into multiple segments. Using non-GSM characters reduces the limit to 70 characters per segment. Sinch handles concatenation, but be aware of cost implications for multi-part messages.
Yes, Sinch offers delivery reports (DLRs) via webhooks. Configure a webhook URL in your Sinch dashboard, and create a corresponding route in your Fastify app to receive and process these status updates. This enables you to track message delivery success or failure.
Implement a suppression list (e.g., using a database) to store opted-out numbers. Before sending any campaign, always check the recipient list against this suppression list. Provide a clear opt-out mechanism (e.g., 'reply STOP') and handle incoming webhooks from Sinch to process opt-outs. This ensures compliance with regulations like TCPA and GDPR.
E.164 is an international telephone number format that includes the '+' sign followed by the country code and number (e.g., +15551234567). Using E.164 format ensures correct number formatting for global SMS delivery, crucial for reaching international recipients.
Leverage Fastify's schema validation to define the structure and data types of the request body. This automatically validates incoming requests, ensuring correct data format (like E.164 for phone numbers) and preventing issues related to invalid or malicious input.
Use structured logging (JSON format) with a library like Pino. Log campaign details (message, recipient count), status (pending, sent, failed), batch IDs from Sinch, and any error details. This aids debugging, monitoring, and analysis of your campaign performance.
Check for common error codes like 401 (Unauthorized – incorrect credentials), 400 (Bad Request – invalid format or parameters), and 5xx (Sinch server errors). Inspect the error.response.data from Axios for specific error details from Sinch and ensure credentials are correctly set in your environment variables.
For high-volume campaigns, use a message queue (e.g., BullMQ) for asynchronous processing. Consider caching frequently accessed data (like suppression lists) and optimize your database interactions. Implement Node.js clustering or use a process manager like PM2 if CPU usage becomes a bottleneck.
Building a Scalable SMS Campaign Sender with Fastify, Node.js, and Sinch
Learn how to build production-ready SMS marketing campaigns using Sinch REST API, Fastify, and Node.js. This comprehensive tutorial covers bulk SMS sending, TCPA compliance, rate limiting, database integration with Prisma, and deployment strategies for scalable messaging systems.
You'll create a complete API service that manages bulk SMS campaigns, handles opt-outs, implements suppression lists, and processes delivery reports. This guide addresses real-world requirements for SMS marketing including regulatory compliance, performance optimization, and error handling.
Project Overview and Goals
/batchesendpoint.POST /campaigns) that accepts JSON payload withrecipients(phone number array) andmessage(string), sends SMS via Sinch, logs attempts, and returns confirmation.System Architecture
(Note: Verify Mermaid diagram rendering on your publishing platform)
1. Setting up the Project
Initialize your Node.js project and install necessary dependencies.
Create Project Directory: Open your terminal and create a directory for the project:
Initialize Node.js Project: Create a
package.jsonfile with default settings:Install Dependencies: Install Fastify, Axios, and Dotenv:
Install Development Dependencies (Optional – Prisma): For database logging, install Prisma:
Initialize Prisma (Optional): Create
prismadirectory withschema.prismafile:DATABASE_URLin.envwith your database connection string.Create Project Structure: Set up directory structure:
src/server.js: Main application file with Fastify setup and routes.src/sinchService.js: Module for Sinch API interaction..env: Stores sensitive credentials. Never commit this file..gitignore: Specifies untracked files for Git.Configure
.gitignore: Add Node.js ignores and.env:Configure
.env: Add placeholders for Sinch credentials:.envkeeps sensitive data secure and out of your codebase.dotenvloads these intoprocess.env.2. Implementing Core Functionality (Sinch Service)
Encapsulate SMS sending logic in a dedicated service module.
Edit
src/sinchService.js: Create a function for Sinch's/batchesendpoint:/batchesendpoint structure.3. Building the API Layer with Fastify
Set up Fastify server and define the API endpoint.
Edit
src/server.js: Configure Fastify, load environment variables, and define routes:/campaignsvalidates requests, callssinchService, handles errors, and sends appropriate responses./healthendpoint enables monitoring and container orchestration.0.0.0.0(required for Docker).4. Integrating with Sinch (Credentials Setup)
Obtain API credentials from Sinch.
Navigate to Sinch Dashboard: Log in to your Sinch Customer Dashboard.
Find SMS API Credentials:
+15551234567).https://us.sms.api.sinch.comhttps://eu.sms.api.sinch.comhttps://ca.sms.api.sinch.comhttps://au.sms.api.sinch.comhttps://br.sms.api.sinch.comUpdate
.envFile: Add your credentials:.envsecure and ensure it's in.gitignore.5. Error Handling, Logging, and Retry Mechanisms
Your setup includes basic error handling and logging.
try...catchblocks catch exceptions inserver.jsandsinchService.js.sinchServicethrows errors for missing credentials or failed API calls.500on errors with generic messages, logging details internally.logger: trueuses Pino for efficient JSON-based logging.For production, implement retries with exponential backoff for transient errors (5xx responses).
Use
axios-retryor manual implementation withsetTimeout.Example Concept:
Testing Errors: Stop your network, provide invalid credentials, or use
toxiproxyto simulate network failures.6. Creating a Database Schema and Data Layer (Optional – Prisma)
If you initialized Prisma, define a schema for campaign logging.
Define Schema (
prisma/schema.prisma): Add campaign model:Create Database Migration: Generate and apply SQL migration:
Generate Prisma Client: Update Prisma client:
Integrate with Server Code:
src/server.js(import, initialization, database operations).7. Adding Security Features
Security is critical for any API.
Input Validation:
Rate Limiting:
Protect against abuse and brute-force attacks.
Install
@fastify/rate-limit:Register in
src/server.js:Adjust
maxandtimeWindowbased on usage patterns.Secrets Management:
.envanddotenvfor local development..env.HTTPS:
Helmet (Optional but Recommended):
@fastify/helmetfor security headers:8. Handling Special Cases for SMS Marketing
Real-world SMS marketing campaigns require specific considerations for compliance, deliverability, and cost optimization.
/batchesendpoint supports up to 1,000 recipients per batch (Sinch SMS API Release Notes).202 Acceptedresponse.+followed by country code and number) for international deliverability.POST /sinch/dlr) to handle webhook payloads.ngrokfor local testing).9. Implementing Performance Optimizations
Optimize for high-load scenarios.
Asynchronous Processing (Queues): Offload Sinch API calls to background job queue (BullMQ) to prevent blocking and improve response times.
Database Connection Pooling: Prisma manages this automatically.
Caching: Cache suppression lists (Redis/Memcached) for large-scale lookups with proper invalidation.
Node.js Clustering: Use
clustermodule or PM2 (pm2 start src/server.js -i max) to leverage multi-core processors.Load Testing: Use
k6,autocannon, orwrkto simulate traffic:Profiling: Use Node.js profiler (
node --prof) or Clinic.js (npm i -g clinic; clinic doctor -- node src/server.js) to analyze performance.10. Adding Monitoring, Observability, and Analytics
Monitor production service behavior.
Health Checks:
GET /healthprovides basic liveness check.Structured Logging:
Performance Metrics (Prometheus):
prom-clientfor application metrics:/metricsendpoint and track custom metrics (campaign rate, Sinch API latency).Error Tracking:
Dashboards:
Alerting:
/healthendpoint failures11. Troubleshooting and Caveats
Common issues and solutions:
401 Unauthorized: IncorrectSERVICE_PLAN_IDorAPI_TOKEN. Verify.envand dashboard. CheckAuthorization: Bearer <token>format.400 Bad Request: Invalid JSON structure, incorrect E.164 format, or parameter issues. Checkerror.response.data.403 Forbidden/Insufficient Funds: Account lacks funds or permissions. Check balance and settings.5xx Server Error: Temporary Sinch issue. Implement retries..envnot loaded: Ensuredotenv.config()is called early. Verify file location.DATABASE_URL: Check format, credentials, and host.async/awaitusagehostbinding (use0.0.0.0for Docker)Check detailed error messages in Fastify logs (
fastify.log.error) and Axios response data (error.response?.data) when troubleshooting.FAQ
How do I get Sinch SMS API credentials?
Log in to your Sinch Customer Dashboard, navigate to SMS → APIs, and locate your Service Plan ID. Click on the Service Plan ID to view your API token (generate one if needed). Find your provisioned phone number in the Numbers section – this is your sender number. Note your region (US, EU, CA, AU, BR) to construct the correct API base URL (e.g., https://us.sms.api.sinch.com).
What is the maximum number of recipients per Sinch SMS batch?
Sinch's
/batchesendpoint supports up to 1,000 recipients per batch request (Sinch SMS API Documentation). Thetofield accepts an array of 1–1000 phone numbers in E.164 format. This limit was increased from 100 to 1,000 on October 12, 2019 (Sinch Release Notes). For campaigns exceeding 1,000 recipients, split into multiple batch requests and implement rate limiting. Use background job queues (BullMQ with Redis) for asynchronous processing.How do I implement E.164 phone number validation in Fastify?
Use Fastify's JSON schema validation with regex pattern:
pattern: '^\\+[1-9]\\d{1,14}$'. This enforces E.164 format (+ followed by country code and number, 7–15 digits total). Schema validation runs automatically before route handler execution, rejecting malformed numbers with 400 Bad Request responses.What are TCPA and GDPR requirements for SMS marketing campaigns?
TCPA (US) requires prior express written consent, clear opt-out mechanisms (STOP keyword), mandatory communication hours (8:00 AM – 9:00 PM recipient local time), and suppression lists (FCC TCPA Guidelines). GDPR (EU) requires explicit consent, right to erasure, and data processing records. For US compliance, most businesses need 10DLC registration for SMS marketing campaigns. Implement suppression list database table, check recipients before every campaign, and process STOP keywords via Sinch webhooks. Penalties: $500–$1,500 per violation for TCPA (Tratta TCPA Guide) with no upper limit, and up to 4% of annual global revenue for GDPR violations.
How do I handle SMS character limits and encoding with Sinch?
GSM-7 encoding supports 160 characters per segment. Unicode/UCS-2 (emojis, special characters) reduces this to 70 characters per segment. Sinch automatically concatenates longer messages, but you're billed per segment. Set maxLength validation (e.g., 1600 characters for ~10 segments) in Fastify schema to prevent excessive costs. Monitor segment counts in production.
What retry strategy should I use for Sinch API failures?
Implement exponential backoff with 3–5 retry attempts for transient errors (5xx responses, network timeouts). Use axios-retry or manual implementation with setTimeout. Only retry retriable errors – don't retry 4xx client errors. Example: retry after 1 s, 2 s, 4 s delays. Log all retry attempts and final failures. Consider circuit breaker patterns for sustained Sinch API outages.
How do I process opt-outs and STOP keywords with Sinch?
Configure webhook URL in Sinch dashboard (Service Plan settings) to receive inbound SMS. Create Fastify route (POST /sinch/inbound) to handle webhook payloads. Parse message body for STOP, UNSUBSCRIBE, or QUIT keywords. Add sender's phone number to suppression list database immediately. Send confirmation SMS. Check suppression list before every campaign send.
What is the recommended database schema for SMS campaign tracking?
Use Prisma with Campaign model containing: id (autoincrement), createdAt (timestamp), message (text), recipientCount (integer), status (PENDING/SENT/FAILED), batchId (Sinch batch ID), and errorDetails (nullable string). Add SuppressionList model with: phoneNumber (E.164 string, unique primary key), reason (STOP/Complaint), createdAt, and updatedAt. Index phoneNumber for fast lookups.
How do I handle rate limiting for Sinch SMS API?
Install
@fastify/rate-limitto protect your API endpoint (e.g., max: 100 requests per minute per IP). For Sinch API rate limits, each service plan has specific messages-per-second limits (Sinch Rate Limits Documentation). A batch with 10 recipients counts as 10 messages. Batches queue in first-in-first-out order. Check account tier limits, implement batch splitting for large campaigns, and use job queues to control velocity. Monitor for 429 Too Many Requests responses and implement backoff.What monitoring and alerting should I implement for SMS campaigns?
Expose /metrics endpoint using prom-client for Prometheus. Track: API request rate and latency, Sinch API success/failure rates, campaign throughput, error rates by type, and queue depth. Set alerts for: error rate > 1%, API latency > 500 ms p95, Sinch API failures, health check failures, and low Sinch account balance. Use Grafana dashboards and Sentry for error tracking.