This guide provides a complete walkthrough for building a production-ready service using Fastify (a high-performance Node.js web framework) to send SMS messages via the Infobip API. We will cover everything from initial project setup to deployment and monitoring best practices.
By the end of this tutorial, you will have a functional Fastify application with an API endpoint capable of sending SMS messages programmatically. This solves common needs like sending notifications, alerts, verification codes, or marketing messages directly from your backend application.
Project Overview and Goals
- Goal: Build a simple, robust Fastify API endpoint (
POST /send-sms
) that accepts a recipient phone number, sender ID, and message text, and uses the Infobip API to send the SMS. - Problem Solved: Enables programmatic SMS sending from a backend application, abstracting the direct Infobip API interaction into a clean service layer.
- Technologies:
- Node.js: The runtime environment.
- Fastify: A low-overhead, high-performance web framework for Node.js, chosen for its speed, extensibility, and developer experience.
- Infobip API: The third-party service used for sending SMS messages.
- @infobip-api/sdk: The official Infobip Node.js SDK simplifies interaction with the Infobip API.
- dotenv: For managing environment variables securely.
- fastify-rate-limit: For protecting the API endpoint.
- Final Outcome: A Fastify application with a single POST endpoint (
/send-sms
) that securely and reliably sends SMS messages via Infobip, including basic validation, error handling, and logging. - Prerequisites:
- Node.js (v18 or later recommended) and npm/yarn installed.
- An active Infobip account (a free trial account works, but has limitations).
- Important Free Trial Limitation: Free Infobip accounts can only send SMS messages to the phone number you verified during registration. Attempts to send to other numbers will fail. This is a very common source of issues when starting.
- Basic understanding of Node.js, APIs, and asynchronous JavaScript.
- Access to a terminal or command prompt.
System Architecture
The basic flow is straightforward:
graph LR
Client[Client Application] -- HTTP POST Request --> FastifyAPI[Fastify API Endpoint (/send-sms)]
FastifyAPI -- Uses SDK --> InfobipSDK[@infobip-api/sdk]
InfobipSDK -- HTTPS API Call --> Infobip[Infobip SMS API]
Infobip -- Sends --> SMS[SMS Message]
SMS -- Delivered --> UserDevice[User's Phone]
Infobip -- API Response --> InfobipSDK
InfobipSDK -- Returns Response/Error --> FastifyAPI
FastifyAPI -- HTTP Response --> Client
(Note: Rendering of the above diagram depends on the platform's support for Mermaid syntax.)
1. Setting up the Project
Let's initialize our Node.js project and install the necessary dependencies.
-
Create Project Directory: Open your terminal and create a new directory for the project, then navigate into it.
mkdir fastify-infobip-sms cd fastify-infobip-sms
-
Initialize npm Project: Run
npm init
and follow the prompts. You can accept the defaults for most questions.npm init -y
This creates a
package.json
file. -
Install Dependencies: We need Fastify, the Infobip SDK, and
dotenv
for environment variables.npm install fastify @infobip-api/sdk dotenv
fastify
: The core web framework.@infobip-api/sdk
: Simplifies interactions with the Infobip API.dotenv
: Loads environment variables from a.env
file intoprocess.env
.
-
Install Development Dependencies (Optional but Recommended): Tools like
nodemon
help during development by automatically restarting the server on file changes.npm install --save-dev nodemon
-
Configure
package.json
Scripts and Enable ES Modules: Add scripts to yourpackage.json
for easily running and developing your application. Crucially, add""type"": ""module""
to enable theimport
/export
syntax used in the code examples.// package.json { // ... other fields like name, version ""main"": ""index.js"", ""type"": ""module"", // <-- Add this line to enable ES Modules ""scripts"": { ""start"": ""node index.js""_ ""dev"": ""nodemon index.js""_ // Use nodemon for development ""test"": ""echo \""Error: no test specified\"" && exit 1"" }_ // ... other fields like dependencies_ devDependencies }
-
Create Project Structure: Set up a basic directory structure for organization.
mkdir routes config touch index.js routes/sms.js config/infobip.js .env .env.example .gitignore
index.js
: The main application entry point.routes/
: Directory to hold route definitions.routes/sms.js
: File specifically for SMS-related routes.config/
: Directory for configuration files.config/infobip.js
: Configuration specific to the Infobip client..env
: Stores sensitive environment variables (API keys_ etc.). Do not commit this file..env.example
: A template showing required environment variables. Commit this file..gitignore
: Specifies intentionally untracked files that Git should ignore.
-
Configure
.gitignore
: Addnode_modules
and.env
to your.gitignore
file to prevent committing them.# .gitignore node_modules .env *.log
-
Set up Environment Variables: Define the required environment variables in
.env.example
and.env
.# .env.example # Infobip Credentials (Get from your Infobip account dashboard) INFOBIP_API_KEY=your_infobip_api_key_here INFOBIP_BASE_URL=your_infobip_base_url_here # e.g._ xyz.api.infobip.com # Server Configuration PORT=3000 HOST=0.0.0.0
Copy
.env.example
to.env
and fill in your actual Infobip API Key and Base URL. You can find these in your Infobip account dashboard_ typically under API Keys management. The Base URL is specific to your account.- Purpose: Using environment variables keeps sensitive credentials out of your codebase_ enhancing security and making configuration easier across different environments (development_ staging_ production).
2. Implementing Core Functionality
Now_ let's implement the core logic for sending SMS messages using the Infobip SDK within a Fastify route.
-
Configure Infobip Client: Create a reusable Infobip client instance.
// config/infobip.js import { Infobip_ AuthType } from '@infobip-api/sdk'; import dotenv from 'dotenv'; dotenv.config(); // Load .env variables const apiKey = process.env.INFOBIP_API_KEY; const baseUrl = process.env.INFOBIP_BASE_URL; if (!apiKey || !baseUrl) { console.error('Error: Infobip API Key or Base URL not found in environment variables.'); console.error('Ensure INFOBIP_API_KEY and INFOBIP_BASE_URL are set in your .env file.'); process.exit(1); // Exit if configuration is missing } const infobipClient = new Infobip({ baseUrl: baseUrl_ apiKey: apiKey_ authType: AuthType.ApiKey_ // Specify API Key authentication }); export default infobipClient;
- Why: This centralizes the Infobip client setup. We load credentials from the environment_ validate their presence_ and export a ready-to-use client instance.
process.exit(1)
ensures the application fails fast if critical configuration is missing.
- Why: This centralizes the Infobip client setup. We load credentials from the environment_ validate their presence_ and export a ready-to-use client instance.
-
Create the SMS Sending Route: Define the Fastify route handler that will receive requests and trigger the SMS sending process.
// routes/sms.js import infobipClient from '../config/infobip.js'; async function smsRoutes(fastify_ options) { // Define schema for request body validation const sendSmsSchema = { body: { type: 'object'_ required: ['to'_ 'from'_ 'text']_ properties: { to: { type: 'string'_ description: 'Recipient phone number in international format (e.g._ 447123456789)'_ // Consider using a library like libphonenumber-js for more robust validation if needed }_ from: { type: 'string'_ description: 'Sender ID (alphanumeric_ 3-11 chars; or numeric_ 3-16 digits)'_ minLength: 3_ maxLength: 16 // Looser max length to cover both types }_ text: { type: 'string'_ description: 'The content of the SMS message'_ minLength: 1_ maxLength: 1600 // Practical upper limit for multi-part SMS }_ }_ }_ response: { 200: { type: 'object'_ properties: { bulkId: { type: 'string' }_ messages: { type: 'array'_ items: { type: 'object'_ properties: { messageId: { type: 'string' }_ to: { type: 'string' }_ status: { type: 'object'_ properties: { groupId: { type: 'number' }_ groupName: { type: 'string' }_ id: { type: 'number' }_ name: { type: 'string' }_ description: { type: 'string' } } } } } } } }_ // Defining explicit error response schemas (e.g._ 400_ 500) is good practice for documentation // but not strictly required for functionality. } }; fastify.post('/send-sms'_ { schema: sendSmsSchema }_ async (request_ reply) => { const { to, from, text } = request.body; request.log.info(`Received request to send SMS to: ${to} from: ${from}`); try { const infobipResponse = await infobipClient.channels.sms.send({ messages: [{ destinations: [{ to: to }], from: from, text: text, }], }); request.log.info({ msg: 'SMS submitted to Infobip successfully', infobipResponse: infobipResponse.data }, 'Infobip Response'); // Return the relevant part of the Infobip response reply.code(200).send(infobipResponse.data); } catch (error) { request.log.error({ msg: 'Failed to send SMS via Infobip', err: error }, 'Infobip API Error'); // Check if it's an Infobip API error with details if (error.response && error.response.data) { reply.code(error.response.status || 500).send({ error: 'Infobip API Error', details: error.response.data }); } else { // General server error reply.code(500).send({ error: 'Internal Server Error', message: error.message }); } } }); } export default smsRoutes;
- Why: This file defines the
/send-sms
endpoint usingfastify.post
. - Schema Validation: We use Fastify's built-in JSON schema validation (
sendSmsSchema
) to automatically validate the request body. This ensures required fields (to
,from
,text
) are present and meet basic type/format criteria before our handler logic runs. This is crucial for robustness and security. - SDK Usage: It calls
infobipClient.channels.sms.send
with the payload constructed from the request body. The SDK handles the underlying HTTP request, authentication, and URL construction. - Logging: Uses
request.log
for contextual logging (info for success, error for failures). - Error Handling: A
try...catch
block handles potential errors during the API call. It attempts to parse specific Infobip errors fromerror.response.data
for better client feedback, falling back to a generic 500 error.
- Why: This file defines the
-
Set up the Main Fastify Application: Configure the main server file to load environment variables, register routes, and start listening.
// index.js import Fastify from 'fastify'; import dotenv from 'dotenv'; import smsRoutes from './routes/sms.js'; // Import rate limit plugin if you installed it // import fastifyRateLimit from '@fastify/rate-limit'; dotenv.config(); // Load .env variables early const fastify = Fastify({ logger: true // Enable built-in Pino logger }); // --- Plugin Registration --- // Example: Register Rate Limiting (Install with: npm install @fastify/rate-limit) /* await fastify.register(fastifyRateLimit, { max: 100, // max requests per window timeWindow: '1 minute' }); fastify.log.info('Rate limit plugin registered'); */ // --- Route Registration --- fastify.register(smsRoutes, { prefix: '/api/v1' }); // Prefix routes for versioning fastify.log.info('SMS routes registered under /api/v1'); // --- 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; const host = process.env.HOST || '0.0.0.0'; // Listen on all available network interfaces await fastify.listen({ port: parseInt(port, 10), host: host }); // Ensure port is a number // fastify.log.info(`Server listening on port ${port}`); // listen() logs this already } catch (err) { fastify.log.error(err); process.exit(1); } }; start();
- Why: This is the entry point. It initializes Fastify with logging enabled, registers our SMS routes under a versioned prefix (
/api/v1
), includes a basic/health
check endpoint (good practice for monitoring), and starts the server, handling potential startup errors. Rate limiting is commented out but shown as an example of plugin registration.
- Why: This is the entry point. It initializes Fastify with logging enabled, registers our SMS routes under a versioned prefix (
3. Building a Complete API Layer
Our core functionality already includes the API endpoint. Let's refine it based on best practices.
-
Authentication/Authorization: For this simple example, we assume the API is called internally or protected by an upstream gateway. For external access, you would implement mechanisms like API Key validation (passed via headers), JWT tokens, or OAuth. This would typically involve Fastify hooks or plugins (
fastify-auth
,fastify-jwt
). -
Request Validation: We already implemented robust request validation using Fastify's schema feature in
routes/sms.js
. This prevents malformed requests from reaching our core logic. -
API Endpoint Documentation: The schema definition in
routes/sms.js
serves as basic documentation. For more comprehensive documentation, consider integratingfastify-swagger
which can automatically generate an OpenAPI (Swagger) specification from your routes and schemas. -
Testing with
curl
/ Postman:Request (
curl
): Replace placeholders with your actual data and running server address.curl -X POST http://localhost:3000/api/v1/send-sms \ -H ""Content-Type: application/json"" \ -d '{ ""to"": ""YOUR_RECIPIENT_PHONE_NUMBER"", ""from"": ""YourSenderID"", ""text"": ""Hello from Fastify and Infobip!"" }'
- Important: If using an Infobip free trial,
YOUR_RECIPIENT_PHONE_NUMBER
must be the phone number you registered with Infobip.YourSenderID
should adhere to Infobip's rules (alphanumeric 3-11 chars, or numeric 3-16).
Success Response Example (JSON, Status 200): (Structure based on Infobip API documentation)
{ ""bulkId"": ""some-unique-bulk-id-generated-by-infobip"", ""messages"": [ { ""to"": ""YOUR_RECIPIENT_PHONE_NUMBER"", ""status"": { ""groupId"": 1, ""groupName"": ""PENDING"", ""id"": 26, ""name"": ""PENDING_ACCEPTED"", ""description"": ""Message sent to next instance"" }, ""messageId"": ""some-unique-message-id-generated-by-infobip"" } ] }
Error Response Example (JSON, Status 400 - Validation Error):
{ ""statusCode"": 400, ""error"": ""Bad Request"", ""message"": ""body should have required property 'text'"" }
Error Response Example (JSON, Status ~500 - Infobip API Error): (Example: Invalid API Key)
{ ""error"": ""Infobip API Error"", ""details"": { ""requestError"": { ""serviceException"": { ""messageId"": ""UNAUTHORIZED"", ""text"": ""Invalid login details"" } } } }
(Note: Exact error structure from Infobip might vary slightly)
- Important: If using an Infobip free trial,
4. Integrating with Infobip
We've already set up the core integration using the SDK.
- Configuration: Done via
.env
file and loaded inconfig/infobip.js
.INFOBIP_API_KEY
: Your secret API key for authentication. Obtain from Infobip Dashboard -> Developers -> API Keys. Treat this like a password.INFOBIP_BASE_URL
: Your account-specific API endpoint URL. Also found on the API Keys page in the Infobip dashboard. Format is usuallyyour-unique-id.api.infobip.com
.
- API Key Security: Storing the key in
.env
and adding.env
to.gitignore
prevents accidental commits. In production, use your deployment environment's secret management system (e.g., AWS Secrets Manager, Kubernetes Secrets, Heroku Config Vars). - Dashboard Navigation (General):
- Log in to your Infobip account.
- Navigate to the ""Developers"" or ""API"" section (exact naming may vary).
- Find the ""API Keys"" management page.
- Copy your Base URL shown on this page.
- Create a new API Key if needed, giving it appropriate permissions (e.g., SMS sending). Copy the generated key immediately (it might not be shown again).
- Fallback Mechanisms: For simple SMS sending, a robust fallback is complex. Options include:
- Retry Logic: Implement client-side retries (see Section 5).
- Alternative Provider: (Beyond scope).
- Queueing: Place SMS requests in a queue (e.g., Redis, RabbitMQ) and have a separate worker process them. If Infobip fails, the worker can retry or flag the message for manual intervention. This adds significant complexity. For this guide, we rely on direct calls with basic error handling.
5. Error Handling, Logging, and Retry Mechanisms
- Error Handling Strategy:
- Use Fastify's schema validation for input errors (400 Bad Request).
- Use
try...catch
around external API calls (Infobip). - Parse specific errors from the Infobip SDK/API response (
error.response.data
) when available, returning relevant status codes (e.g., 401 Unauthorized, 403 Forbidden, 5xx Service Unavailable) and details. - Return a generic 500 Internal Server Error for unexpected issues.
- Never expose raw stack traces or sensitive error details to the client in production.
- Logging:
- Fastify's built-in Pino logger (
logger: true
) provides efficient JSON logging. - Log key events: request received, successful API submission, errors encountered.
- Include contextual information: request ID (Fastify adds this automatically), relevant data (like recipient number, masked if necessary for privacy), Infobip response/error details.
- In production, configure log levels (
info
,warn
,error
) and forward logs to a centralized logging system (e.g., Datadog, ELK stack, Splunk) for analysis and alerting.
// Example of enhanced logging in routes/sms.js catch block request.log.error({ msg: 'Failed to send SMS via Infobip', err: { // Log specific parts of the error, not necessarily the whole object message: error.message, code: error.code, // If available status: error.response?.status, // HTTP status from Infobip if available infobipError: error.response?.data // Infobip specific error payload }, requestId: request.id, // Fastify adds this automatically recipient: to // Consider masking this in production logs if needed }, 'Infobip API Error');
- Fastify's built-in Pino logger (
- Retry Mechanisms:
- Infobip might handle some level of retries internally for delivery, but API call failures (network issues, temporary 5xx errors) might require client-side retries.
- Simple Retry: You could wrap the
infobipClient.channels.sms.send
call in a loop with a delay. - Exponential Backoff: A better approach. Wait longer between retries (e.g., 1s, 2s, 4s...). Libraries like
async-retry
can simplify this. - Caveat: Retrying automatically can be risky for actions like sending SMS (potential duplicates if the first request succeeded but the response failed). Only retry on specific error types known to be transient (e.g., 503 Service Unavailable, network timeouts). Do not retry on 4xx errors or potentially successful submissions where the status is unclear. For this basic guide, we omit automatic client-side retries for simplicity and safety.
6. Database Schema and Data Layer
This specific service (sending a single SMS on demand) does not inherently require a database.
- Optional Extensions: You could add a database (e.g., PostgreSQL, MongoDB with Prisma or TypeORM) to:
- Log SMS message history (recipient, sender, text, timestamp, Infobip message ID, status).
- Store message templates.
- Manage user accounts or contacts.
- If Adding a DB:
- Design a schema (e.g., an
sms_logs
table). - Use an ORM or query builder for data access.
- Implement migrations (
prisma migrate dev
,knex migrate:latest
). - Consider indexing (e.g., on
messageId
,timestamp
,recipient
).
- Design a schema (e.g., an
For this guide, we focus solely on the stateless action of sending an SMS.
7. Security Features
- Input Validation: Handled by Fastify's schema validation (
routes/sms.js
). This prevents basic injection attacks and ensures data types are correct. - Input Sanitization: While basic validation helps, explicitly sanitizing input intended for external systems or databases is good practice. Libraries like
DOMPurify
(for HTML) or custom logic might be needed depending on the data. For simple phone numbers and sender IDs, strict validation is often sufficient. However, sanitizing free-form text input (text
) might be considered if it's rendered or processed insecurely by downstream systems, even if Infobip handles it safely. Fastify typically handles basic JSON parsing safely. - Rate Limiting: Crucial to prevent abuse and control costs.
- Install:
npm install @fastify/rate-limit
- Register the plugin in
index.js
(as shown in the commented-out example). - Configure
max
requests andtimeWindow
. Adjust based on expected load and Infobip's limits.
- Install:
- API Key Security: Already covered (use
.env
, environment variables in production). - HTTPS: Ensure your Fastify service is deployed behind a reverse proxy (like Nginx or Caddy) or PaaS that terminates TLS/SSL, so communication is encrypted. The Infobip SDK uses HTTPS for its API calls.
- Common Vulnerabilities:
- Injection: Prevented by using the SDK (which handles parameterization) and input validation. Avoid constructing API requests or database queries via string concatenation with user input.
- Denial of Service (DoS): Rate limiting helps mitigate this.
- Information Exposure: Ensure error messages don't leak sensitive details. Log verbosely internally, but return generic errors to clients.
8. Handling Special Cases
- Phone Number Formatting: Infobip requires numbers in international format (e.g.,
447123456789
, not07123456789
or+44...
). Your validation schema or logic should enforce this. Consider using a library likelibphonenumber-js
for robust parsing and validation if accepting varied input formats. - Sender ID: Adhere to Infobip's rules (alphanumeric/numeric lengths). Some countries have restrictions on alphanumeric senders.
- Character Limits & Encoding: Standard SMS messages have character limits (160 for GSM-7, 70 for Unicode). Longer messages are split into multiple parts. The Infobip API generally handles splitting, but be aware of potential costs for multi-part messages. The API also supports different encodings and transliteration if needed (configurable in the API payload, check Infobip docs/SDK options).
- Infobip Free Trial Limitation: Reiterate that free accounts can ONLY send SMS to the phone number verified during registration. This is a common source of ""errors"" for developers starting out.
- Delivery Reports: Infobip can send delivery status updates to a webhook (Notify URL). Setting this up involves configuring a separate endpoint in your Fastify app to receive these POST requests from Infobip and updating the status of the message (likely requiring a database from Section 6). This is beyond the scope of basic sending but crucial for tracking final delivery status.
9. Performance Optimizations
For a simple API proxy like this, performance is largely dictated by the network latency and the speed of the external API (Infobip).
- Fastify's Speed: Choosing Fastify already provides a high-performance foundation.
- SDK Efficiency: The official SDK is generally optimized.
- Avoid Blocking Operations: Ensure all I/O (like the Infobip API call) uses
async/await
correctly to avoid blocking the Node.js event loop. - Payload Size: Keep request/response payloads minimal.
- Caching: Not typically applicable for sending unique SMS messages. Caching might be relevant if fetching templates or contact lists, but not for the send action itself.
- Load Testing: Use tools like
autocannon
(npm install -g autocannon
) to test how many requests per second your endpoint can handle before hitting Infobip limits or resource constraints.(Remember the free trial number limitation when load testing)autocannon http://localhost:3000/api/v1/send-sms -m POST -H ""Content-Type=application/json"" -b '{""to"":""YOUR_TEST_NUMBER"",""from"":""TestSender"",""text"":""Load test""}'
- Profiling: Use Node.js built-in profiler or tools like
0x
to identify CPU bottlenecks if performance issues arise within your own code (unlikely for this simple case).
10. Monitoring, Observability, and Analytics
- Health Checks: The
/health
endpoint provides a basic check for monitoring systems (like Kubernetes liveness/readiness probes or uptime checkers). - Performance Metrics:
- Log response times for the
/send-sms
route. - Monitor Node.js process metrics (CPU, memory, event loop lag). Tools like
pm2
provide some monitoring. - Integrate with Application Performance Monitoring (APM) tools (Datadog, New Relic, Dynatrace) for deeper insights. These tools often auto-instrument Fastify.
- Log response times for the
- Error Tracking:
- Use services like Sentry or Bugsnag. Integrate their Node.js SDKs to capture unhandled exceptions and log errors with more context.
- Configure alerting in your logging/monitoring system for high error rates or specific error codes (e.g., Infobip authentication failures).
- Dashboards: Create dashboards in your logging/monitoring tool to visualize:
- Request volume (overall and per sender/recipient if needed).
- API latency (p50, p90, p99).
- Error rates (overall, 4xx vs 5xx).
- Infobip status codes distribution (requires logging the
status.name
from the response). - System resource usage.
11. Troubleshooting and Caveats
- Common Errors & Solutions:
Error: Infobip API Key or Base URL not found...
: Ensure.env
file exists in the project root, is correctly named, and containsINFOBIP_API_KEY
andINFOBIP_BASE_URL
with valid values. Restart the server after creating/modifying.env
.- Infobip Response:
401 Unauthorized
/{""requestError"": {""serviceException"": {""text"": ""Invalid login details""}}}
: API Key is incorrect or expired. Verify the key in.env
matches the one in the Infobip dashboard. Ensure theINFOBIP_BASE_URL
is also correct for that key. - Infobip Response: Error related to
to
number (e.g.,EC_INVALID_DESTINATION_ADDRESS
): Phone number format is likely incorrect. Ensure it's in international format without+
or leading00
(e.g.,447123456789
). - Infobip Response:
EC_MESSAGE_NOT_ALLOWED_ON_FREE_TRIAL
/ Sending Fails Silently: You are using a free trial account and trying to send to a number other than the one registered with Infobip. This is the most common issue for beginners. Verify the recipient number matches your registered number exactly. - Infobip Response: Error related to
from
ID (e.g.,EC_INVALID_SENDER
): Sender ID doesn't meet Infobip's format requirements (length, characters) or may be restricted in the destination country. ECONNREFUSED
/ETIMEDOUT
: Network issue connecting to the Infobip API. Check server connectivity, firewalls, and Infobip's service status page.- Fastify Response:
400 Bad Request
with validation message: The request body sent to/send-sms
is missing required fields or has incorrect data types (e.g., sending a number instead of a string). Check thecurl
or client request payload against the schema inroutes/sms.js
.
- Platform Limitations:
- Infobip Free Trial: Can only send to the registered/verified phone number.
- Infobip Rate Limits: Production accounts have API rate limits. Check your Infobip plan details. Implement rate limiting in Fastify accordingly.
- Carrier Filtering/Blocking: Mobile carriers can sometimes filter messages perceived as spam. Content, sender ID reputation, and sending volume can affect deliverability.
- Version Compatibility: Use compatible versions of Node.js, Fastify, and
@infobip-api/sdk
. Check their respective documentation for requirements. This guide assumes Node.js v18+ and recent versions of the libraries. - Edge Cases: Consider very long messages (cost implications), sending to unsupported countries, handling specific non-Latin characters (requires correct encoding/transliteration settings).
12. Deployment and CI/CD
- Deployment Environments:
- PaaS (Heroku, Fly.io, Render): Easiest option. Connect your Git repo, configure environment variables (
INFOBIP_API_KEY
,INFOBIP_BASE_URL
,PORT
,HOST=0.0.0.0
) via their dashboard/CLI, and deploy. They handle infrastructure, scaling (to an extent), and often TLS termination. - Containers (Docker): Create a
Dockerfile
to package your app. Deploy the container to services like AWS ECS/EKS, Google Cloud Run/GKE, or a VM with Docker installed. Requires more infrastructure management. - VMs (AWS EC2, DigitalOcean Droplet): Install Node.js, copy your code, install dependencies, set up environment variables, and use a process manager like
pm2
(npm install -g pm2
,pm2 start index.js
) to run the app reliably. Requires setting up a reverse proxy (Nginx/Caddy) for TLS and load balancing.
- PaaS (Heroku, Fly.io, Render): Easiest option. Connect your Git repo, configure environment variables (
- Environment Configuration: Never hardcode credentials. Use environment variables specific to each deployment stage (development, staging, production).
- CI/CD Pipeline (e.g., GitHub Actions, GitLab CI):
- Trigger: On push/merge to main/production branch.
- Lint: Run code quality checks (e.g., ESLint).
- Test: Run unit/integration tests (requires setting up test environment variables securely).
- Build: (If using TypeScript or a build step). For this JS project, this might just be installing production dependencies (
npm ci --omit=dev
). - Package: (If using Docker) Build and push the Docker image to a registry.
- Deploy: Trigger deployment to the target environment (e.g.,
fly deploy
,heroku deploy
, update Kubernetes deployment).