Send MMS Messages with Node.js and Infobip API: Complete Tutorial
Learn how to send MMS (Multimedia Messaging Service) messages using Node.js, Express, and the Infobip API. Step-by-step guide with code examples, error handling, and deployment tips.
This guide provides a step-by-step walkthrough for building a Node.js application using the Express framework to send Multimedia Messaging Service (MMS) messages via the Infobip API. We will cover project setup, core implementation using the Infobip Node.js SDK, API endpoint creation, configuration, error handling, and deployment considerations.
By the end of this tutorial, you will have a functional Express API endpoint capable of accepting requests and sending MMS messages containing text and media (like images) to specified recipients through Infobip. This solves the common need for applications to engage users with richer media content beyond simple SMS.
Project Overview and Goals
Goal: To create a simple backend service that exposes an API endpoint for sending MMS messages.
Problem Solved: Enables applications to programmatically send rich media messages (images, potentially video/audio depending on Infobip/carrier support) alongside text, enhancing user communication and engagement compared to plain SMS.
Technologies:
Node.js: A JavaScript runtime environment for building server-side applications. Chosen for its event-driven, non-blocking I/O model, suitable for API development, and large ecosystem (npm).
Express: A minimal and flexible Node.js web application framework. Chosen for its simplicity in setting up routes, middleware, and handling HTTP requests/responses.
Infobip API & Node.js SDK (@infobip-api/sdk): Provides the communication bridge to Infobip's platform for sending MMS messages. The SDK simplifies interaction compared to raw HTTP requests.
dotenv: A module to load environment variables from a .env file into process.env, keeping sensitive credentials out of source code.
Node.js and npm (or yarn) installed on your development machine.
Basic understanding of JavaScript, Node.js, Express, and REST APIs.
A mobile phone number registered and verified with your Infobip account (especially for free trial accounts, which often have sending restrictions).
A publicly accessible URL hosting the media file (e.g., an image) you want to send.
Final Outcome: A running Node.js Express server with a single API endpoint (/send-mms) that accepts a destination phone number, a media URL, and optional text, then uses the Infobip SDK to dispatch the MMS message.
1. Setting up the Project
Let's initialize our Node.js project and install the necessary dependencies.
Create Project Directory:
Open your terminal or command prompt and create a new directory for your project, then navigate into it.
Bash
mkdir infobip-mms-sender
cd infobip-mms-sender
Initialize npm Project:
This command creates a package.json file, which tracks project metadata and dependencies. You can accept the defaults or customize them.
Bash
npm init -y
Install Dependencies:
We need Express for the web server, the Infobip SDK for API interaction, and dotenv for managing environment variables.
Bash
npm install express @infobip-api/sdk dotenv
express: The web framework.
@infobip-api/sdk: The official Infobip SDK for Node.js.
dotenv: Loads environment variables from a .env file.
Create Project Structure:
Create the basic files and directories.
Example commands:
Bash
# On Linux/macOStouch server.js .env .env.example .gitignore
# On Windows (Command Prompt)type nul > server.js
type nul > .envtype nul > .env.example
type nul > .gitignore
# On Windows (PowerShell)
New-Item server.js -ItemType File
New-Item .env -ItemType File
New-Item .env.example -ItemType File
New-Item .gitignore -ItemType File
server.js: Our main application file containing the Express server logic.
.env: Stores sensitive credentials (API Key, Base URL). This file should NOT be committed to version control.
.env.example: A template showing required environment variables. This should be committed.
.gitignore: Specifies intentionally untracked files that Git should ignore (like .env and node_modules).
Configure .gitignore:
Add the following lines to your .gitignore file to prevent committing sensitive data and unnecessary files:
Populate .env:
Copy .env.example to .env and replace the placeholder values with your actual Infobip credentials (obtained in Section 4).
.env
# .env (DO NOT COMMIT THIS FILE)
# Infobip Credentials
INFOBIP_API_KEY=abcdef1234567890abcdef1234567890-abcdef12-abcd-1234-abcd-abcdef123456
INFOBIP_BASE_URL=xyz123.api.infobip.com
# Server Configuration
PORT=3000
INFOBIP_API_KEY: Your unique key for authenticating API requests.
INFOBIP_BASE_URL: The specific API domain assigned to your Infobip account.
PORT: The port number your Express server will listen on.
We'll encapsulate the logic for interacting with the Infobip SDK within a dedicated function.
Edit server.js:
Open server.js and start by requiring the necessary modules and configuring dotenv.
JavaScript
// server.jsrequire('dotenv').config(); // Load environment variables from .env fileconst express = require('express');
const { Infobip, AuthType } = require('@infobip-api/sdk');
const app = express();
app.use(express.json()); // Middleware to parse JSON request bodiesconstPORT = process.env.PORT || 3000;
constINFOBIP_API_KEY = process.env.INFOBIP_API_KEY;
constINFOBIP_BASE_URL = process.env.INFOBIP_BASE_URL;
// --- Input Validation ---if (!INFOBIP_API_KEY || !INFOBIP_BASE_URL) {
console.error('Error: Infobip API Key or Base URL not found in environment variables.');
process.exit(1); // Exit if essential config is missing
}
// --- Initialize Infobip Client ---let infobipClient;
try {
infobipClient = newInfobip({
baseUrl: INFOBIP_BASE_URL,
apiKey: INFOBIP_API_KEY,
authType: AuthType.ApiKey,
});
console.log('Infobip client initialized.');
} catch (initError) {
console.error('Error initializing Infobip client:', initError);
process.exit(1);
}
// --- MMS Sending Function ---// (To be added next)// --- API Endpoint ---// (To be added in Section 3)// --- Start Server ---
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
We load environment variables first using dotenv.
We import Express and the necessary components from the Infobip SDK.
We enable Express's JSON body parser.
We retrieve configuration from process.env.
Crucially, we add validation to ensure API credentials are loaded, preventing runtime errors later.
We initialize the infobipClient once, providing the Base URL, API Key, and specifying the authentication type (ApiKey), wrapped in a try-catch for initialization errors.
Create the sendMmsMessage Function:
Add the following function within server.js (before the API endpoint section). This function takes the recipient number, media URL, and optional text, then constructs and sends the request using the Infobip SDK.
JavaScript
// server.js// ... (previous code) ...// --- MMS Sending Function ---asyncfunctionsendMmsMessage(toNumber, mediaUrl, messageText = '') {
console.log(`Attempting to send MMS to ${toNumber} with media ${mediaUrl}`);
// Basic validationif (!toNumber || !mediaUrl) {
thrownewError('Recipient number (toNumber) and media URL (mediaUrl) are required.');
}
// Construct the MMS payload.// Always consult the latest Infobip API documentation for the definitive MMS payload structure.const payload = {
messages: [
{
destinations: [{ to: toNumber }],
// 'from' might be required depending on your Infobip number setup// from: 'YourInfobipNumber',content: {
mediaUrl: mediaUrl, // URL of the publicly accessible mediatext: messageText, // Optional text part of the message
},
// Optional: Specify MMS template ID if using templates// templateid: 'YOUR_MMS_TEMPLATE_ID',// Optional: Add subject for MMShead: {
subject: 'MMS from Your App'// Example subject
}
}
],
// Optional: deliveryTimeWindow, notifyUrl, etc.
};
try {
// Use the appropriate SDK method for sending messages.// Verify the correct method (e.g., sms.send or a dedicated mms.send) in the SDK version you are using.// Infobip often uses the SMS channel endpoint for multiple message types based on payload content.const response = await infobipClient.channels.sms.send(payload);
console.log('Infobip API Response:', JSON.stringify(response.data, null, 2));
return response.data; // Return the success response body
} catch (error) {
console.error('Error sending MMS via Infobip:', error.response ? JSON.stringify(error.response.data, null, 2) : error.message);
// Re-throw a structured error for the API layer to handleconst errorMessage = error.response?.data?.requestError?.serviceException?.text || error.message || 'Unknown error sending MMS';
const status = error.response?.status || 500;
throw { status, message: errorMessage, details: error.response?.data };
}
}
// --- API Endpoint ---// (To be added in Section 3)// ... (rest of the code) ...
The function is async because SDK calls are asynchronous (return Promises).
It performs basic validation for required parameters.
Payload Construction: We define a content object containing mediaUrl and text, and an optional head for the subject. You must verify the exact structure required by the Infobip API/SDK.
SDK Call: We use infobipClient.channels.sms.send(payload). Infobip's unified APIs often handle different message types via the same endpoint based on payload. Verify the correct method in your SDK version's documentation.
Error Handling: We wrap the SDK call in a try...catch block, logging details and re-throwing a structured error for the API layer.
3. Building a Complete API Layer
Now, let's create the Express route that will use our sendMmsMessage function.
Add API Endpoint in server.js:
Place this code between the sendMmsMessage function and the app.listen call.
JavaScript
// server.js// ... (previous code, including sendMmsMessage function) ...// --- API Endpoint ---
app.post('/send-mms', async (req, res) => {
const { to, mediaUrl, text } = req.body;
// --- Request Validation ---if (!to) {
return res.status(400).json({ error: 'Missing required field: to (destination phone number)' });
}
if (!mediaUrl) {
return res.status(400).json({ error: 'Missing required field: mediaUrl (URL of the media file)' });
}
// Basic validation for number format (E.164 recommended)// A more robust validation library (like libphonenumber-js) is advised for productionif (!/^\+?[1-9]\d{1,14}$/.test(to)) {
return res.status(400).json({ error: 'Invalid phone number format. Use E.164 format (e.g., +14155552671).' });
}
// Basic URL validationtry {
newURL(mediaUrl);
} catch (_) {
return res.status(400).json({ error: 'Invalid mediaUrl format.' });
}
try {
const result = awaitsendMmsMessage(to, mediaUrl, text);
// Send success response based on Infobip's result
res.status(200).json({ success: true, data: result });
} catch (error) {
// Handle errors thrown from sendMmsMessageconsole.error(`API Error sending MMS to ${to}:`, error);
const statusCode = error.status || 500;
res.status(statusCode).json({ success: false, error: error.message || 'Failed to send MMS', details: error.details });
}
});
// ... (app.listen call) ...
We define a POST route at /send-mms.
It extracts to, mediaUrl, and optional text from the JSON request body (req.body).
Request Validation: We perform server-side validation for required fields and basic format checks (phone number, URL). Invalid requests are rejected with a 400 Bad Request. Robust validation is crucial.
It calls our sendMmsMessage function with the validated data.
If sendMmsMessage succeeds, it sends a 200 OK response.
If sendMmsMessage throws an error, it catches the error, logs it, and sends an appropriate HTTP error response.
Testing with cURL:
Make sure your server is running (node server.js). Open another terminal and use curl to test the endpoint. Replace placeholders with your verified phone number and a valid, publicly accessible image URL.
Bash
curl -X POST http://localhost:3000/send-mms \
-H ""Content-Type: application/json"" \
-d '{
""to"": ""+14155552671"",
""mediaUrl"": ""https://www.infobip.com/wp-content/uploads/2021/09/shape-2-1.png"",
""text"": ""Check out this cool image! Sent via Node.js and Infobip.""
}'
Securely handling your Infobip credentials is vital.
Obtain API Key and Base URL:
Log in to your Infobip account portal.
Navigate to the Homepage or Dashboard. Your Base URL (e.g., xxxxx.api.infobip.com) is usually displayed prominently here.
Go to Developers -> API Keys (or a similar section).
Click Create API Key (or similar). Give your key a descriptive name (e.g., NodeJS MMS App).
Copy the generated API Key immediately. You typically won't be able to see it again after closing the creation dialog.
Ensure the API key has the necessary permissions to send messages via the appropriate channels (SMS/MMS).
Store Credentials Securely:
As done in Step 1.7, open your local .env file (which is not committed to Git).
Paste your actual INFOBIP_API_KEY and INFOBIP_BASE_URL values.
.env
# .env (DO NOT COMMIT)
INFOBIP_API_KEY=paste_your_actual_api_key_here
INFOBIP_BASE_URL=paste_your_actual_base_url_here
PORT=3000
Code Integration (dotenv):
The line require('dotenv').config(); at the very top of server.js loads these variables into process.env.
The code then accesses them via process.env.INFOBIP_API_KEY and process.env.INFOBIP_BASE_URL. This keeps credentials out of the source code itself.
5. Error Handling, Logging, and Retry Mechanisms
Robust error handling and logging are essential for production systems.
Error Handling Strategy:
Validation Errors: The API endpoint (/send-mms) performs initial checks for required fields and basic format validation, returning 400 Bad Request errors immediately.
SDK/API Errors: The sendMmsMessage function wraps the Infobip SDK call in a try...catch. It catches errors from the SDK (network issues, API errors) and parses the response to extract meaningful error messages and status codes provided by Infobip. It then re-throws a structured error.
API Layer Catch-All: The async (req, res) handler in the API endpoint has its own try...catch to handle errors thrown by sendMmsMessage or any other unexpected issues, ensuring a JSON error response is always sent to the client instead of crashing the server.
Logging:
Basic Logging: We are using console.log for informational messages (server start, initialization, attempt messages) and console.error for errors.
Production Logging: For production, replace console.log/console.error with a dedicated logging library like winston or pino. This enables:
Different log levels (info, warn, error, debug).
Structured logging (JSON format) for easier parsing by log management systems.
Outputting logs to files, databases, or external services (like Datadog, Splunk).
Example Logging Points:
Log incoming requests (method, URL, source IP - be mindful of PII).
Log successful MMS dispatches with the messageId and bulkId.
Log detailed errors, including the full error object from Infobip when available.
Retry Mechanisms:
Why Retry? Transient network issues or temporary Infobip service unavailability might cause requests to fail. Retrying can improve reliability.
Implementation: Retries are not implemented in this basic example. For production, you could implement a retry strategy within the catch block of the sendMmsMessage function:
Check if the error is retryable (e.g., network errors, specific 5xx status codes from Infobip like 503 Service Unavailable). Do not retry client errors (4xx).
Implement exponential backoff: Wait increasingly longer durations between retries (e.g., 1s, 2s, 4s, 8s) to avoid overwhelming the service.
Limit the number of retries (e.g., 3 attempts).
Libraries like axios-retry (if using axios directly) or async-retry can help manage this logic. Since we use the SDK, manual implementation or checking if the SDK has built-in retry options is needed.
Testing Error Scenarios:
Send requests with missing fields (to, mediaUrl).
Send requests with invalid data (incorrect phone format, invalid URL).
Temporarily modify .env with an invalid INFOBIP_API_KEY to test authentication errors.
If possible, simulate network issues (though harder without specific tools).
Send to a non-existent or blocked number (if Infobip provides specific errors for these).
6. Database Schema and Data Layer
This specific application (a simple MMS sending proxy) does not require a database. It acts as a stateless gateway to the Infobip API.
If Persistence Were Needed:
Use Case: You might want to store a record of sent messages, their status (using messageId and potentially Delivery Reports - see Infobip docs), recipient details, timestamps, and associated application data.
Schema Example (e.g., PostgreSQL/MySQL):
SQL
CREATE TABLE mms_log (
id SERIAL PRIMARY KEY,
infobip_message_id VARCHAR(255) UNIQUE, -- From Infobip response
infobip_bulk_id VARCHAR(255), -- From Infobip response
recipient_number VARCHAR(20) NOT NULL,
media_url TEXT NOT NULL,
message_text TEXT,
status VARCHAR(50) DEFAULT'PENDING', -- e.g., PENDING, SENT, DELIVERED, FAILED
submitted_at TIMESTAMPWITHTIME ZONE DEFAULTCURRENT_TIMESTAMP,
last_updated_at TIMESTAMPWITHTIME ZONE DEFAULTCURRENT_TIMESTAMP,
error_message TEXT -- Store error if sending failed initially
);
-- Index for querying by messageId or recipientCREATE INDEX idx_mms_log_message_id ON mms_log(infobip_message_id);
CREATE INDEX idx_mms_log_recipient ON mms_log(recipient_number);
Data Layer: Use an Object-Relational Mapper (ORM) like Prisma, Sequelize, or TypeORM, or a query builder like Knex.js to interact with the database within your Node.js application. You would insert a record before or after calling the Infobip API and potentially update the status later based on delivery reports (which requires setting up a webhook endpoint to receive status updates from Infobip).
For this guide's scope, we omit database integration.
7. Adding Security Features
Security is paramount, especially when dealing with external APIs and user data.
Input Validation and Sanitization:
Validation: As implemented in Section 3, always validate incoming request data (req.body). Ensure required fields exist, check data types, and validate formats (phone numbers, URLs). Use libraries like joi or express-validator for more complex validation rules.
Sanitization: While less critical for this specific app where we mostly pass data to Infobip, sanitize any input that might be stored or reflected back to users to prevent Cross-Site Scripting (XSS). For URLs and phone numbers passed to Infobip, rely on Infobip's handling, but ensure your own logging or storage doesn't introduce vulnerabilities.
Protection Against Common Vulnerabilities:
API Key Security: Never hardcode API keys. Use environment variables (.env) and ensure .env is in .gitignore. Use tools like helmet middleware for Express to set various security-related HTTP headers (e.g., X-Frame-Options, Strict-Transport-Security).
Preventing Abuse: Implement measures to stop malicious actors from using your endpoint excessively.
Rate Limiting:
Prevent brute-force attacks and abuse by limiting the number of requests a client can make in a given time window. Use middleware like express-rate-limit.
Bash
npm install express-rate-limit
JavaScript
// server.jsconst rateLimit = require('express-rate-limit');
// ...const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutesmax: 100, // Limit each IP to 100 requests per windowMsstandardHeaders: true, // Return rate limit info in the `RateLimit-*` headerslegacyHeaders: false, // Disable the `X-RateLimit-*` headersmessage: 'Too many requests from this IP, please try again after 15 minutes'
});
app.use('/send-mms', apiLimiter); // Apply limiter specifically to the MMS endpoint// ... (rest of the API endpoint setup)
Authentication/Authorization (Optional):
Currently, the endpoint is open. In a real application, you'd protect it.
Methods: API Keys (specific to your users), JWT (JSON Web Tokens), OAuth2.
Implement middleware to check for valid authentication credentials before allowing access to the /send-mms route.
Media URL Security:
Ensure the mediaUrl points to a trusted source or implement checks if necessary. Be aware that your server will effectively instruct Infobip to fetch content from this URL.
8. Handling Special Cases
Real-world scenarios often involve edge cases.
Phone Number Formatting:
E.164 Standard: Infobip strongly recommends (and often requires) the E.164 format for phone numbers (e.g., +14155552671). E.164 is the ITU-T Recommendation (effective November 2010) defining the international public telecommunication numbering plan. Include the + and country code.
Validation: Use robust libraries like libphonenumber-js for parsing and validating numbers from various formats if your input source isn't strictly E.164.
Media URL Accessibility & Format:
Public Access: The mediaUrlmust be publicly accessible without requiring authentication, cookies, or complex redirects, so Infobip's servers can fetch it.
Supported Formats: Infobip supports common image formats (JPEG, PNG, GIF) and video/audio formats (check their documentation for specifics like MP4 H.264/AAC). Sending unsupported formats will likely fail.
Content Size Limits: MMS messages have size limits typically ranging from 300KB to 1MB, varying by destination carrier and country. U.S. carriers commonly support up to 600KB per MMS. Infobip may resize images automatically, but starting with optimized files ensures better delivery rates. Always verify current limits in Infobip's official SMS API documentation (/sms/2/text/advanced endpoint).
Character Encoding:
For the text part, use UTF-8 encoding, which is standard and supports a wide range of characters. The Infobip SDK/API generally handles this correctly.
Infobip Free Trial Limitations:
As noted previously, free trial accounts often restrict sending to the phone number used during registration. Ensure you test with that number initially. Paid accounts remove this limitation.
Destination Carrier Limitations:
Not all mobile carriers or handsets fully support MMS, or they might have stricter size limits or format restrictions. Delivery can fail due to downstream limitations beyond Infobip's control. Check Infobip's documentation or support for country/carrier-specific information if deliverability issues arise.
9. Implementing Performance Optimizations
For this simple proxy service, performance bottlenecks are unlikely within the Node.js application itself. The main latency will come from the network roundtrip to the Infobip API and Infobip's internal processing.
SDK Client Initialization: We correctly initialize the infobipClient once when the application starts, avoiding the overhead of creating a new client for every request.
Asynchronous Operations: Node.js and the async/await pattern handle the asynchronous nature of the API call efficiently without blocking the server.
Payload Size: Keep the request payload to Infobip minimal. Only include necessary fields.
Caching: Caching is not applicable here, as each request is unique and triggers a real-time send operation.
Load Testing: If handling high volume, use tools like k6, Artillery, or JMeter to simulate traffic against your /send-mms endpoint and monitor response times and resource usage (CPU, memory) of your Node.js process. This helps determine scaling needs.
Node.js Clustering: For multi-core servers, use Node.js's built-in cluster module or a process manager like pm2 in cluster mode to run multiple instances of your application, distributing the load across CPU cores.
10. Monitoring, Observability, and Analytics
Monitoring ensures your service is healthy and performing as expected.
Health Checks:
Add a simple health check endpoint that monitoring services can ping.
JavaScript
// server.js// ...
app.get('/health', (req, res) => {
// Basic check: server is running// Could add checks for Infobip connectivity if needed (e.g., a lightweight API call)
res.status(200).json({ status: 'UP', timestamp: newDate().toISOString() });
});
// ...
Performance Metrics:
Use Application Performance Monitoring (APM) tools (Datadog, New Relic, Dynatrace) or Node.js-specific libraries (prom-client for Prometheus) to track:
Request latency (average, p95, p99 for /send-mms).
Request rate (requests per second).
Error rates (percentage of 4xx and 5xx responses).
Node.js process metrics (CPU usage, memory usage, event loop lag).
Error Tracking:
Integrate services like Sentry or Bugsnag. These automatically capture unhandled exceptions and errors logged via supported logging libraries, providing stack traces, request context, and alerting.
Logging (Revisited):
Ensure logs (preferably structured JSON) are aggregated into a central system (ELK stack, Splunk, Datadog Logs, Loki) for searching, analysis, and dashboarding.
Key Metrics Dashboard:
Create dashboards in your monitoring/log management tool showing:
MMS Sent Rate (count of successful 200 responses from /send-mms).
MMS Error Rate (count of 4xx/5xx responses from /send-mms).
API Latency (for /send-mms).
Infobip API Error Breakdown (based on parsing error messages/codes from Infobip).
Server resource usage.
Alerting:
Set up alerts based on thresholds:
High error rate (> 5% of requests failing).
Increased latency (> 1 second average response time).
Server down (health check failing).
High resource usage (CPU > 80%, low available memory).
11. Troubleshooting and Caveats
Common issues and things to watch out for:
Error: Invalid login details (UNAUTHORIZED)
Cause: Incorrect INFOBIP_API_KEY or INFOBIP_BASE_URL in .env. API key might lack permissions.
Solution: Verify credentials in .env match those from the Infobip portal exactly. Check API key permissions in Infobip. Ensure .env is being loaded correctly (e.g., require('dotenv').config() is called early).
Error: Bad request / Invalid destination address
Cause: Phone number (to) format is incorrect. It might be missing the country code or +, or contain invalid characters.
Solution: Ensure numbers are passed in E.164 format (e.g., +14155552671). Implement stricter validation (consider libphonenumber-js).
Error: Cannot fetch media / Invalid media URL
Cause: The mediaUrl is inaccessible (private, requires login, typo), invalid, or points to an unsupported file type/size.
Solution: Verify the URL is public and correct. Check supported media types and size limits in Infobip docs. Test the URL in an incognito browser window or using curl.
Error: ServiceException / Internal Server Error from Infobip (5xx)
Cause: Temporary issue on Infobip's side.
Solution: Implement retry logic (Section 5) for transient errors. Check Infobip's status page. Contact Infobip support if persistent.
Messages Sent but Not Received:
Cause: Free trial limitations (sending only to registered number). Carrier filtering/blocking. Invalid or inactive destination number. Handset issues (MMS disabled, incompatible phone).
Solution: Confirm you're using the registered number if on a free trial. Check Infobip delivery reports (requires webhook setup) for status details. Verify the destination number is active and capable of receiving MMS. Test with different media types/sizes.
Frequently asked questions
This involves setting up an Express server, integrating the Infobip Node.js SDK, creating an API endpoint, handling credentials securely via environment variables, and constructing the MMS message payload according to Infobip's API specifications. The endpoint then uses the SDK to send the MMS via Infobip's platform to the designated recipient.
The Infobip Node.js SDK simplifies interaction with the Infobip API. It provides functions for various communication channels, including SMS and MMS, handling authentication, request construction, and response parsing, reducing the complexity compared to direct HTTP calls. It streamlines MMS sending by providing pre-built methods.
You'll create a .env file in your project's root directory containing your API key and base URL. Add INFOBIP_API_KEY=your_key and INFOBIP_BASE_URL=your_base_url to this file. Use the dotenv module to load these variables into process.env, so they can be accessed securely within your Node.js app.
Infobip recommends the E.164 format (e.g., +14155552671) to ensure consistent and reliable message delivery worldwide. This format includes the plus sign (+) followed by the country code and the subscriber number without any spaces or punctuation.
Implement thorough input validation and use try-catch blocks around Infobip API calls. Parse error responses for specific codes and messages to understand the reason for failures. Consider implementing retry logic for transient errors like service unavailability using exponential backoff and a limited number of attempts.
The payload requires "messages" which is an array of message objects. Each message needs "destinations" with the recipient "to" number, optional "from", "content" with "mediaUrl" and "text", an optional "templateid", and an optional "head" for the subject. Refer to the Infobip API documentation for the latest structure.
Common image formats like JPEG, PNG, and GIF are typically supported. Video/audio formats (MP4) are also supported, but always consult current Infobip documentation. MMS has size limits, roughly 300KB-1MB but varying by carrier; Infobip may resize images, but sending smaller, optimized media improves reliability and cost.
You need a database if message logging or tracking is required. Store details like message IDs, recipient info, send time, and delivery status for analytics or investigation.
This typically signifies an incorrect API key or base URL. Double-check your .env file. Ensure it is loaded correctly at the beginning of your server.js with require('dotenv').config(). Also, check if the API key has sufficient permissions in your Infobip account.
Use environment variables for sensitive information. Implement rate limiting using express-rate-limit to prevent abuse. Validate and sanitize input using tools like joi. Consider using helmet to set secure HTTP headers and adding authentication/authorization mechanisms to your API endpoint.
While E.164 is recommended, handle varied inputs using a dedicated phone number validation library like libphonenumber-js. It can parse and normalize diverse number formats into E.164, ensuring compatibility with Infobip.
This could be due to carrier limitations, Infobip free trial restrictions (sending only to the registered number), or issues with the recipient's device or network. Use Infobip's delivery reports (webhooks) for detailed status updates and troubleshoot accordingly.
Initialize the Infobip SDK client only once on server startup to reduce overhead. Use async/await to handle asynchronous API calls efficiently. Ensure minimal payload size to Infobip. Load test with tools like k6 or Artillery to identify bottlenecks and potential scaling issues.
Yes, use a logging library like Winston or Pino, especially for production. Structure logs in JSON format. Set up application performance monitoring (APM) with tools like Datadog or New Relic and track metrics like latency, error rates, and resource usage.
This guide provides a step-by-step walkthrough for building a Node.js application using the Express framework to send Multimedia Messaging Service (MMS) messages via the Infobip API. We will cover project setup, core implementation using the Infobip Node.js SDK, API endpoint creation, configuration, error handling, and deployment considerations.
By the end of this tutorial, you will have a functional Express API endpoint capable of accepting requests and sending MMS messages containing text and media (like images) to specified recipients through Infobip. This solves the common need for applications to engage users with richer media content beyond simple SMS.
Project Overview and Goals
Goal: To create a simple backend service that exposes an API endpoint for sending MMS messages.
Problem Solved: Enables applications to programmatically send rich media messages (images, potentially video/audio depending on Infobip/carrier support) alongside text, enhancing user communication and engagement compared to plain SMS.
Technologies:
@infobip-api/sdk): Provides the communication bridge to Infobip's platform for sending MMS messages. The SDK simplifies interaction compared to raw HTTP requests..envfile intoprocess.env, keeping sensitive credentials out of source code.System Architecture:
Prerequisites:
Final Outcome: A running Node.js Express server with a single API endpoint (
/send-mms) that accepts a destination phone number, a media URL, and optional text, then uses the Infobip SDK to dispatch the MMS message.1. Setting up the Project
Let's initialize our Node.js project and install the necessary dependencies.
Create Project Directory: Open your terminal or command prompt and create a new directory for your project, then navigate into it.
mkdir infobip-mms-sender cd infobip-mms-senderInitialize npm Project: This command creates a
package.jsonfile, which tracks project metadata and dependencies. You can accept the defaults or customize them.Install Dependencies: We need Express for the web server, the Infobip SDK for API interaction, and
dotenvfor managing environment variables.express: The web framework.@infobip-api/sdk: The official Infobip SDK for Node.js.dotenv: Loads environment variables from a.envfile.Create Project Structure: Create the basic files and directories.
Example commands:
# On Linux/macOS touch server.js .env .env.example .gitignore # On Windows (Command Prompt) type nul > server.js type nul > .env type nul > .env.example type nul > .gitignore # On Windows (PowerShell) New-Item server.js -ItemType File New-Item .env -ItemType File New-Item .env.example -ItemType File New-Item .gitignore -ItemType Fileserver.js: Our main application file containing the Express server logic..env: Stores sensitive credentials (API Key, Base URL). This file should NOT be committed to version control..env.example: A template showing required environment variables. This should be committed..gitignore: Specifies intentionally untracked files that Git should ignore (like.envandnode_modules).Configure
.gitignore: Add the following lines to your.gitignorefile to prevent committing sensitive data and unnecessary files:Set up Environment Variable Examples: Add the following structure to
.env.example:Populate
.env: Copy.env.exampleto.envand replace the placeholder values with your actual Infobip credentials (obtained in Section 4).INFOBIP_API_KEY: Your unique key for authenticating API requests.INFOBIP_BASE_URL: The specific API domain assigned to your Infobip account.PORT: The port number your Express server will listen on.2. Implementing Core Functionality (MMS Sending Logic)
We'll encapsulate the logic for interacting with the Infobip SDK within a dedicated function.
Edit
server.js: Openserver.jsand start by requiring the necessary modules and configuringdotenv.// server.js require('dotenv').config(); // Load environment variables from .env file const express = require('express'); const { Infobip, AuthType } = require('@infobip-api/sdk'); const app = express(); app.use(express.json()); // Middleware to parse JSON request bodies const PORT = process.env.PORT || 3000; const INFOBIP_API_KEY = process.env.INFOBIP_API_KEY; const INFOBIP_BASE_URL = process.env.INFOBIP_BASE_URL; // --- Input Validation --- if (!INFOBIP_API_KEY || !INFOBIP_BASE_URL) { console.error('Error: Infobip API Key or Base URL not found in environment variables.'); process.exit(1); // Exit if essential config is missing } // --- Initialize Infobip Client --- let infobipClient; try { infobipClient = new Infobip({ baseUrl: INFOBIP_BASE_URL, apiKey: INFOBIP_API_KEY, authType: AuthType.ApiKey, }); console.log('Infobip client initialized.'); } catch (initError) { console.error('Error initializing Infobip client:', initError); process.exit(1); } // --- MMS Sending Function --- // (To be added next) // --- API Endpoint --- // (To be added in Section 3) // --- Start Server --- app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); });dotenv.process.env.infobipClientonce, providing the Base URL, API Key, and specifying the authentication type (ApiKey), wrapped in a try-catch for initialization errors.Create the
sendMmsMessageFunction: Add the following function withinserver.js(before the API endpoint section). This function takes the recipient number, media URL, and optional text, then constructs and sends the request using the Infobip SDK.// server.js // ... (previous code) ... // --- MMS Sending Function --- async function sendMmsMessage(toNumber, mediaUrl, messageText = '') { console.log(`Attempting to send MMS to ${toNumber} with media ${mediaUrl}`); // Basic validation if (!toNumber || !mediaUrl) { throw new Error('Recipient number (toNumber) and media URL (mediaUrl) are required.'); } // Construct the MMS payload. // Always consult the latest Infobip API documentation for the definitive MMS payload structure. const payload = { messages: [ { destinations: [{ to: toNumber }], // 'from' might be required depending on your Infobip number setup // from: 'YourInfobipNumber', content: { mediaUrl: mediaUrl, // URL of the publicly accessible media text: messageText, // Optional text part of the message }, // Optional: Specify MMS template ID if using templates // templateid: 'YOUR_MMS_TEMPLATE_ID', // Optional: Add subject for MMS head: { subject: 'MMS from Your App' // Example subject } } ], // Optional: deliveryTimeWindow, notifyUrl, etc. }; try { // Use the appropriate SDK method for sending messages. // Verify the correct method (e.g., sms.send or a dedicated mms.send) in the SDK version you are using. // Infobip often uses the SMS channel endpoint for multiple message types based on payload content. const response = await infobipClient.channels.sms.send(payload); console.log('Infobip API Response:', JSON.stringify(response.data, null, 2)); return response.data; // Return the success response body } catch (error) { console.error('Error sending MMS via Infobip:', error.response ? JSON.stringify(error.response.data, null, 2) : error.message); // Re-throw a structured error for the API layer to handle const errorMessage = error.response?.data?.requestError?.serviceException?.text || error.message || 'Unknown error sending MMS'; const status = error.response?.status || 500; throw { status, message: errorMessage, details: error.response?.data }; } } // --- API Endpoint --- // (To be added in Section 3) // ... (rest of the code) ...asyncbecause SDK calls are asynchronous (return Promises).contentobject containingmediaUrlandtext, and an optionalheadfor the subject. You must verify the exact structure required by the Infobip API/SDK.infobipClient.channels.sms.send(payload). Infobip's unified APIs often handle different message types via the same endpoint based on payload. Verify the correct method in your SDK version's documentation.try...catchblock, logging details and re-throwing a structured error for the API layer.3. Building a Complete API Layer
Now, let's create the Express route that will use our
sendMmsMessagefunction.Add API Endpoint in
server.js: Place this code between thesendMmsMessagefunction and theapp.listencall.// server.js // ... (previous code, including sendMmsMessage function) ... // --- API Endpoint --- app.post('/send-mms', async (req, res) => { const { to, mediaUrl, text } = req.body; // --- Request Validation --- if (!to) { return res.status(400).json({ error: 'Missing required field: to (destination phone number)' }); } if (!mediaUrl) { return res.status(400).json({ error: 'Missing required field: mediaUrl (URL of the media file)' }); } // Basic validation for number format (E.164 recommended) // A more robust validation library (like libphonenumber-js) is advised for production if (!/^\+?[1-9]\d{1,14}$/.test(to)) { return res.status(400).json({ error: 'Invalid phone number format. Use E.164 format (e.g., +14155552671).' }); } // Basic URL validation try { new URL(mediaUrl); } catch (_) { return res.status(400).json({ error: 'Invalid mediaUrl format.' }); } try { const result = await sendMmsMessage(to, mediaUrl, text); // Send success response based on Infobip's result res.status(200).json({ success: true, data: result }); } catch (error) { // Handle errors thrown from sendMmsMessage console.error(`API Error sending MMS to ${to}:`, error); const statusCode = error.status || 500; res.status(statusCode).json({ success: false, error: error.message || 'Failed to send MMS', details: error.details }); } }); // ... (app.listen call) ...POSTroute at/send-mms.to,mediaUrl, and optionaltextfrom the JSON request body (req.body).400 Bad Request. Robust validation is crucial.sendMmsMessagefunction with the validated data.sendMmsMessagesucceeds, it sends a200 OKresponse.sendMmsMessagethrows an error, it catches the error, logs it, and sends an appropriate HTTP error response.Testing with cURL: Make sure your server is running (
node server.js). Open another terminal and usecurlto test the endpoint. Replace placeholders with your verified phone number and a valid, publicly accessible image URL.curl -X POST http://localhost:3000/send-mms \ -H ""Content-Type: application/json"" \ -d '{ ""to"": ""+14155552671"", ""mediaUrl"": ""https://www.infobip.com/wp-content/uploads/2021/09/shape-2-1.png"", ""text"": ""Check out this cool image! Sent via Node.js and Infobip."" }'Expected Success Response (Example):
{ ""success"": true, ""data"": { ""messages"": [ { ""to"": ""+14155552671"", ""status"": { ""groupId"": 1, ""groupName"": ""PENDING"", ""id"": 26, ""name"": ""PENDING_ACCEPTED"", ""description"": ""Message accepted, pending delivery."" }, ""messageId"": ""some-unique-message-id-from-infobip"" } ], ""bulkId"": ""some-unique-bulk-id-from-infobip"" } }Expected Error Response (Example - Invalid API Key):
{ ""success"": false, ""error"": ""Invalid login details"", ""details"": { ""requestError"": { ""serviceException"": { ""messageId"": ""UNAUTHORIZED"", ""text"": ""Invalid login details"" } } } }(Response status code would be 401 Unauthorized)
4. Integrating with Infobip (Credentials)
Securely handling your Infobip credentials is vital.
Obtain API Key and Base URL:
xxxxx.api.infobip.com) is usually displayed prominently here.NodeJS MMS App).Store Credentials Securely:
.envfile (which is not committed to Git).INFOBIP_API_KEYandINFOBIP_BASE_URLvalues.Code Integration (
dotenv):require('dotenv').config();at the very top ofserver.jsloads these variables intoprocess.env.process.env.INFOBIP_API_KEYandprocess.env.INFOBIP_BASE_URL. This keeps credentials out of the source code itself.5. Error Handling, Logging, and Retry Mechanisms
Robust error handling and logging are essential for production systems.
Error Handling Strategy:
/send-mms) performs initial checks for required fields and basic format validation, returning400 Bad Requesterrors immediately.sendMmsMessagefunction wraps the Infobip SDK call in atry...catch. It catches errors from the SDK (network issues, API errors) and parses the response to extract meaningful error messages and status codes provided by Infobip. It then re-throws a structured error.async (req, res)handler in the API endpoint has its owntry...catchto handle errors thrown bysendMmsMessageor any other unexpected issues, ensuring a JSON error response is always sent to the client instead of crashing the server.Logging:
console.logfor informational messages (server start, initialization, attempt messages) andconsole.errorfor errors.console.log/console.errorwith a dedicated logging library likewinstonorpino. This enables:messageIdandbulkId.Retry Mechanisms:
catchblock of thesendMmsMessagefunction:503 Service Unavailable). Do not retry client errors (4xx).axios-retry(if using axios directly) orasync-retrycan help manage this logic. Since we use the SDK, manual implementation or checking if the SDK has built-in retry options is needed.Testing Error Scenarios:
to,mediaUrl)..envwith an invalidINFOBIP_API_KEYto test authentication errors.6. Database Schema and Data Layer
This specific application (a simple MMS sending proxy) does not require a database. It acts as a stateless gateway to the Infobip API.
If Persistence Were Needed:
Use Case: You might want to store a record of sent messages, their status (using
messageIdand potentially Delivery Reports - see Infobip docs), recipient details, timestamps, and associated application data.Schema Example (e.g., PostgreSQL/MySQL):
CREATE TABLE mms_log ( id SERIAL PRIMARY KEY, infobip_message_id VARCHAR(255) UNIQUE, -- From Infobip response infobip_bulk_id VARCHAR(255), -- From Infobip response recipient_number VARCHAR(20) NOT NULL, media_url TEXT NOT NULL, message_text TEXT, status VARCHAR(50) DEFAULT 'PENDING', -- e.g., PENDING, SENT, DELIVERED, FAILED submitted_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, last_updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, error_message TEXT -- Store error if sending failed initially ); -- Index for querying by messageId or recipient CREATE INDEX idx_mms_log_message_id ON mms_log(infobip_message_id); CREATE INDEX idx_mms_log_recipient ON mms_log(recipient_number);Data Layer: Use an Object-Relational Mapper (ORM) like Prisma, Sequelize, or TypeORM, or a query builder like Knex.js to interact with the database within your Node.js application. You would insert a record before or after calling the Infobip API and potentially update the
statuslater based on delivery reports (which requires setting up a webhook endpoint to receive status updates from Infobip).For this guide's scope, we omit database integration.
7. Adding Security Features
Security is paramount, especially when dealing with external APIs and user data.
Input Validation and Sanitization:
req.body). Ensure required fields exist, check data types, and validate formats (phone numbers, URLs). Use libraries likejoiorexpress-validatorfor more complex validation rules.Protection Against Common Vulnerabilities:
API Key Security: Never hardcode API keys. Use environment variables (
.env) and ensure.envis in.gitignore. Use tools likehelmetmiddleware for Express to set various security-related HTTP headers (e.g.,X-Frame-Options,Strict-Transport-Security).// server.js const helmet = require('helmet'); // ... app.use(helmet()); // Add recommended security headers // ...Preventing Abuse: Implement measures to stop malicious actors from using your endpoint excessively.
Rate Limiting:
Prevent brute-force attacks and abuse by limiting the number of requests a client can make in a given time window. Use middleware like
express-rate-limit.// server.js const rateLimit = require('express-rate-limit'); // ... const apiLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per windowMs standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers message: 'Too many requests from this IP, please try again after 15 minutes' }); app.use('/send-mms', apiLimiter); // Apply limiter specifically to the MMS endpoint // ... (rest of the API endpoint setup)Authentication/Authorization (Optional):
/send-mmsroute.Media URL Security:
mediaUrlpoints to a trusted source or implement checks if necessary. Be aware that your server will effectively instruct Infobip to fetch content from this URL.8. Handling Special Cases
Real-world scenarios often involve edge cases.
Phone Number Formatting:
+14155552671). E.164 is the ITU-T Recommendation (effective November 2010) defining the international public telecommunication numbering plan. Include the+and country code.libphonenumber-jsfor parsing and validating numbers from various formats if your input source isn't strictly E.164.Media URL Accessibility & Format:
mediaUrlmust be publicly accessible without requiring authentication, cookies, or complex redirects, so Infobip's servers can fetch it./sms/2/text/advancedendpoint).Character Encoding:
textpart, use UTF-8 encoding, which is standard and supports a wide range of characters. The Infobip SDK/API generally handles this correctly.Infobip Free Trial Limitations:
Destination Carrier Limitations:
9. Implementing Performance Optimizations
For this simple proxy service, performance bottlenecks are unlikely within the Node.js application itself. The main latency will come from the network roundtrip to the Infobip API and Infobip's internal processing.
infobipClientonce when the application starts, avoiding the overhead of creating a new client for every request.async/awaitpattern handle the asynchronous nature of the API call efficiently without blocking the server.k6,Artillery, orJMeterto simulate traffic against your/send-mmsendpoint and monitor response times and resource usage (CPU, memory) of your Node.js process. This helps determine scaling needs.clustermodule or a process manager likepm2in cluster mode to run multiple instances of your application, distributing the load across CPU cores.10. Monitoring, Observability, and Analytics
Monitoring ensures your service is healthy and performing as expected.
Health Checks:
// server.js // ... app.get('/health', (req, res) => { // Basic check: server is running // Could add checks for Infobip connectivity if needed (e.g., a lightweight API call) res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() }); }); // ...Performance Metrics:
prom-clientfor Prometheus) to track:/send-mms).Error Tracking:
Logging (Revisited):
Key Metrics Dashboard:
/send-mms)./send-mms)./send-mms).Alerting:
11. Troubleshooting and Caveats
Common issues and things to watch out for:
Error: Invalid login details (
UNAUTHORIZED)INFOBIP_API_KEYorINFOBIP_BASE_URLin.env. API key might lack permissions..envmatch those from the Infobip portal exactly. Check API key permissions in Infobip. Ensure.envis being loaded correctly (e.g.,require('dotenv').config()is called early).Error: Bad request / Invalid destination address
to) format is incorrect. It might be missing the country code or+, or contain invalid characters.+14155552671). Implement stricter validation (considerlibphonenumber-js).Error: Cannot fetch media / Invalid media URL
mediaUrlis inaccessible (private, requires login, typo), invalid, or points to an unsupported file type/size.curl.Error: ServiceException / Internal Server Error from Infobip (5xx)
Messages Sent but Not Received: