code examples

Sent logo
Sent TeamMay 3, 2025 / code examples / Article

How to Send SMS with Fastify and Infobip: Complete Node.js Tutorial

Learn how to build a production-ready SMS API with Fastify and Infobip. Step-by-step guide covering setup, authentication, error handling, and deployment.

How to Send SMS with Fastify and Infobip: Node.js Integration Guide

This guide provides a complete walkthrough for building a production-ready SMS service using Fastify (a high-performance Node.js web framework) and the Infobip SMS API. You'll learn how to integrate an SMS gateway into your Node.js application, from initial project setup to deployment and monitoring best practices.

By the end of this tutorial, you'll have a functional Fastify application with an API endpoint capable of sending SMS messages programmatically through Infobip's SMS gateway. This integration enables common use cases like sending notifications, alerts, verification codes, OTP authentication, and marketing messages directly from your backend application.

What You'll Build: A REST API endpoint (POST /send-sms) that accepts recipient phone numbers, sender IDs, and message content, then reliably delivers SMS messages via Infobip's cloud communications platform.

Prerequisites and Requirements

Before starting this Node.js SMS integration tutorial, ensure you have:

  • Node.js (v18 or later recommended, minimum v14 for Infobip SDK) and npm/yarn installed
  • An active Infobip account with API credentials (free trial available with 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 with authorization errors.
  • Basic understanding of Node.js, REST APIs, and asynchronous JavaScript (async/await)
  • Access to a terminal or command prompt

1. Setting up the Node.js Project

Let's initialize the Node.js project and install the necessary dependencies for SMS integration.

  1. Create Project Directory: Open your terminal and create a new directory for the project, then navigate into it.

    bash
    mkdir fastify-infobip-sms
    cd fastify-infobip-sms
  2. Initialize npm Project: Run npm init and follow the prompts. You can accept the defaults for most questions.

    bash
    npm init -y

    This creates a package.json file.

  3. Install Dependencies: Install Fastify, the Infobip SDK, and dotenv for secure environment variable management.

    bash
    npm install fastify @infobip-api/sdk dotenv
    • fastify: High-performance web framework for Node.js
    • @infobip-api/sdk: Official Infobip Node.js SDK (v0.3.2, requires Node.js v14+)
    • dotenv: Loads environment variables from a .env file into process.env
  4. Install Development Dependencies (Optional but Recommended): Tools like nodemon help during development by automatically restarting the server on file changes.

    bash
    npm install --save-dev nodemon
  5. Configure package.json Scripts and Enable ES Modules: Add scripts to your package.json for easily running and developing your application. Crucially, add "type": "module" to enable the import/export syntax used in the code examples.

    json
    // 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
    }
  6. Create Project Structure: Set up a basic directory structure for organization.

    bash
    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.
  7. Configure .gitignore: Add node_modules and .env to your .gitignore file to prevent committing them.

    plaintext
    # .gitignore
    node_modules
    .env
    *.log
  8. Set up Environment Variables: Define the required environment variables in .env.example and .env.

    plaintext
    # .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 the SMS Sending Functionality

Now let's implement the core logic for sending SMS messages using the Infobip SDK within a Fastify route handler.

  1. Configure Infobip Client: Create a reusable Infobip client instance.

    javascript
    // 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.
  2. Create the SMS Sending Route: Define the Fastify route handler that will receive requests and trigger the SMS sending process.

    javascript
    // 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 E.164 international format (e.g., 447123456789)',
              pattern: '^[1-9][0-9]{1,14}$' // E.164 format: 1-15 digits, no leading zero
            },
            from: {
              type: 'string',
              description: 'Sender ID (alphanumeric, 3-11 chars; or numeric, 3-16 digits)',
              minLength: 3,
              maxLength: 16
            },
            text: {
              type: 'string',
              description: 'SMS message content',
              minLength: 1,
              maxLength: 1600 // Supports multi-part SMS messages
            },
          },
        },
        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 using fastify.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 from error.response.data for better client feedback, falling back to a generic 500 error.
  3. Set up the Main Fastify Application: Configure the main server file to load environment variables, register routes, and start listening.

    javascript
    // 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.

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 integrating fastify-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.

    bash
    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)

    json
    {
      "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):

    json
    {
      "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)

    json
    {
      "error": "Infobip API Error",
      "details": {
        "requestError": {
          "serviceException": {
            "messageId": "UNAUTHORIZED",
            "text": "Invalid login details"
          }
        }
      }
    }

    (Note: Exact error structure from Infobip might vary slightly)

4. Integrating with the Infobip SMS API

We've already set up the core Infobip API integration using the official SDK.

  • API Configuration: Done via .env file and loaded in config/infobip.js.
    • INFOBIP_API_KEY: Your secret API key for authentication. Obtain from Infobip Dashboard → Developers → API Keys. Treat this credential as a password.
    • INFOBIP_BASE_URL: Your account-specific API endpoint URL (e.g., xyz.api.infobip.com). Found on the API Keys page in the Infobip dashboard.
  • 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):
    1. Log in to your Infobip account.
    2. Navigate to the "Developers" or "API" section (exact naming may vary).
    3. Find the "API Keys" management page.
    4. Copy your Base URL shown on this page.
    5. 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.
    javascript
    // 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');
  • 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).

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.
    1. Install: npm install @fastify/rate-limit
    2. Register the plugin in index.js (as shown in the commented-out example).
    3. Configure max requests and timeWindow. Adjust based on expected load and Infobip's limits.
  • 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 and SMS Formatting

  • Phone Number Formatting (E.164 Standard): Infobip requires phone numbers in E.164 international format without the + prefix (e.g., 447123456789 for UK, not 07123456789 or +447123456789). The E.164 standard defines international phone numbering with a maximum of 15 digits: [Country Code 1-3 digits][National Code][Subscriber Number]. Consider using libphonenumber-js for robust phone number validation and parsing if accepting varied input formats.
  • Sender ID Requirements: Adhere to Infobip's sender ID rules (alphanumeric 3-11 characters, or numeric 3-16 digits). Some countries enforce restrictions on alphanumeric sender IDs or require sender registration.
  • SMS Character Limits & Encoding:
    • GSM-7 encoding: 160 characters per SMS segment
    • Unicode encoding: 70 characters per SMS segment
    • Multi-part messages are automatically split by the Infobip API, but each segment incurs separate costs
  • Infobip Free Trial Limitation: Free accounts can ONLY send SMS to the verified registration phone number. This is the most common error for developers starting SMS integration.
  • Delivery Reports & Webhooks: Infobip can send real-time delivery status updates to a webhook endpoint (Notify URL). Configure a separate endpoint in your Fastify app to receive POST requests from Infobip for message status tracking. This requires database integration to store message delivery states.

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.
    bash
    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"}'
    (Remember the free trial number limitation when load testing)
  • 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.
  • 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 contains INFOBIP_API_KEY and INFOBIP_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 the INFOBIP_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 leading 00 (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 the curl or client request payload against the schema in routes/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.
  • 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):
    1. Trigger: On push/merge to main/production branch.
    2. Lint: Run code quality checks (e.g., ESLint).
    3. Test: Run unit/integration tests (requires setting up test environment variables securely).
    4. Build: (If using TypeScript or a build step). For this JS project, this might just be installing production dependencies (npm ci --omit=dev).
    5. Package: (If using Docker) Build and push the Docker image to a registry.
    6. Deploy: Trigger deployment to the target environment (e.g., fly deploy, heroku deploy, update Kubernetes deployment).

