code examples
code examples
Send SMS with Infobip API in Node.js: Complete Express Integration Guide
Build a robust Node.js application using Express to send SMS messages via the Infobip API. This step-by-step tutorial covers API authentication, 10DLC compliance, error handling, and production-ready logging with Winston.
Send SMS with Infobip API in Node.js: Complete Express Integration Guide
Build a robust Node.js application using Express to send SMS messages via the Infobip API for marketing campaigns. This step-by-step tutorial covers API authentication, 10DLC compliance, error handling, and production-ready logging with Winston.
You'll create a backend service that accepts requests to send targeted SMS messages, integrates securely with Infobip, handles errors gracefully, and follows production-ready best practices.
What you'll build:
- Express API endpoint to trigger SMS sends
- Secure Infobip API integration using API keys
- Input validation and error handling
- Structured application architecture
- Production logging and API security
Technology Stack:
- Node.js: JavaScript runtime environment
- Express: Minimal Node.js web framework
- Axios: Promise-based HTTP client for Infobip API requests
- dotenv: Environment variable management for secure configuration
- Winston: Structured logging library
- Infobip API: SMS messaging service
System Architecture:
graph LR
A[Client/Trigger<br>(e.g., Frontend,<br>Scheduler, etc.)] --> B{Node.js/Express App<br>(API Endpoint)};
B --> C[Infobip API<br>(SMS Sending)];
subgraph B [Node.js/Express App]
direction TB
B1[Validate Request] --> B2[Build Infobip Payload];
B2 --> B3[Call Infobip API];
B3 --> B4[Handle Response/Error];
endPrerequisites:
- Node.js and npm: Installed on your development machine (Download Node.js)
- Infobip Account: Register at infobip.com/signup
- Infobip API Key and Base URL: Obtain from your Infobip account dashboard
- Verified Phone Number (Free Trial): Free trial accounts can only send SMS to verified numbers
- ⚠️ Critical: 10DLC Campaign Registration (US Traffic): For US Application-to-Person (A2P) SMS using 10-digit long codes, you must register a Brand and Campaign through Infobip. This is mandatory and enforced by US carriers. Sending without proper registration will cause message failures. See Infobip 10DLC Documentation for the registration process. This guide assumes you have an approved campaign.
Final Outcome:
You'll have a functional Express application with a secured API endpoint (/api/campaign/send-sms) that:
- Accepts destination phone number and message text
- Validates input
- Sends SMS via Infobip
- Logs actions and errors
- Returns structured results
Configuration uses environment variables for security.
1. Node.js Project Setup for Infobip SMS Integration
Initialize the Node.js project and install dependencies.
-
Create Project Directory: Create a new directory and navigate into it:
bashmkdir infobip-sms-campaign-app cd infobip-sms-campaign-app -
Initialize npm: Initialize the project (accept defaults or customize):
bashnpm init -y -
Install Dependencies: Install Express, Axios, dotenv, and Winston:
bashnpm install express axios dotenv winston -
Create Project Structure: Set up the directory structure:
bashmkdir src mkdir src/routes mkdir src/services mkdir src/controllers mkdir src/config mkdir src/middleware touch src/server.js touch src/routes/campaignRoutes.js touch src/services/infobipService.js touch src/controllers/campaignController.js touch src/config/logger.js touch src/middleware/auth.js touch .env touch .gitignoreDirectory structure:
src/: All source codesrc/routes/: Express route definitionssrc/services/: External service interaction logic (Infobip)src/controllers/: Request handlerssrc/config/: Configuration files (logger)src/middleware/: Custom middleware (authentication)src/server.js: Main application entry point.env: Environment variables (Never commit to Git).gitignore: Files Git should ignore
-
Configure
.gitignore: Add these entries to prevent committing sensitive files:plaintext# .gitignore node_modules .env npm-debug.log *.log -
Set up Environment Variables: Add placeholders to
.env(replace with actual values later):dotenv# .env # Server Configuration PORT=3000 NODE_ENV=development # Infobip API Credentials INFOBIP_API_KEY=YOUR_INFOBIP_API_KEY INFOBIP_BASE_URL=YOUR_INFOBIP_BASE_URL # Internal API Security # Generate a strong random key for production INTERNAL_API_KEY=YOUR_SECRET_API_KEY_FOR_INTERNAL_ACCESSEnvironment variables:
PORT: Express server portNODE_ENV: Environment (affects logging behavior)INFOBIP_API_KEY: API key from InfobipINFOBIP_BASE_URL: Your unique base URL (e.g.,xxxxx.api.infobip.com)INTERNAL_API_KEY: Secret key to protect your API endpoint
2. Building the Infobip SMS Service with Error Handling
Create a dedicated service to handle all Infobip API interactions. This promotes separation of concerns and improves testability.
-
Configure Logger (
src/config/logger.js): Set up Winston for structured logging.javascript// src/config/logger.js const winston = require('winston'); const logger = winston.createLogger({ level: process.env.NODE_ENV === 'production' ? 'info' : 'debug', // Log level based on environment format: winston.format.combine( winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.errors({ stack: true }), // Log stack traces winston.format.splat(), winston.format.json() // Log in JSON format ), defaultMeta: { service: 'infobip-sms-service' }, // Add service context transports: [ // In production, you might write to files or external services // new winston.transports.File({ filename: 'error.log', level: 'error' }), // new winston.transports.File({ filename: 'combined.log' }), ], }); // If we're not in production, log to the `console` with a simpler format. if (process.env.NODE_ENV !== 'production') { logger.add(new winston.transports.Console({ format: winston.format.combine( winston.format.colorize(), winston.format.simple() ), })); } module.exports = logger; -
Edit
src/services/infobipService.js: This file will contain the logic for building the request and calling the Infobip ""Send SMS"" API endpoint (/sms/2/text/advanced), now using the logger.javascript// src/services/infobipService.js const axios = require('axios'); const logger = require('../config/logger'); // Use the configured logger // Retrieve Infobip credentials from environment variables const INFOBIP_API_KEY = process.env.INFOBIP_API_KEY; const INFOBIP_BASE_URL = process.env.INFOBIP_BASE_URL; /** * Builds the full API endpoint URL. * @returns {string} The complete URL for the Infobip Send SMS API. * @throws {Error} If INFOBIP_BASE_URL is not set. */ const buildUrl = () => { if (!INFOBIP_BASE_URL) { logger.error('INFOBIP_BASE_URL environment variable is not set.'); throw new Error('Infobip configuration error: Base URL is missing.'); } return `https://${INFOBIP_BASE_URL}/sms/2/text/advanced`; }; /** * Constructs the necessary HTTP headers for Infobip API authentication. * @returns {object} Headers object including Content-Type and Authorization. * @throws {Error} If INFOBIP_API_KEY is not set. */ const buildHeaders = () => { if (!INFOBIP_API_KEY) { logger.error('INFOBIP_API_KEY environment variable is not set.'); throw new Error('Infobip configuration error: API Key is missing.'); } return { 'Authorization': `App ${INFOBIP_API_KEY}`, 'Content-Type': 'application/json', 'Accept': 'application/json', }; }; /** * Constructs the request body payload for the Infobip Send SMS API. * @param {string} destinationNumber - The recipient's phone number in E.164 format (e.g., +447...). * @param {string} messageText - The content of the SMS message. * @param {string} [sender] - Optional: The sender ID (Alphanumeric or Short Code/Long Code). **Crucially, this MUST be a valid, registered Sender ID appropriate for the destination country and regulations (e.g., pre-registered 10DLC for US A2P). Using an invalid/unregistered sender will likely cause message failure. Defaulting to 'InfoSMS' is often NOT valid for many regions/use cases.** Verify requirements with Infobip documentation and local regulations. * @returns {object} The request body payload. * @throws {Error} If destinationNumber or messageText is missing. */ const buildRequestBody = (destinationNumber, messageText, sender) => { if (!destinationNumber || !messageText) { logger.warn('Attempted to build request body with missing destination or text.', { destinationNumber, messageText }); throw new Error('Destination number and message text are required.'); } const payload = { messages: [ { destinations: [{ to: destinationNumber }], text: messageText, // Only include 'from' if a valid sender is provided. // Relying on Infobip's default/configured sender might be safer if unsure. ...(sender && { from: sender }), // Add campaignId, notifyUrl etc. here if required by your campaign setup }, ], // tracking: { // Optional: Add tracking if needed for campaign analytics // track: 'URL', // type: 'MY_CAMPAIGN_TRACKER' // } }; logger.debug('Built Infobip request payload.', { payload }); // Log payload at debug level return payload; }; /** * Sends an SMS message using the Infobip API. * @param {string} destinationNumber - The recipient's phone number (E.164 format recommended). * @param {string} messageText - The message content. * @param {string} [sender] - Optional: The sender ID (see buildRequestBody docs for critical usage notes). * @returns {Promise<object>} A promise that resolves with the relevant part of the Infobip API response (e.g., message status and ID). * @throws {Error} If the API call fails or returns an error status. */ const sendSms = async (destinationNumber, messageText, sender) => { let url, headers, requestBody; try { url = buildUrl(); headers = buildHeaders(); requestBody = buildRequestBody(destinationNumber, messageText, sender); logger.info(`Attempting to send SMS to ${destinationNumber} via Infobip...`); const response = await axios.post(url, requestBody, { headers: headers }); logger.info('Infobip API Response Status:', { status: response.status }); // Log only essential parts of the response, not the whole potentially large object logger.debug('Infobip API Response Body:', { data: response.data }); // Basic check for success indication in the response structure const messageInfo = response.data?.messages?.[0]; if (messageInfo) { const status = messageInfo.status; if (status?.groupName === 'PENDING' || status?.groupName === 'DELIVERED') { logger.info(`SMS submitted successfully for ${destinationNumber}. Status: ${status.name}, Message ID: ${messageInfo.messageId}`); } else { logger.warn(`SMS submission for ${destinationNumber} resulted in status: ${status?.name || 'Unknown'} (${status?.description || 'N/A'}), Group: ${status?.groupName || 'Unknown'}`, { messageId: messageInfo.messageId }); } // Return curated info return { messageId: messageInfo.messageId, status: status, to: messageInfo.to }; } else { logger.warn(`Unexpected response structure received from Infobip for ${destinationNumber}.`, { responseData: response.data }); // Return raw data if structure is unknown, but flag it return { rawResponse: response.data, warning: ""Unexpected response structure"" }; } } catch (error) { logger.error('Error sending SMS via Infobip.', { message: error.message, url: url, // Log the URL attempted destination: destinationNumber, // Log relevant context stack: error.stack, // Include stack trace }); let errorMessage = 'Failed to send SMS due to an internal error.'; // Generic message if (error.response) { // The request was made and the server responded with a non-2xx status code logger.error('Infobip API Error Response:', { status: error.response.status, headers: error.response.headers, data: error.response.data, // Log the full error body from Infobip }); // Attempt to extract a meaningful error message from Infobip's response const serviceException = error.response.data?.requestError?.serviceException; if (serviceException) { errorMessage = `Infobip API Error: ${serviceException.text || 'Unknown error'} (ID: ${serviceException.messageId || 'N/A'})`; } else { errorMessage = `Infobip API request failed with status ${error.response.status}.`; } throw new Error(errorMessage); // Throw curated error message } else if (error.request) { // The request was made but no response was received logger.error('Infobip Error: No response received.', { request: error.request }); errorMessage = 'No response received from Infobip API. Check network connectivity and Base URL.'; throw new Error(errorMessage); } else { // Something happened in setting up the request that triggered an Error errorMessage = `Error setting up Infobip request: ${error.message}`; throw new Error(errorMessage); // Throw the setup error message } } }; module.exports = { sendSms, // Expose helpers for potential testing _buildUrl: buildUrl, _buildHeaders: buildHeaders, _buildRequestBody: buildRequestBody };- Logging: Replaced
console.*withlogger.*calls using the Winston logger. Added more contextual information to logs. - Error Handling: Logs detailed technical errors server-side and throws a more user-friendly (but still informative) error message for the controller layer.
- Sender ID: Added stronger warnings about the
senderparameter in the JSDoc and implementation notes. It's now optional and only included if explicitly provided, encouraging reliance on Infobip's configured defaults if unsure. - Response Handling: Returns a curated object with key information (
messageId,status,to) instead of the full raw response on success.
- Logging: Replaced
3. Creating the Express API Endpoint
Create the Express controller and route that will use the infobipService.
-
Edit
src/controllers/campaignController.js: This controller handles the incoming HTTP request, performs validation, calls the service, and sends back a curated response.javascript// src/controllers/campaignController.js const infobipService = require('../services/infobipService'); const logger = require('../config/logger'); // Basic E.164 format regex (simplified: starts with +, then digits) // For robust validation, consider libraries like 'libphonenumber-js' const E164_REGEX = /^\+[1-9]\d{1,14}$/; /** * Controller function to handle sending an SMS message. * POST /api/campaign/send-sms * Body: { "to": "+14155552671", "text": "messageContent", "sender": "OptionalSenderID" } */ const sendCampaignSms = async (req, res) => { const { to, text, sender } = req.body; // Include optional sender // Input Validation const errors = []; if (!to) { errors.push('Missing required field: `to` (phone number)'); } else if (!E164_REGEX.test(to)) { // Add more robust validation if needed (e.g., using libphonenumber-js) errors.push('Invalid phone number format for `to`. Use E.164 format (e.g., +14155552671).'); } if (!text) { errors.push('Missing required field: `text` (message content)'); } else if (text.length > 1600) { // Example: Arbitrary limit to prevent abuse errors.push('Message text exceeds maximum allowed length (1600 characters).'); } // Optional: Validate 'sender' format if provided if (errors.length > 0) { logger.warn('Invalid request to send SMS.', { errors, requestBody: req.body }); return res.status(400).json({ success: false, message: 'Invalid request payload.', errors: errors, }); } try { // Pass validated data to the service const result = await infobipService.sendSms(to, text, sender); // Pass sender if provided // Service logs details. Controller sends client-friendly, curated response. res.status(200).json({ success: true, message: 'SMS submitted successfully to Infobip.', // Return only necessary info, e.g., messageId for tracking details: { messageId: result?.messageId, statusGroup: result?.status?.groupName, recipient: result?.to, } }); } catch (error) { // The service layer already logged the detailed technical error. // Log the error context at the controller level. logger.error('Error processing send SMS request in controller.', { errorMessage: error.message, recipient: to, // Log context stack: error.stack // Log stack trace for controller-level issues }); // Send a generic server error response to the client. Avoid exposing internal details. res.status(500).json({ success: false, message: 'Failed to send SMS due to a server error. Please check server logs for details or contact support.', // Do NOT include error.message directly here in production // errorId: 'SOME_TRACKING_ID' // Optional: provide an ID for correlation }); } }; module.exports = { sendCampaignSms, };- Validation: Added basic E.164 regex validation for the
tofield and a length check fortext. Returns specific error messages using backticks for field names. - Curated Responses: Success response now includes only
messageId,statusGroup, andrecipientfrom the service result. Error response is generic, advising to check logs, and explicitly does not include the rawerror.message. - Logging: Logs validation failures and errors occurring within the controller itself.
- Validation: Added basic E.164 regex validation for the
-
Edit
src/routes/campaignRoutes.js: Define the Express route and link it to the controller function.javascript// src/routes/campaignRoutes.js const express = require('express'); const campaignController = require('../controllers/campaignController'); // Optional: Import authentication middleware // const { requireApiKey } = require('../middleware/auth'); const router = express.Router(); // Define the route for sending SMS // POST /api/campaign/send-sms // Consider adding authentication middleware here: // router.post('/send-sms', requireApiKey, campaignController.sendCampaignSms); router.post('/send-sms', campaignController.sendCampaignSms); // You could add more campaign-related routes here (e.g., getting campaign status) module.exports = router; -
Edit
src/server.js: Set up the main Express application, load environment variables, configure middleware (including logging), and mount the routes.javascript// src/server.js // Load environment variables from .env file FIRST require('dotenv').config(); const express = require('express'); const helmet = require('helmet'); // For security headers const campaignRoutes = require('./routes/campaignRoutes'); const logger = require('./config/logger'); // Import the logger // Create the Express application const app = express(); // Security Middleware app.use(helmet()); // Set various security HTTP headers app.use(express.json()); // Middleware to parse JSON request bodies app.use(express.urlencoded({ extended: true })); // Parse URL-encoded bodies // HTTP Request Logging Middleware (using Winston) app.use((req, res, next) => { // Log basic request info logger.info(`Incoming Request: ${req.method} ${req.path}`, { ip: req.ip, userAgent: req.get('User-Agent'), body: req.body // Be cautious logging full bodies in production (PII/secrets) }); // Log response finish res.on('finish', () => { logger.info(`Request Finished: ${req.method} ${req.path} - Status: ${res.statusCode}`, { duration: `${Date.now() - res.locals.startTime}ms` // Requires setting startTime earlier }); }); res.locals.startTime = Date.now(); // Store start time for duration calculation next(); }); // Mount the campaign routes under the /api/campaign prefix // Make sure to apply any necessary authentication middleware here or in the router itself app.use('/api/campaign', campaignRoutes); // Simple health check endpoint app.get('/health', (req, res) => { res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() }); }); // 404 Handler for undefined routes app.use((req, res, next) => { res.status(404).json({ message: 'Resource not found at ' + req.originalUrl }); }); // Centralized Error Handler Middleware (Logs errors using Winston, sends generic response) // This catches errors passed via next(err) or uncaught synchronous errors in route handlers // Note: Asynchronous errors need to be caught and passed to next() or handled in controllers/services app.use((err, req, res, next) => { // Log the error using Winston logger.error('Unhandled error caught by central handler:', { message: err.message, stack: err.stack, url: req.originalUrl, method: req.method, ip: req.ip, }); // Avoid sending stack traces or detailed errors to the client in production const statusCode = err.status || 500; // Use error status or default to 500 const responseMessage = process.env.NODE_ENV === 'production' ? 'Internal Server Error' : err.message; // Show more detail in dev res.status(statusCode).json({ message: 'An unexpected error occurred.', error: responseMessage // Provide minimal info }); }); // Start the server const PORT = process.env.PORT || 3000; // Default to 3000 if PORT not set app.listen(PORT, () => { logger.info(`Server listening on port ${PORT}`); logger.info(`Current environment: ${process.env.NODE_ENV || 'development'}`); // Verify essential environment variables are loaded if (!process.env.INFOBIP_API_KEY || !process.env.INFOBIP_BASE_URL) { logger.warn('Infobip API Key or Base URL not found in environment variables. API calls will fail.'); } if (!process.env.INTERNAL_API_KEY && process.env.NODE_ENV === 'production') { logger.warn('INTERNAL_API_KEY is not set. The API endpoint security is compromised in production!'); } }); // Handle unhandled promise rejections process.on('unhandledRejection', (reason, promise) => { logger.error('Unhandled Rejection at:', { promise, reason: reason.stack || reason }); // Optionally exit process: process.exit(1); }); // Handle uncaught exceptions process.on('uncaughtException', (error) => { logger.error('Uncaught Exception:', { message: error.message, stack: error.stack }); // Graceful shutdown logic might go here process.exit(1); // Mandatory exit after uncaught exception });dotenv.config(): Still first.- Logging: Integrated Winston for request logging and centralized error handling.
- Security: Added
helmetmiddleware. - Error Handling: Enhanced the final error handler to use Winston and provide less detail in production. Added global handlers for
unhandledRejectionanduncaughtException. - Startup Checks: Added warnings if essential keys (Infobip or internal API key) are missing.
-
Update
package.json(Optional but Recommended): Add scripts to easily start the server.json// package.json (add inside "scripts") "scripts": { "start": "node src/server.js", "dev": "nodemon src/server.js", // If you install nodemon: npm install --save-dev nodemon "lint": "eslint .", // Example if using ESLint "test": "echo \"Error: no test specified\" && exit 1" // Keep or replace },
4. Infobip API Authentication and Configuration
This section details how to get your Infobip credentials and configure the application.
-
Obtain Infobip API Key and Base URL:
- Log in to your Infobip account portal.
- Navigate to the Developers section or API Keys management area.
- Base URL: Find your unique API Base URL (e.g.,
[your-unique-id].api.infobip.com). Copy this value. - API Key: Generate a new API key. Give it a descriptive name (e.g.,
nodejs-campaign-app). Copy the generated key value immediately. - Security Note: Treat your API key like a password. Use environment variables; do not commit it to version control.
-
Update
.envFile: Replace the placeholder values in your.envfile with the actual Base URL, API Key, and a secure internal API key.dotenv# .env (Example values - Replace with your actual credentials!) PORT=3000 NODE_ENV=development # Infobip API Credentials INFOBIP_API_KEY=abcdef1234567890abcdef1234567890-abcdef12-abcd-1234-abcd-abcdef123456 INFOBIP_BASE_URL=z9x8y7.api.infobip.com # Internal API Security (Generate a strong random string for this) INTERNAL_API_KEY=Str0ngS3cr3tK3yF0rYourAPI! -
Environment Variable Purpose:
PORT: Network port the Express server listens on.NODE_ENV: Set toproductionin deployed environments.INFOBIP_API_KEY: Authenticates requests to the Infobip API (Authorization: App <key>).INFOBIP_BASE_URL: Correct domain for Infobip API endpoints (e.g.,yoursubdomain.api.infobip.com). Does not includehttps://.INTERNAL_API_KEY: Used to secure your application's API endpoint (see Section 7 - Note: Section 7 is not present in the provided text).
-
Campaign Registration (Crucial Reminder):
- As highlighted in the prerequisites, simply having an API key is often insufficient, especially for A2P 10DLC in the US. You must have a registered and approved campaign linked to your sender number(s).
- Use the Infobip portal or the Number Registration API to register your Brand and Campaign. This involves detailing your use case, providing message samples, and explaining opt-in procedures.
- Allow time for Infobip Compliance review and approval.
- Only after approval can you reliably send messages using the associated sender ID/number. You might need to include a
campaignIdin the API request body (src/services/infobipService.js). Verify the requirements for your specific approved campaign. Failure to comply is a primary reason for message delivery issues.
5. Production Error Handling with Winston Logging
Our implementation now includes structured logging with Winston and improved error handling. Let's discuss retries.
- Consistent Error Strategy: Errors are caught at the lowest level possible (e.g.,
infobipService). Detailed technical info is logged there using Winston. A curated error is thrown upwards. The controller catches this, logs the context, and sends a generic, safe error response to the client. Centralized handlers catch uncaught exceptions/rejections. - Logging:
- We now use Winston configured in
src/config/logger.js. It provides structured JSON logging, different levels based onNODE_ENV, and timestamping. - Logs include context like service name, request details, and error stacks.
- In production, configure transports to write logs to files or stream them to external aggregation services (ELK, Datadog, Splunk).
- We now use Winston configured in
- Retry Mechanisms:
- Network glitches or temporary Infobip issues (like
503 Service Unavailable) might cause transient failures. Implementing retries can improve reliability for these cases. - Use libraries like
async-retryoraxios-retry.axios-retryintegrates directly with Axios, whileasync-retryis a more general-purpose promise retry library. - Caution: Only retry on specific, recoverable error types (network errors, 5xx server errors). Do not retry on client errors (4xx) or non-recoverable errors.
- Network glitches or temporary Infobip issues (like
Frequently Asked Questions
Why is input validation important for sending SMS via API?
Input validation prevents sending messages to invalid numbers, exceeding character limits, or passing malicious data to the Infobip API. It improves security and reliability.
How to send SMS messages with Infobip API?
Use the Infobip SMS API's `/sms/2/text/advanced` endpoint. Make a POST request to this endpoint with the necessary authorization and request body containing recipient number, message text, and optionally, the sender ID.
What is the role of Express.js in the Infobip SMS app?
Express.js acts as the web framework for the Node.js application. It handles routing, middleware (like authentication), and HTTP request/response management. It simplifies building robust and scalable web APIs for sending SMS.
Why does Infobip campaign registration matter for US SMS?
US regulations require Application-to-Person (A2P) 10DLC messages to be sent under registered campaigns. Registering a brand and campaign with Infobip ensures compliance, preventing message filtering and failure.
When should I use a custom sender ID with Infobip?
Use a custom sender ID only if it's registered and compliant with regulations for your target region. If unsure, rely on Infobip's default configured sender ID to avoid delivery issues.
How to integrate Winston logger in Node.js Express app?
Install Winston (`npm install winston`) and configure a logger instance. Use `logger.*` (e.g., `logger.info`, `logger.error`) to replace `console.*`. Set up transports for different environments (console, file, external services).
What is the purpose of the .env file in the Infobip integration?
The `.env` file stores sensitive configuration data, such as your Infobip API key, base URL, and any internal API keys for your application. It is crucial for security to never commit the `.env` file to version control.
How to handle errors when sending SMS with Infobip?
Implement comprehensive error handling at each level: service, controller, and global handlers. Log detailed errors server-side using Winston but provide generic error messages to clients. Use Axios interceptors or dedicated retry libraries for handling transient network issues.
What is the technology stack for the Infobip SMS app?
The project uses Node.js with Express, Axios for making API requests, dotenv for environment variables, Winston for logging, and the Infobip API for sending SMS messages.
How to set up a Node.js Express project for Infobip SMS?
Create a project directory, initialize npm (`npm init -y`), install required packages (`npm install express axios dotenv winston`), structure your project (routes, services, controllers), and configure your `.env` file with credentials.
When should I implement retry mechanisms for Infobip API calls?
Implement retries for transient errors like network issues or 5xx server errors from Infobip. Use libraries like `async-retry` or `axios-retry`. Never retry on client errors (4xx) like validation failures.
How to structure a Node.js Express application for maintainability?
Organize code into modules with distinct responsibilities: routes for defining API endpoints, controllers for handling requests, services for interacting with external APIs (like Infobip), and middleware for cross-cutting concerns.
What is the project structure for the Node.js Infobip SMS app?
The recommended structure includes `src/` for source code, containing folders for routes, services, controllers, config, and middleware. The `server.js` file serves as the main entry point.
Can I use a free trial Infobip account for sending SMS?
Yes, but free trials usually limit sending to the phone number verified during signup. Check Infobip's free trial terms for limitations.