code examples
code examples
Send MMS Messages with Node.js, Express, and Infobip API
Complete guide to building a production-ready Node.js application using Express framework to send MMS messages via Infobip API. Covers setup, authentication, error handling, 10DLC registration, security, and deployment.
This guide provides a complete walkthrough for building a production-ready Node.js application using the Express framework to send Multimedia Messaging Service (MMS) messages via the Infobip API. We'll cover everything from project setup and core MMS sending logic to error handling, security considerations, and deployment.
By the end of this tutorial, you'll have a functional Express API endpoint capable of accepting requests to send MMS messages containing text and media (like images) to specified recipients through Infobip.
Project Goals:
- Create a simple REST API endpoint using Node.js and Express.
- Integrate the Infobip Node.js SDK to send MMS messages.
- Securely manage API credentials.
- Implement basic error handling and logging.
- Provide clear steps for setup, implementation, testing, and deployment.
Technology Stack:
- Node.js: A JavaScript runtime environment for building server-side applications. Minimum version 14 required for the Infobip SDK.
- Express: A minimal and flexible Node.js web application framework.
- Infobip Node.js SDK (
@infobip-api/sdk): The official library for interacting with the Infobip API, simplifying MMS sending. Version 0.3.1 (released September 17, 2023) is the latest stable release. - dotenv: A module to load environment variables from a
.envfile.
System Architecture:
The basic flow involves a client (like curl, Postman, or a frontend application) making an HTTP POST request to our Express API. The Express app validates the request, uses the Infobip SDK (configured with your API key and base URL) to send the MMS message via the Infobip platform, and then returns a response to the client indicating success or failure.
graph LR
A[Client Application / curl] -- HTTP POST Request --> B(Node.js / Express API);
B -- Send MMS Request (SDK) --> C(Infobip API);
C -- MMS Sent --> D(Recipient Handset);
C -- API Response (Success/Error) --> B;
B -- API Response --> A;Prerequisites:
- Infobip Account: You need an active Infobip account. Sign up if you don't have one.
- API Key: Generated from your Infobip account security settings.
- Base URL: Your unique API domain provided by Infobip (e.g.,
xxxxx.api.infobip.com). - MMS-enabled Sender Number: You need a phone number provisioned on Infobip that is capable of sending MMS messages.
- US 10DLC Registration (Required for A2P MMS): If sending MMS in the United States for Application-to-Person (A2P) messaging, you must complete 10DLC registration through The Campaign Registry (TCR). As of September 1, 2023, all SMS and MMS messages sent to US phone numbers from unregistered 10DLC phone numbers are blocked by carriers. Registration requires:
- Brand Registration: Business identification with valid Tax ID (EIN) matching your business name
- Campaign Registration: Description of message campaign objectives, use case, and opt-in collection methods
- Processing Time: Allow several business days for approval
- You can request a number via the Infobip portal under "Channels and Numbers".
- US 10DLC Registration (Required for A2P MMS): If sending MMS in the United States for Application-to-Person (A2P) messaging, you must complete 10DLC registration through The Campaign Registry (TCR). As of September 1, 2023, all SMS and MMS messages sent to US phone numbers from unregistered 10DLC phone numbers are blocked by carriers. Registration requires:
- Important: Free trial accounts often have limitations, typically restricting sending only to the phone number used during registration. Attempts to send to other numbers will likely fail.
- Node.js and npm (or yarn): Installed on your development machine. Node.js 14 or higher is required for the Infobip SDK. Download Node.js from https://nodejs.org/.
- Basic JavaScript/Node.js knowledge: Familiarity with asynchronous programming (
async/await) is helpful. - Terminal/Command Line access.
1. Setting Up the Project
Let's initialize our Node.js project and install the necessary dependencies.
-
Create Project Directory: Open your terminal and create a new directory for the project, then navigate into it.
bashmkdir infobip-mms-sender cd infobip-mms-sender -
Initialize npm Project: This creates a
package.jsonfile to manage dependencies and project metadata.bashnpm init -y(The
-yflag accepts the default settings). -
Install Dependencies: We need Express for the web server, the Infobip SDK, and
dotenvfor managing environment variables.bashnpm install express @infobip-api/sdk dotenv express-validator(If you prefer yarn:
yarn add express @infobip-api/sdk dotenv express-validator) (Note: Addedexpress-validatorhere as it's used later). -
Create Project Structure: Set up a basic directory structure for better organization.
bashmkdir src mkdir src/routes mkdir src/controllers mkdir src/services touch src/index.js touch src/routes/mmsRoutes.js touch src/controllers/mmsController.js touch src/services/infobipService.js touch .env touch .gitignore -
Configure
.gitignore: Prevent sensitive information and unnecessary files from being committed to version control. Add the following lines to your.gitignorefile:plaintext# .gitignore node_modules/ .env npm-debug.log *.log -
Set Up Environment Variables: Open the
.envfile and add your Infobip credentials and sender number. Never commit this file to Git.bash# .env INFOBIP_API_KEY=YOUR_INFOBIP_API_KEY INFOBIP_BASE_URL=YOUR_INFOBIP_BASE_URL INFOBIP_SENDER_NUMBER=YOUR_MMS_ENABLED_SENDER_NUMBER PORT=3000- Replace
YOUR_INFOBIP_API_KEYwith the key from your Infobip account. - Replace
YOUR_INFOBIP_BASE_URLwith your specific Infobip API domain. - Replace
YOUR_MMS_ENABLED_SENDER_NUMBERwith the number you'll send MMS from (must be MMS capable and in international format, e.g.,14155552671). PORTdefines the port your Express server will listen on.
- Replace
-
Update
package.jsonScripts: Addstartanddevscripts topackage.jsonfor easily running the application. Replace the existingscriptsblock or add this one:json{ "scripts": { "start": "node src/index.js", "dev": "node --watch src/index.js", "test": "echo \"Error: no test specified\" && exit 1" } }- (Ensure this JSON snippet is placed correctly within your
package.jsonfile, replacing the existingscriptsobject if necessary). npm startwill run the application.npm run devwill run the application and automatically restart it when file changes are detected (requires Node.js v18.11.0+ for--watch).
- (Ensure this JSON snippet is placed correctly within your
2. Implementing Core Functionality (MMS Sending Service)
We'll encapsulate the Infobip SDK interaction within a dedicated service file.
-
Configure Infobip SDK Client: In
src/services/infobipService.js, import the SDK and initialize the client using the environment variables.javascript// src/services/infobipService.js import { Infobip, AuthType } from '@infobip-api/sdk'; import dotenv from 'dotenv'; dotenv.config(); // Load environment variables from .env file const apiKey = process.env.INFOBIP_API_KEY; const baseUrl = process.env.INFOBIP_BASE_URL; if (!apiKey || !baseUrl) { console.error( 'Error: INFOBIP_API_KEY and INFOBIP_BASE_URL must be set in the .env file.' ); process.exit(1); // Exit if essential config is missing } // Initialize Infobip client const infobipClient = new Infobip({ baseUrl: baseUrl, apiKey: apiKey, authType: AuthType.ApiKey, // Use API Key authentication }); /** * Sends an MMS message using the Infobip SDK. * @param {string} recipientNumber - The recipient's phone number in international format. * @param {string} senderNumber - The MMS-enabled sender number. * @param {string} subject - The subject of the MMS message. * @param {string} textContent - The text part of the message. * @param {string} mediaUrl - The publicly accessible URL of the media file (e.g., image). * @returns {Promise<object>} - The response data from the Infobip API. * @throws {Error} - Throws an error if the MMS sending fails. */ export const sendMms = async ( recipientNumber, senderNumber, subject, textContent, mediaUrl ) => { console.log( `Attempting to send MMS to ${recipientNumber} from ${senderNumber}` ); // Construct the MMS payload. // IMPORTANT: The payload structure and SDK method (`channels.mms.send`) shown here // are based on common patterns. Always consult the *official* Infobip API // documentation and Node.js SDK reference for the most current and accurate details, // as these can change over time. const payload = { from: senderNumber, to: recipientNumber, subject: subject, content: [ // First content part: Text { type: 'TEXT', text: textContent, }, // Second content part: Media (Image in this case) { type: 'IMAGE', // Can be 'VIDEO', etc. Adjust content type as needed. url: mediaUrl, // filename: 'image.jpg' // Optional: can specify a filename }, ], // smil: '<smil>...</smil>' // Optional: SMIL structure - Generally avoid due to iOS incompatibility }; try { // Use the Infobip SDK's MMS channel to send the message. // Verify this method call against current Infobip SDK documentation. const response = await infobipClient.channels.mms.send(payload); console.log('Infobip API Response:', JSON.stringify(response.data, null, 2)); return response.data; // Return the successful response body } catch (error) { // Log the detailed error from Infobip if available const errorDetails = error.response ? JSON.stringify(error.response.data, null, 2) : error.message; console.error(`Error sending MMS via Infobip: ${errorDetails}`); // Extract a user-friendly message if possible from the Infobip error response const errorMessage = error.response?.data?.requestError?.serviceException?.text || error.message || 'Unknown error occurred while sending MMS via Infobip.'; throw new Error(`Failed to send MMS: ${errorMessage}`); } }; // Export the client only if needed elsewhere (unlikely for this example) // export { infobipClient };- Explanation:
- We load environment variables using
dotenv. - We initialize the
Infobipclient with thebaseUrl,apiKey, and specifyAuthType.ApiKey. - The
sendMmsfunction takes recipient, sender, subject, text, and a media URL as parameters. - Payload Structure & SDK Method: The code constructs a payload and uses
infobipClient.channels.mms.send(payload). Crucially, verify the exact payload structure and method path in the official Infobip SDK/API documentation as this is a common point of failure if outdated. - Error Handling: A
try...catchblock wraps the API call. It logs detailed errors and throws a new, potentially more user-friendly error message extracted from the Infobip response if possible.
- We load environment variables using
- Explanation:
3. Building the API Layer (Express Route and Controller)
Now, let's create the Express endpoint that will receive requests and use our infobipService.
-
Create MMS Controller: This file handles the request logic for MMS sending.
javascript// src/controllers/mmsController.js import { sendMms } from '../services/infobipService.js'; const senderNumber = process.env.INFOBIP_SENDER_NUMBER; if (!senderNumber) { console.error('Error: INFOBIP_SENDER_NUMBER must be set in the .env file.'); process.exit(1); } export const handleSendMms = async (req, res) => { // Input validation should have been performed by middleware (see routes) const { recipientNumber, subject, textContent, mediaUrl } = req.body; try { const result = await sendMms( recipientNumber, senderNumber, // Use the sender number from .env subject, textContent, mediaUrl ); // Respond with the success details from Infobip res.status(200).json({ message: 'MMS submitted successfully to Infobip.', details: result, }); } catch (error) { console.error('API Error handling MMS request:', error.message); // Respond with a generic server error message // Avoid leaking detailed internal errors to the client in production res.status(500).json({ message: 'Failed to send MMS.', // Use the simplified error message thrown by the service layer error: error.message || 'Internal Server Error', }); } };- Explanation:
- Imports the
sendMmsfunction. - Retrieves the
INFOBIP_SENDER_NUMBERfrom environment variables. - Assumes input validation is handled by middleware (like
express-validator). - Calls the
sendMmsservice function. - Responds with
200 OKand the Infobip result on success. - Responds with
500 Internal Server Erroron failure, logging the detailed error server-side but returning the simplified error message from the service layer.
- Imports the
- Explanation:
-
Create MMS Routes: Define the API endpoint and include validation middleware.
javascript// src/routes/mmsRoutes.js import express from 'express'; import { handleSendMms } from '../controllers/mmsController.js'; import { body, validationResult } from 'express-validator'; // Import validation functions const router = express.Router(); // Define the POST endpoint for sending MMS with validation middleware // POST /api/mms/send router.post('/send', // Validation rules: body('recipientNumber') .isMobilePhone('any', { strictMode: false }) // 'any' allows various formats, strictMode: false is more permissive .withMessage('Invalid recipient phone number format. Use international format ideally (e.g., +14155552671).') .trim(), // Sanitize body('subject') .trim() .notEmpty().withMessage('Subject is required.'), body('textContent') .trim() .notEmpty().withMessage('Text content is required.'), body('mediaUrl') .trim() .isURL({ protocols: ['http','https'], require_protocol: true }) .withMessage('Invalid or insecure media URL format. Must be a valid HTTP/HTTPS URL.'), // Middleware to handle validation results: (req, res, next) => { const errors = validationResult(req); if (!errors.isEmpty()) { // If validation errors exist, return 400 Bad Request return res.status(400).json({ errors: errors.array() }); } // If validation passes, proceed to the controller next(); }, // Controller function to handle the request after validation: handleSendMms ); export default router;- Explanation:
- Imports Express, the controller, and
express-validatorfunctions. - Defines the
POST /api/mms/sendroute. - Uses
express-validatormiddleware (body(...)) to define validation and basic sanitization rules (trim()) before the controller runs. - Includes a check for
validationResult. If errors occur, it sends a400 Bad Requestresponse. Otherwise,next()passes control tohandleSendMms. - The
isMobilePhonevalidation usesstrictMode: falsefor broader acceptance but notes the ideal format. - The
isURLvalidation ensures a valid HTTP/HTTPS URL.
- Imports Express, the controller, and
- Explanation:
-
Set Up Express Server: Tie everything together in
src/index.js.javascript// src/index.js import express from 'express'; import dotenv from 'dotenv'; import mmsRoutes from './routes/mmsRoutes.js'; // Import the MMS routes // Load environment variables dotenv.config(); const app = express(); const port = process.env.PORT || 3000; // Use port from .env or default to 3000 // Middleware app.use(express.json()); // Enable parsing JSON request bodies app.use(express.urlencoded({ extended: true })); // Enable parsing URL-encoded bodies // Basic Logging Middleware (optional) app.use((req, res, next) => { console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); next(); // Pass control to the next middleware/route handler }); // API Routes app.use('/api/mms', mmsRoutes); // Mount the MMS routes under /api/mms // Basic Health Check Endpoint app.get('/health', (req, res) => { res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() }); }); // Global Error Handler (Basic Example) // Catches errors not handled in specific routes or middleware before it app.use((err, req, res, next) => { console.error('Unhandled Error:', err.stack || err); // Log the stack trace // Avoid sending stack trace in production response const errorMessage = process.env.NODE_ENV === 'production' ? 'Something went wrong!' : err.message || 'Internal Server Error'; res.status(err.status || 500).json({ message: errorMessage, // Optionally include error code or type in production if safe }); }); // Start the server app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); });- Explanation:
- Initializes Express.
- Loads environment variables.
- Uses
express.json()andexpress.urlencoded()middleware. - Mounts the
mmsRoutesunder/api/mms. - Includes a
/healthendpoint and a basic global error handler that avoids leaking stack traces in production. - Starts the server.
- Explanation:
4. Integrating with Infobip (Configuration Recap)
- API Key and Base URL: Securely stored in
.envand loaded viadotenv. Ensure.envis in.gitignore. - Infobip SDK Initialization: Handled in
src/services/infobipService.js. - MMS-Enabled Sender Number: Stored in
.envand used as thefromaddress. Verify this number is provisioned for MMS in your Infobip account (""Channels and Numbers"" section).
5. Error Handling, Logging, and Retries
-
Error Handling:
infobipService.jscatches SDK errors, logs details, and throws simplified errors.mmsController.jscatches service errors, logs them, and returns500 Internal Server Errorwith the simplified message.express-validatorinmmsRoutes.jshandles input validation errors, returning400 Bad Request.index.jshas a global error handler for unexpected errors.- Production: Consider using dedicated error tracking services (e.g., Sentry, Datadog).
-
Logging:
- Basic
console.log/console.erroris used. - Production: Implement structured logging (e.g., using libraries like
winstonorpino) for better analysis and integration with log management systems.
- Basic
-
Retry Mechanisms:
- Consider implementing retries for transient network errors or specific HTTP status codes like
429 Too Many Requestsfrom Infobip. Libraries likep-retrycan simplify this. Be cautious not to retry permanent failures (e.g., invalid credentials, bad requests4xxother than429). - Conceptual Example (requires
npm install p-retry): The following shows how you might integratep-retryinto thesendMmsfunction ininfobipService.js. Adapt it carefully based on which errors are deemed retryable according to Infobip's API behavior.
javascript// Conceptual example using p-retry inside infobipService.js sendMms function: /* import pRetry from 'p-retry'; // Make sure to install and import // Inside the sendMms function, replace the try...catch block with this: try { const response = await pRetry( async () => { // The operation to retry - ensure infobipClient and payload are accessible here console.log('Attempting Infobip API call...'); // Note: If infobipClient itself throws an error that shouldn't be retried, // you might need more sophisticated error checking within this async function // or check the error type in onFailedAttempt. return await infobipClient.channels.mms.send(payload); }, { retries: 3, // Number of retries (e.g., 3 attempts after the initial one) minTimeout: 1000, // Initial delay in milliseconds (1 second) factor: 2, // Exponential backoff factor (delay doubles each retry) onFailedAttempt: error => { // Log retry attempts and check if the error is retryable console.warn( `MMS send attempt ${error.attemptNumber} failed. Retrying in ${error.retryAfter} ms. Error: ${error.message}` ); // Example: Don't retry on client errors (4xx) other than 429 (Too Many Requests) // Adjust status code checks based on Infobip's actual responses for transient vs permanent errors. const statusCode = error.response?.status; if (statusCode && statusCode >= 400 && statusCode !== 429 && statusCode < 500) { console.error(`Non-retryable client error (${statusCode}), aborting retries.`); throw error; // Abort retrying for non-retryable client errors } // Optionally add checks for specific retryable 5xx errors if needed }, } ); // If pRetry succeeds: console.log('Infobip API Response after retries:', JSON.stringify(response.data, null, 2)); return response.data; } catch (error) { // This catch block executes if all retries failed or if a non-retryable error was thrown const errorDetails = error.response ? JSON.stringify(error.response.data, null, 2) : error.message; console.error(`Error sending MMS via Infobip after retries: ${errorDetails}`); const errorMessage = error.response?.data?.requestError?.serviceException?.text || error.message || 'Unknown error occurred after retries.'; throw new Error(`Failed to send MMS: ${errorMessage}`); } */ - Consider implementing retries for transient network errors or specific HTTP status codes like
6. Database Schema and Data Layer (Optional Logging)
Logging message attempts and delivery statuses to a database provides valuable tracking and auditing capabilities.
Simple Logging Approach (No DB): Console logging is implemented as shown. File logging or external log aggregation services are alternatives for persistence.
Database Approach (Example Schema): If using a database (e.g., PostgreSQL, MongoDB) potentially with an ORM (e.g., Prisma, Sequelize):
- Entity Relationship Diagram (Conceptual):
mermaid
erDiagram MMS_LOG ||--o{ MMS_RECIPIENT : contains MMS_LOG { string messageId PK ""Infobip Message ID / Internal UUID"" string bulkId NULL ""Infobip Bulk ID (if applicable)"" string senderNumber string status ""SUBMITTED, PENDING, SENT, DELIVERED, FAILED, REJECTED"" string errorCode NULL string errorDescription NULL datetime createdAt datetime updatedAt } MMS_RECIPIENT { string logMessageId FK string recipientNumber } - Data Access Layer: Implement functions (e.g., using your chosen ORM or database driver) to create, read, and update records based on this schema.
- Migrations: Use ORM tools (e.g.,
prisma migrate dev,sequelize db:migrate) for managing database schema changes.
Implementation (Conceptual - adding log entry):
This shows where you might add a logging call in the mmsController.js after a successful submission to Infobip. It assumes you have created a dbLogService module with the necessary database interaction logic (e.g., createMmsLog).
// Conceptual code inside mmsController.js, within the `try` block after `await sendMms(...)`
/*
try {
const result = await sendMms(
recipientNumber,
senderNumber,
subject,
textContent,
mediaUrl
);
// Log successful submission attempt to database (fire-and-forget or await)
try {
// Assumes dbLogService.createMmsLog exists and handles DB interaction.
// Use Infobip message ID if available, otherwise generate a local one.
const messageId = result.messages?.[0]?.messageId || `local-${Date.now()}`;
await dbLogService.createMmsLog({
messageId: messageId,
bulkId: result.bulkId,
senderNumber: senderNumber,
recipientNumber: recipientNumber, // Or store in related MMS_RECIPIENT table
status: 'SUBMITTED', // Initial status upon successful API call
createdAt: new Date(),
updatedAt: new Date(),
});
console.log(`MMS submission logged to DB with ID: ${messageId}`);
} catch (dbError) {
console.error(""Failed to log MMS submission to database:"", dbError);
// Decide if failure to log should affect the API response (usually not critical)
}
// Respond with the success details from Infobip
res.status(200).json({
message: 'MMS submitted successfully to Infobip.',
details: result,
});
} catch (error) {
// ... existing error handling ...
}
*/7. Adding Security Features
- Input Validation and Sanitization: Use
express-validator(as shown inmmsRoutes.js) for schema validation (checking required fields, types), sanitization (e.g.,trim()), and format checking (isMobilePhone,isURL). The example usesisMobilePhone('any', { strictMode: false })which is permissive; adjust parameters based on required phone number strictness.isURL()ensures valid, secure HTTP/HTTPS URLs.bash# Ensure express-validator is installed npm install express-validator - API Key Security: Keep keys in
.env, ensure.envis in.gitignore, manage access permissions within the Infobip platform, and consider rotating keys periodically. - Rate Limiting: Protect against brute-force attacks and API abuse using middleware like
express-rate-limit.bashnpm install express-rate-limitjavascript// In src/index.js import rateLimit from 'express-rate-limit'; // ... Express app setup ... 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.', // keyGenerator: (req, res) => req.ip // Default key generator (IP address) }); // Apply the rate limiting middleware to API routes app.use('/api/', apiLimiter); // Mount your API routes *after* the limiter app.use('/api/mms', mmsRoutes); // ... rest of server setup ... - HTTPS: Essential for production environments to encrypt data in transit. Use a reverse proxy (like Nginx or Caddy) or rely on Platform-as-a-Service (PaaS) or load balancer features for SSL/TLS termination.
- Content Security: Validate the
mediaUrlstrictly. Ensure the media host is trusted and secure. Be cautious about Server-Side Request Forgery (SSRF) vulnerabilities if themediaUrlcan be influenced by external users; ensure the URL points to expected locations.
8. Handling Special Cases Relevant to MMS
- Content Size Limits:
- Recommended Maximum: Keep total MMS size (text + media) below 300 KB for maximum carrier compatibility. This is the "smallest common denominator" across device capabilities and carrier configurations.
- Infobip Recommendation: Infobip officially recommends messages under 300 KB, though some operators support up to 1 MB.
- US Carrier-Specific Limits (as of 2024):
- AT&T: Accepts media up to 1 MB
- Verizon: Accepts media up to 1.7 MB (images up to 1.2 MB, videos up to 3.5 MB)
- T-Mobile: Accepts media up to 3 MB for receiving, 1 MB for sending
- Best Practice: Use attachments no larger than 600 KB for non-JPEG/PNG/GIF formats. Validate media file size before attempting to send.
- Supported Content Types: Use common, widely supported formats. For images: JPEG, GIF, PNG. For text: TEXT/PLAIN (UTF-8 encoded). For video: MP4 (H.264 video codec, AAC audio codec). Ensure the
typefield in the payload (IMAGE,VIDEO,TEXT) matches the actual content. - URL Accessibility:
- The
mediaUrlmust be publicly accessible via HTTP or HTTPS without requiring authentication cookies or headers. Infobip's servers need to fetch this content. - Minimum Availability: Keep media URLs available for at least 3 days after sending the MMS. Infobip may retry fetching content during delivery attempts.
- Best Practice: Use unique URLs for new content to prevent caching issues.
- The
- Content IDs: When referencing content, use IDs that avoid angle brackets, contain only basic letters/numbers, and stay under 20 characters.
- Character Encoding: Always use UTF-8 encoding for the
textContentfield to support a wide range of characters. - Subject Line Length: Keep subject lines under 500 characters (operators support up to 1,600 characters, but brevity improves compatibility).
- International Formatting: Use the E.164 format (country code prefixed with
+, no spaces or dashes, e.g.,+14155552671) for both theto(recipient) andfrom(sender) phone numbers. - SMIL Files: Synchronized Multimedia Integration Language (SMIL) is used to control the layout and timing of MMS content presentation. However, support is inconsistent across devices, especially on iOS. It's generally recommended to avoid using explicit SMIL. Instead, rely on the order of elements in the
contentarray in the payload for sequencing (e.g., text first, then image). - Group Messaging: The current code sends an MMS to a single recipient (
tofield accepts a single number). Sending to multiple recipients as a group MMS (3+ participants) requires an MMS-capable 10DLC number and potentially different payload structure. Consult the Infobip API documentation for group MMS specifics.
9. Performance Optimizations (Considerations)
-
External Dependencies: The primary performance factor will likely be the latency of the Infobip API itself and the time it takes for Infobip to fetch the media from your
mediaUrl. -
Asynchronous Nature: Node.js is well-suited for I/O-bound tasks like making API calls, so the application itself should handle concurrent requests efficiently.
-
Media Hosting: Host your media files (
mediaUrl) on a fast, reliable server or a Content Delivery Network (CDN) geographically close to Infobip's infrastructure if possible, to minimize fetch times. -
Payload Size: Keep the JSON payload sent to your API and the response size reasonable. Avoid overly large request/response bodies.
-
Load Testing: Use tools like
k6,artillery, orJMeterto simulate user traffic and identify potential bottlenecks in your application or infrastructure under load.Example k6 Load Test Script: (Install k6 globally:
npm install -g k6or use Docker/other installation methods) Save the following asmms-test.js:javascriptimport http from 'k6/http'; import { sleep, check } from 'k6'; export const options = { vus: 10, // Number of concurrent virtual users duration: '30s', // Duration of the test }; export default function () { // Target your running API endpoint const url = 'http://localhost:3000/api/mms/send'; // IMPORTANT: Replace with a valid recipient number allowed by your Infobip account // (e.g., your registered phone number during the free trial) const recipient = '+1YOUR_TEST_NUMBER'; // Use a small, reliable, publicly accessible image URL for testing const mediaUrl = 'https://www.infobip.com/wp-content/uploads/2021/09/01-infobip-logo-menu.png'; const payload = JSON.stringify({ recipientNumber: recipient, subject: `K6 Load Test MMS VU=${__VU}`, textContent: `Testing MMS sending from k6. VU=${__VU}, Iteration=${__ITER}`, mediaUrl: mediaUrl }); const params = { headers: { 'Content-Type': 'application/json', }, }; // Send the POST request const res = http.post(url, payload, params); // Check if the request was successful (status 200) and response looks okay check(res, { 'status is 200': (r) => r.status === 200, 'response body contains message': (r) => r.body.includes('MMS submitted successfully'), // Add more checks as needed, e.g., response time 'response time < 500ms': (r) => r.timings.duration < 500, }); // Wait for 1 second before the next request (per VU) sleep(1); }Run the test from your terminal (ensure your Node.js app is running):
k6 run mms-test.js(Remember to replace+1YOUR_TEST_NUMBERwith a number you can actually send to via your Infobip account).
10. Monitoring, Observability, and Analytics
- Health Checks: Implement a basic
/healthendpoint (as shown inindex.js) that monitoring systems can poll to check if the application is running. - Performance Metrics: Monitor key application and system metrics:
- Request latency (average, p95, p99)
- Request rate (requests per second/minute)
- Error rates (HTTP 4xx, 5xx)
- Node.js process metrics (CPU usage, memory usage, event loop lag).
- Use libraries like
prom-clientfor Prometheus metrics or integrate with Application Performance Monitoring (APM) tools (e.g., Datadog, Dynatrace, New Relic).
- Error Tracking: Integrate dedicated error tracking services (e.g., Sentry, Bugsnag) to capture, aggregate, and alert on unhandled exceptions and errors in real-time.
- Distributed Tracing: For more complex systems involving multiple microservices, consider implementing distributed tracing using standards like OpenTelemetry to track requests across service boundaries.
- Infobip Delivery Reports (DLRs): Infobip can send asynchronous status updates about message delivery (e.g.,
DELIVERED_TO_HANDSET,FAILED_TO_DELIVER) via webhooks. Configure a webhook URL in your Infobip account settings pointing to an endpoint in your application. Create a new route and controller in your Express app to receive these POST requests, parse the DLR data, and update your logs or database accordingly. This provides crucial visibility into the final delivery status.
11. Troubleshooting and Caveats
- Invalid Credentials: (
401 Unauthorizedresponse from Infobip)- Solution: Double-check that
INFOBIP_API_KEYandINFOBIP_BASE_URLin your.envfile are correct and match your Infobip account details. Ensure you are usingAuthType.ApiKeyduring client initialization. Verify the API key is active and has MMS permissions.
- Solution: Double-check that
- Invalid Phone Number Format: (
400 Bad Requestfrom your API validator or an error from Infobip)- Solution: Ensure recipient and sender numbers are provided in the international E.164 format (e.g.,
+14155552671). Check theisMobilePhonevalidation rules inmmsRoutes.js.
- Solution: Ensure recipient and sender numbers are provided in the international E.164 format (e.g.,
- Sender Number Not MMS Capable / Not Provisioned: (Infobip error message like "Sender not registered", "MMS not enabled for sender", or similar)
- Solution: Confirm that the
INFOBIP_SENDER_NUMBERspecified in.envis correctly provisioned and activated for sending MMS messages within the Infobip portal ("Channels and Numbers"). This often requires specific registration processes (e.g., US 10DLC registration for A2P MMS in the US).
- Solution: Confirm that the
- Free Trial Limitations:
- Caveat: This is the most common issue for new users. Infobip free trial accounts typically restrict sending messages only to the phone number that was verified during the signup process. Attempts to send to other numbers will fail.
- Solution: During the trial period, ensure your
recipientNumbermatches your verified phone number. Check the specific error message returned by Infobip, as it often indicates this limitation. Upgrade to a paid account for broader sending capabilities.
- Media URL Issues: (Infobip error related to fetching content, often resulting in message failure)
- Invalid/Insecure URL: Ensure the
mediaUrlstarts withhttp://orhttps://. Check theisURLvalidation inmmsRoutes.js. - URL Not Publicly Reachable: Verify the URL is accessible from the public internet without requiring logins or specific headers. Check server uptime and firewalls. Infobip's servers must be able to fetch the content.
- Unsupported Content Type: Make sure the media file at the URL is in a supported format (JPEG, PNG, GIF for images; MP4 H.264/AAC for video; plain text).
- Content Too Large: Keep the media file size within recommended limits (ideally < 300-600KB). Large files may fail to download or be rejected by carriers.
- Invalid/Insecure URL: Ensure the
- Insufficient Funds: (Infobip error indicating lack of balance)
- Solution: Ensure your Infobip account has sufficient balance to cover the cost of sending MMS messages.
- Carrier Filtering/Blocking:
- Caveat: Mobile carriers may filter or block MMS messages based on content, sender reputation, or compliance with regulations (like 10DLC in the US).
- Solution: Adhere to content best practices (avoid spammy content), ensure proper sender registration if required (e.g., 10DLC campaign registration), and contact Infobip support if you suspect carrier filtering is causing delivery issues.
- SDK Method or Payload Mismatch:
- Caveat: The specific Infobip SDK method (e.g.,
infobipClient.channels.mms.send) or the required structure of thepayloadobject might change between different versions of the SDK or due to updates in the Infobip API itself. - Solution: Always consult the current official Infobip Node.js SDK documentation and API reference for the definitive method signature and payload structure required for sending MMS messages. Do not rely solely on examples (like this one) without verifying against the official documentation.
- Caveat: The specific Infobip SDK method (e.g.,
- SMIL Incompatibility:
- Solution: As mentioned earlier, avoid using SMIL for controlling presentation due to poor cross-device compatibility, especially with iOS. Rely on the order of items in the
contentarray.
- Solution: As mentioned earlier, avoid using SMIL for controlling presentation due to poor cross-device compatibility, especially with iOS. Rely on the order of items in the
12. Deployment and CI/CD
Deployment Options: Choose a platform suitable for Node.js applications, such as Platform-as-a-Service (PaaS) providers (Heroku, Render, Vercel - check MMS backend support), container orchestration (Docker with Kubernetes), or traditional Virtual Machines (AWS EC2, Google Compute Engine).
Basic PaaS Deployment Steps (Conceptual for Heroku/Render):
startscript: Ensure yourpackage.jsonincludes astartscript:""start"": ""node src/index.js"".- Procfile (Heroku Specific): Create a file named
Procfile(no extension) in your project root with the line:web: npm start. Render often infers the start command. - Node.js Version: Specify the Node.js engine version in
package.jsonto match what you developed with and what the platform supports:(Place thisjson{ ""engines"": { ""node"": "">=18.0.0"" } }enginesblock at the top level of yourpackage.json). - Platform CLI/Dashboard: Install the platform's Command Line Interface (CLI) (e.g.,
heroku login,render login) or use their web dashboard. - Create Application: Create a new application instance on the platform.
- Configure Environment Variables: Set the required environment variables (
INFOBIP_API_KEY,INFOBIP_BASE_URL,INFOBIP_SENDER_NUMBER,PORT- often set by the platform,NODE_ENV=production) through the platform's settings interface or CLI. Crucially, do not commit your.envfile to Git. - Deploy: Deploy your code, typically by pushing your Git repository to the platform's remote (e.g.,
git push heroku main,git push render main).
CI/CD Pipeline (Conceptual using GitHub Actions):
Continuous Integration/Continuous Deployment (CI/CD) automates testing and deployment. Create a workflow file, for example, .github/workflows/deploy.yml:
# .github/workflows/deploy.yml
name: Deploy MMS Sender API
on:
push:
branches: [ main ] # Trigger deployment on pushes to the main branch
jobs:
deploy:
runs-on: ubuntu-latest
environment: production # Optional: Define environment for secrets/rules
steps:
- name: Checkout code
uses: actions/checkout@v4 # Use the latest version
- name: Set up Node.js
uses: actions/setup-node@v4 # Use the latest version
with:
node-version: '18' # Match your package.json engines field
cache: 'npm' # Enable caching of npm dependencies
- name: Install Dependencies
run: npm ci # Use 'ci' for clean, reproducible installs in CI/CD
# Optional Steps: Add steps here for linting, testing, building, etc.
# - name: Run Linter
# run: npm run lint
# - name: Run Tests
# run: npm test
# - name: Build Step (if needed)
# run: npm run build
# Example Deployment Step for Heroku (replace with your platform's action/method)
- name: Deploy to Heroku
uses: akhileshns/heroku-deploy@v3.12.14 # Check for the latest version of this action
with:
heroku_api_key: ${{ secrets.HEROKU_API_KEY }} # Store API key in GitHub Secrets
heroku_app_name: ""your-heroku-app-name"" # Replace with your actual Heroku app name
heroku_email: ""your-heroku-email@example.com"" # Replace with your Heroku account email
# Optional: Specify branch, Procfile path, healthcheck URL, etc.
# Add steps/actions for other platforms (Render, Vercel, AWS, GCP etc.) as needed
# Example Deployment Step for Render (using Render Deploy Hook)
# - name: Trigger Render Deploy Hook
# run: curl -X POST ${{ secrets.RENDER_DEPLOY_HOOK_URL }} # Store hook URL in GitHub Secrets- GitHub Secrets: Configure secrets like
HEROKU_API_KEYorRENDER_DEPLOY_HOOK_URLin your GitHub repository settings (Settings>Secrets and variables>Actions) to securely provide credentials to the workflow. - Platform Actions: Use official or community-supported GitHub Actions specific to your deployment platform (Heroku, Render, AWS Elastic Beanstalk, etc.) for streamlined deployments.