code examples
code examples
SMS Marketing Campaigns with Sinch API: Fastify & Node.js Guide
Build production-ready bulk SMS marketing campaigns using Sinch REST API, Fastify, and Node.js. Complete guide covering rate limiting, TCPA compliance, Prisma integration, opt-out handling, and deployment for scalable messaging systems.
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
- Goal: Create a Node.js backend service that exposes an API endpoint to send SMS messages to a list of recipients using the Sinch SMS API.
- Technology:
- Node.js: JavaScript runtime environment.
- Fastify: High-performance web framework chosen for speed, extensibility, and built-in logging with schema validation.
- Sinch SMS REST API: Third-party service for sending SMS via the
/batchesendpoint. - Axios: Promise-based HTTP client for Sinch API requests.
- Dotenv: Secure environment variable management.
- Prisma: (Optional) ORM for database interaction to log campaign details.
- Docker: Application containerization for deployment.
- Outcome: REST API endpoint (
POST /campaigns) that accepts JSON payload withrecipients(phone number array) andmessage(string), sends SMS via Sinch, logs attempts, and returns confirmation. - Prerequisites:
- Node.js and npm (or yarn) installed.
- Sinch account with SMS API credentials (Service Plan ID, API Token).
- Provisioned phone number within your Sinch account.
- Basic familiarity with Node.js, REST APIs, and terminal commands.
- (Optional) Docker installed for containerization.
- (Optional) Database (PostgreSQL, MySQL) for campaign logging with Prisma.
System Architecture
graph LR
A[Client / API Caller] -- HTTP POST /campaigns --> B(Fastify App);
B -- Send SMS Request --> C(Sinch SMS REST API);
C -- SMS Delivery --> D(Recipient Phones);
B -- Log Campaign Data --> E[(Database)];
C -- Response --> B;
B -- API Response --> A;
style B fill:#f9f,stroke:#333,stroke-width:2px
style C fill:#ccf,stroke:#333,stroke-width:2px
style E fill:#eee,stroke:#333,stroke-width:1px,stroke-dasharray: 5 5(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:
bashmkdir sinch-fastify-campaigns cd sinch-fastify-campaigns -
Initialize Node.js Project: Create a
package.jsonfile with default settings:bashnpm init -y -
Install Dependencies: Install Fastify, Axios, and Dotenv:
bashnpm install fastify axios dotenv -
Install Development Dependencies (Optional – Prisma): For database logging, install Prisma:
bashnpm install prisma @prisma/client --save-dev -
Initialize Prisma (Optional): Create
prismadirectory withschema.prismafile:bashnpx prisma init- Configuration: Update
DATABASE_URLin.envwith your database connection string.
- Configuration: Update
-
Create Project Structure: Set up directory structure:
bashmkdir src touch src/server.js src/sinchService.js .env .gitignoresrc/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:plaintext# .gitignore node_modules .env npm-debug.log* yarn-debug.log* yarn-error.log* dist coverage .DS_Store -
Configure
.env: Add placeholders for Sinch credentials:plaintext# .env # Sinch Credentials SINCH_SERVICE_PLAN_ID=YOUR_SERVICE_PLAN_ID SINCH_API_TOKEN=YOUR_API_TOKEN SINCH_NUMBER=YOUR_SINCH_PHONE_NUMBER # e.g., +15551234567 SINCH_REGION_URL=https://us.sms.api.sinch.com # Or eu., ca., au., br., etc. # Server Configuration PORT=3000 # Database (Optional – Adjust based on your provider) # Example for PostgreSQL DATABASE_URL="postgresql://user:password@host:port/database?schema=public"- Purpose:
.envkeeps sensitive data secure and out of your codebase.dotenvloads these intoprocess.env.
- Purpose:
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:javascript// src/sinchService.js import axios from 'axios'; // Load Sinch credentials from environment variables const SERVICE_PLAN_ID = process.env.SINCH_SERVICE_PLAN_ID; const API_TOKEN = process.env.SINCH_API_TOKEN; const SINCH_NUMBER = process.env.SINCH_NUMBER; const SINCH_API_BASE_URL = process.env.SINCH_REGION_URL || 'https://us.sms.api.sinch.com'; /** * Sends an SMS campaign batch using the Sinch REST API. * @param {string[]} recipients - Array of phone numbers in E.164 format (e.g., +15551234567). * @param {string} message - Text message body. * @returns {Promise<object>} - Response data from Sinch API. * @throws {Error} - Throws error if API call fails. */ async function sendSmsBatch(recipients, message) { if (!SERVICE_PLAN_ID || !API_TOKEN || !SINCH_NUMBER) { throw new Error('Sinch API credentials are not configured in .env file.'); } if (!recipients || recipients.length === 0) { throw new Error('Recipient list cannot be empty.'); } if (!message) { throw new Error('Message body cannot be empty.'); } const endpoint = `${SINCH_API_BASE_URL}/xms/v1/${SERVICE_PLAN_ID}/batches`; const payload = { from: SINCH_NUMBER, to: recipients, body: message, }; const config = { headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json', }, }; try { console.log(`Sending SMS batch to ${recipients.length} recipients via Sinch…`); const response = await axios.post(endpoint, payload, config); console.log('Sinch API response:', response.data); return response.data; } catch (error) { console.error('Error sending SMS via Sinch:', error.response?.data || error.message); throw new Error(`Sinch API request failed: ${error.response?.data?.text || error.message}`); } } export { sendSmsBatch };- Why this approach?
- Modularity: Separates Sinch logic from server code.
- Security: Loads credentials from environment variables.
- Error Handling: Includes validation and catches Axios errors.
- Clarity: Uses documented Sinch
/batchesendpoint structure.
- Why this approach?
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:javascript// src/server.js import Fastify from 'fastify'; import dotenv from 'dotenv'; import { sendSmsBatch } from './sinchService.js'; // Load environment variables dotenv.config(); // Initialize Fastify with logging const fastify = Fastify({ logger: true }); // API Route Definition const campaignSchema = { body: { type: 'object', required: ['recipients', 'message'], properties: { recipients: { type: 'array', items: { type: 'string', pattern: '^\\+[1-9]\\d{1,14}$' // E.164 format validation }, minItems: 1, }, message: { type: 'string', minLength: 1, maxLength: 1600 } } }, response: { 200: { type: 'object', properties: { message: { type: 'string' }, batchId: { type: 'string' }, recipientCount: { type: 'number' } } }, } }; fastify.post('/campaigns', { schema: campaignSchema }, async (request, reply) => { const { recipients, message } = request.body; // **Important:** Implement suppression list check here. // Filter out recipients who opted out. try { const sinchResponse = await sendSmsBatch(recipients, message); reply.code(200).send({ message: 'SMS campaign batch submitted successfully.', batchId: sinchResponse.id, recipientCount: recipients.length }); } catch (error) { fastify.log.error(`Campaign sending failed: ${error.message}`); reply.code(500).send({ error: 'Failed to send SMS campaign.', details: error.message }); } }); // Health Check Route fastify.get('/health', async (request, reply) => { return { status: 'ok', timestamp: new Date().toISOString() }; }); // Start Server const start = async () => { try { const port = process.env.PORT || 3000; await fastify.listen({ port: parseInt(port, 10), host: '0.0.0.0' }); } catch (err) { fastify.log.error(err); process.exit(1); } }; start();- Why Fastify? Schema validation automatically handles request validation, improving security. Built-in Pino logger provides high-performance logging.
- Route Logic:
/campaignsvalidates requests, callssinchService, handles errors, and sends appropriate responses. - Health Check:
/healthendpoint enables monitoring and container orchestration. - Server Start: Listens on configured port and host
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:
- Go to SMS → APIs.
- Locate your Service plan ID.
- Click your Service Plan ID name.
- Under API Credentials, find your API token. Click "Show" or generate one.
- In Numbers section, find your provisioned phone number (E.164 format:
+15551234567). - Note your Region (US, EU, CA, AU, BR). Common URLs:
- US:
https://us.sms.api.sinch.com - EU:
https://eu.sms.api.sinch.com - Canada:
https://ca.sms.api.sinch.com - Australia:
https://au.sms.api.sinch.com - Brazil:
https://br.sms.api.sinch.com
- US:
-
Update
.envFile: Add your credentials:plaintext# .env SINCH_SERVICE_PLAN_ID=YOUR_ACTUAL_SERVICE_PLAN_ID SINCH_API_TOKEN=YOUR_ACTUAL_API_TOKEN SINCH_NUMBER=+1XXXXXXXXXX SINCH_REGION_URL=https://<region>.sms.api.sinch.com- Security: Keep
.envsecure and ensure it's in.gitignore.
- Security: Keep
5. Error Handling, Logging, and Retry Mechanisms
Your setup includes basic error handling and logging.
- Error Handling:
try...catchblocks catch exceptions inserver.jsandsinchService.js.- Fastify schema validation catches malformed requests.
sinchServicethrows errors for missing credentials or failed API calls.- API route returns
500on errors with generic messages, logging details internally.
- Logging:
- Fastify's
logger: trueuses Pino for efficient JSON-based logging. - Logs include request details, errors, and informational messages.
- Fastify's
- Retry Mechanisms (Advanced):
-
For production, implement retries with exponential backoff for transient errors (5xx responses).
-
Use
axios-retryor manual implementation withsetTimeout. -
Example Concept:
javascript// Inside sinchService.js – Conceptual Retry Logic async function sendWithRetry(url, payload, config, logger = console, retries = 3, delay = 1000) { try { return await axios.post(url, payload, config); } catch (error) { if (retries > 0 && (!error.response || error.response.status >= 500)) { logger.warn(`Retrying Sinch request (${retries} left) after ${delay} ms delay…`); await new Promise(resolve => setTimeout(resolve, delay)); return sendWithRetry(url, payload, config, logger, retries - 1, delay * 2); } else { throw error; } } } -
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:prisma// prisma/schema.prisma generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model Campaign { id Int @id @default(autoincrement()) createdAt DateTime @default(now()) message String recipientCount Int status String // PENDING, SENT, FAILED batchId String? errorDetails String? } // Optional: Model for Suppression List /* model SuppressionList { phoneNumber String @id @unique reason String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } */ -
Create Database Migration: Generate and apply SQL migration:
bashnpx prisma migrate dev --name init_campaign_model -
Generate Prisma Client: Update Prisma client:
bashnpx prisma generate -
Integrate with Server Code:
- Uncomment Prisma lines in
src/server.js(import, initialization, database operations). - Adds database logging for campaign attempts and final status.
- Uncomment Prisma lines in
7. Adding Security Features
Security is critical for any API.
-
Input Validation:
- Fastify schema validation checks request structure, data types, and constraints (E.164 format, message length).
- Prevents injection attacks and malformed data.
-
Rate Limiting:
-
Protect against abuse and brute-force attacks.
-
Install
@fastify/rate-limit:bashnpm install @fastify/rate-limit -
Register in
src/server.js:javascript// src/server.js import rateLimit from '@fastify/rate-limit'; await fastify.register(rateLimit, { max: 100, timeWindow: '1 minute' }); -
Adjust
maxandtimeWindowbased on usage patterns.
-
-
Secrets Management:
- Use
.envanddotenvfor local development. - Production: Use platform secret management (Docker Secrets, Kubernetes Secrets, PaaS environment variables). Never commit
.env.
- Use
-
HTTPS:
- Always use HTTPS in production via reverse proxy (Nginx, Caddy) or hosting platform.
-
Helmet (Optional but Recommended):
- Use
@fastify/helmetfor security headers:
bashnpm install @fastify/helmetjavascript// src/server.js import helmet from '@fastify/helmet'; await fastify.register(helmet); - Use
8. Handling Special Cases for SMS Marketing
Real-world SMS marketing campaigns require specific considerations for compliance, deliverability, and cost optimization.
- Large Recipient Lists:
- Sinch's
/batchesendpoint supports up to 1,000 recipients per batch (Sinch SMS API Release Notes). - For campaigns exceeding 1,000 recipients, split into multiple batches, respecting API rate limits.
- Asynchronous Processing: Use background job queue (BullMQ with Redis) for large batches. Return immediate
202 Acceptedresponse.
- Sinch's
- Character Limits & Encoding:
- GSM-7 encoding: 160 characters per segment.
- Unicode/UCS-2 (emojis, special characters): 70 characters per segment.
- Sinch handles concatenation, but you're billed per segment.
- Set maxLength validation (e.g., 1600 characters) to prevent excessive costs.
- Opt-Outs & Compliance:
- Crucial: Follow TCPA (US), GDPR (EU) regulations. Only message consenting users.
- Provide clear opt-out mechanism (reply STOP).
- Handle incoming SMS webhooks from Sinch to process STOP keywords.
- Maintain suppression list (database table).
- Before sending, check recipients against suppression list and remove opted-out numbers.
- International Formatting:
- Use E.164 format (
+followed by country code and number) for international deliverability. - Schema validation enforces this with regex pattern validation.
- Use E.164 format (
- Delivery Reports (DLRs):
- Configure webhook URL in Sinch dashboard for delivery status updates.
- Create Fastify route (e.g.,
POST /sinch/dlr) to handle webhook payloads. - Process DLRs to update campaign logs with delivery status.
- Requires public endpoint (use
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:bash# Example using autocannon (npm i -g autocannon) autocannon -m POST -H "Content-Type: application/json" -b '{"recipients":["+15551234567"],"message":"Test Load"}' http://localhost:3000/campaigns -
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.- Enhance with database connectivity checks.
-
Structured Logging:
- Fastify's Pino logger outputs JSON for log aggregation (ELK Stack, Splunk, Datadog, Grafana Loki).
- Logs capture context (request IDs, batch IDs, user IDs).
-
Performance Metrics (Prometheus):
- Integrate
prom-clientfor application metrics:
bashnpm install prom-client- Expose
/metricsendpoint and track custom metrics (campaign rate, Sinch API latency). - Set up Prometheus scraping and Grafana visualization.
- Integrate
-
Error Tracking:
- Use Sentry, Bugsnag, or Datadog APM for real-time error capture with context.
-
Dashboards:
- Create Grafana dashboards showing:
- API request rate and latency
- API error rates (4xx, 5xx)
- Sinch API latency and errors
- Campaign throughput
- Database query performance
- System resource usage
- Create Grafana dashboards showing:
-
Alerting:
- Configure alerts for:
- High API error rate (> 1%)
- High API latency (> 500 ms p95)
- High Sinch API error rate
/healthendpoint failures- High resource utilization
- Job queue failures
- Configure alerts for:
11. Troubleshooting and Caveats
Common issues and solutions:
- Sinch Errors:
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.
- Configuration Issues:
.envnot loaded: Ensuredotenv.config()is called early. Verify file location.- Incorrect
DATABASE_URL: Check format, credentials, and host. - Firewall Issues: Ensure outbound requests to Sinch API and database. For webhooks, ensure Sinch can reach your endpoint.
- Code Errors:
- Variable name typos
- Incorrect
async/awaitusage - Schema validation errors
- Deployment Problems:
- Environment variables not set correctly (use platform secrets management)
- Incorrect
hostbinding (use0.0.0.0for Docker) - Port conflicts
- Compliance/Opt-Out Failures:
- Critical: Forgetting suppression list check can cause legal issues. Test thoroughly. Process incoming STOP messages correctly.
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 /batches endpoint supports up to 1,000 recipients per batch request (Sinch SMS API Documentation). The to field 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-limit to 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.
Frequently Asked Questions
How to send bulk SMS messages using Node.js?
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.
What is Fastify and why use it for SMS campaigns?
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.
Why are environment variables important for Sinch API integration?
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.
How to structure a Node.js project for sending SMS campaigns?
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.
What is the role of Axios in this SMS campaign setup?
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.
When should I use a message queue for sending SMS messages?
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.
How to handle Sinch API rate limits in Node.js?
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.
What is the character limit for SMS messages with Sinch?
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.
Can I get delivery reports for SMS messages sent via Sinch?
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.
How to handle opt-outs and comply with SMS regulations?
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.
What is E.164 format and why is it important for SMS?
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.
How to add input validation to my SMS campaign API endpoint?
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.
What are some best practices for logging SMS campaign attempts?
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.
How to troubleshoot common Sinch API integration issues?
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.
How to improve the performance of my SMS campaign sender?
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.