Frequently Asked Questions

How to send SMS with Fastify and Infobip?

Set up a Fastify project with the Infobip SDK, create a /send-sms route, and configure the Infobip client with your API key and base URL. The route handler should extract recipient, sender, and message details from the request body and pass them to the Infobip SDK's send method. This abstracts the API interaction into a service layer within your Fastify app.

What is the Infobip free trial limitation for SMS?

Free Infobip accounts can only send SMS messages to the phone number verified during registration. Attempts to send to other numbers will fail. This is a common issue for developers starting with the free trial, so ensure your recipient matches your registered number.

Why use Fastify for sending SMS messages?

Fastify is a high-performance Node.js web framework known for its speed, extensibility, and excellent developer experience. It provides a robust foundation for building a production-ready SMS service with features like schema validation and easy plugin integration.

When should I add rate limiting to my Fastify SMS service?

Rate limiting is crucial for preventing abuse, controlling costs, and protecting your application from overload. You should implement rate limiting as soon as possible, especially before deploying to production. The fastify-rate-limit plugin makes this easy.

Can I use an Infobip free trial for testing SMS sending in production?

No, using a free trial for production is not recommended due to the limitation of only being able to send to your registered number. Upgrade to a paid Infobip account for full functionality and remove sending restrictions.

What is the role of the @infobip-api/sdk in this setup?

The @infobip-api/sdk simplifies interaction with the Infobip API by handling authentication, API requests, and response parsing. It streamlines the SMS sending process within your Fastify application.

How to handle phone number formatting for Infobip?

Infobip requires international format without the + sign or leading 00. The provided example code uses JSON Schema validation, and stricter validation with libphonenumber-js is recommended for more robust handling of diverse input formats.

How do I configure my Infobip API key securely in a Fastify app?

Store your Infobip API key and base URL in a .env file and load them using the dotenv package. Ensure .env is added to your .gitignore file to prevent accidental commits. In production, use your deployment environment's secure secret management system.

How to handle Infobip API errors in Fastify?

Implement a try-catch block around the Infobip SDK call within your /send-sms route handler. Parse specific error details from error.response.data and return appropriate error responses to clients. Provide generic error messages for unexpected errors, and log more detailed information for troubleshooting.

What are common errors when setting up Infobip SMS with Fastify?

Common errors include missing or incorrect API keys/base URLs in the .env file, attempting to send SMS to numbers other than your registered number on a free trial, incorrect phone number formatting, or network issues connecting to the Infobip API. The article covers troubleshooting steps for each.

Why does Infobip sometimes return 401 Unauthorized errors?

401 errors usually indicate an invalid or expired API key. Verify that the API key in your .env file matches the active key in your Infobip dashboard and that your base URL is correct.

When do I need to consider message encoding with Infobip?

Consider message encoding if you're sending messages with non-GSM-7 characters (like emojis or certain accented letters). The Infobip API supports Unicode but may have different character limits and cost implications for multi-part messages.

How to implement logging and monitoring for my Infobip SMS service?

Fastify's Pino logger provides a base. Log key events like requests, successes, and errors with context. In production, configure log levels, forward to a centralized system, and monitor metrics like API latency, error rates, and Infobip response codes.

How to implement retries for Infobip API requests in Fastify?

Use exponential backoff with a library like async-retry. Only retry on specific transient error types (e.g., 503 Service Unavailable, network timeouts) and avoid retrying 4xx errors or successful submissions with uncertain status. Retries are not included in the example code for safety and simplicity.