Developer Guide: Sending MMS with Node.js, Express, and Infobip
This guide provides a step-by-step 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 will cover project setup, core implementation, API creation, error handling, security, deployment, and verification.
Note: Some sections refer to external links or specific user credentials (like GitHub repository links, API keys, Docker Hub usernames). Where the actual link or value was not provided, placeholders remain. Please replace these placeholders with your actual values.
Project Overview and Goals
What We're Building:
We will create a simple REST API endpoint using Node.js and Express. This endpoint will accept requests containing recipient information, a subject, text content, and a URL pointing to media (like an image). It will then use the Infobip API to construct and send an MMS message including this text and media to the specified recipient.
Problem Solved:
This guide enables developers to programmatically send rich media messages (MMS) through a reliable provider like Infobip, integrating this capability into their applications for use cases such as marketing campaigns, notifications with images, or customer support involving visual aids.
Technologies Used:
- Node.js: A JavaScript runtime environment for building server-side applications.
- Express: A minimal and flexible Node.js web application framework used to build the API layer.
- Infobip API: The third-party service used for sending the MMS messages.
- Axios: A promise-based HTTP client for making requests to the Infobip API.
- form-data: A library to create
multipart/form-data
streams, necessary for the Infobip MMS API. - dotenv: A module to load environment variables from a
.env
file.
System Architecture:
+----------+ +-----------------------+ +--------------+ +-----------+ +-----------+
| Client | ----> | Node.js/Express API | ----> | Infobip API | ----> | Carrier | ----> | Handset |
| (e.g. curl| | (Our Application) | | (MMS Gateway)| | Network | | (Recipient)|
| Postman) | +-----------------------+ +--------------+ +-----------+ +-----------+
+----------+ | ^
| loads secrets from | reads media from
v |
+-------+ +-----------+
| .env | | Media URL |
+-------+ +-----------+
Prerequisites:
- A free or paid Infobip account (Sign up here).
- Your Infobip API Key and Base URL (found in your Infobip dashboard).
- An MMS-capable phone number purchased or verified within your Infobip account (often a 10DLC number for US A2P traffic).
- Node.js v14.x or later (for ES Module support used in this guide) and npm (or yarn) installed on your development machine.
- Basic understanding of JavaScript, Node.js, REST APIs, and asynchronous programming.
Expected Outcome:
By the end of this guide, you will have a running Express application with a single API endpoint (POST /send-mms
) that can successfully send an MMS message containing text and an image (fetched from a URL) via Infobip.
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.
mkdir infobip-mms-express cd infobip-mms-express
-
Initialize npm Project: Initialize the project using npm. The
-y
flag accepts the default settings.npm init -y
This creates a
package.json
file. Make sure it includes""type"": ""module""
to enable ES Module syntax (see Section 3). -
Install Dependencies: We need Express for the server, Axios for HTTP requests,
form-data
to construct the multipart request for Infobip's MMS API, anddotenv
to manage environment variables securely.npm install express axios form-data dotenv
-
Create Project Structure: Set up a basic directory structure for better organization.
mkdir src mkdir src/routes mkdir src/controllers mkdir src/services touch src/server.js touch src/routes/mmsRoutes.js touch src/controllers/mmsController.js touch src/services/infobipService.js touch .env touch .gitignore
-
Configure
.gitignore
: Prevent sensitive files and node modules from being committed to version control. Add the following to your.gitignore
file:# .gitignore node_modules/ .env npm-debug.log
-
Set up Environment Variables: Open the
.env
file and add your Infobip credentials and other configuration. Replace the placeholder values below with your actual credentials. Never commit this file to Git.# .env # Infobip Credentials (Get from your Infobip Dashboard - REPLACE PLACEHOLDERS) INFOBIP_BASE_URL=YOUR_ACCOUNT_SPECIFIC_URL.api.infobip.com INFOBIP_API_KEY=YOUR_API_KEY_HERE # Your MMS-capable sender number from Infobip (REPLACE PLACEHOLDER) SENDER_NUMBER=+1_YOUR_INFOBIP_NUMBER # Server Port PORT=3000
INFOBIP_BASE_URL
: Replace with the Base URL from your Infobip account's homepage or API documentation section (e.g.,xxxxx.api.infobip.com
).INFOBIP_API_KEY
: Replace with the API key generated in the API key management section of your Infobip dashboard. Treat it like a password.SENDER_NUMBER
: Replace with the MMS-enabled phone number associated with your Infobip account that will appear as the sender. Ensure it's in E.164 format (e.g.,+12223334444
).PORT
: The port number on which your Express server will listen.
2. Implementing Core Functionality (Infobip Service)
This service module will encapsulate the logic for interacting with the Infobip MMS API. The key difference from sending SMS is that MMS requires a multipart/form-data
request structure.
Open src/services/infobipService.js
and add the following code:
// src/services/infobipService.js
import axios from 'axios';
import FormData from 'form-data';
// Load environment variables at the top level
const INFOBIP_BASE_URL = process.env.INFOBIP_BASE_URL;
const INFOBIP_API_KEY = process.env.INFOBIP_API_KEY;
const SENDER_NUMBER = process.env.SENDER_NUMBER;
/**
* Sends an MMS message using the Infobip API.
* @param {string} recipient - The recipient's phone number in E.164 format.
* @param {string} subject - The subject line for the MMS.
* @param {string} textContent - The text body of the MMS.
* @param {string} mediaUrl - The URL of the media file (e.g., image) to include.
* @returns {Promise<object>} - A promise that resolves with the Infobip API response.
* @throws {Error} - Throws an error if the request fails.
*/
async function sendMms(recipient, subject, textContent, mediaUrl) {
if (!INFOBIP_BASE_URL || !INFOBIP_API_KEY || !SENDER_NUMBER || INFOBIP_BASE_URL === 'YOUR_ACCOUNT_SPECIFIC_URL.api.infobip.com') {
throw new Error('Infobip credentials or sender number are missing or are placeholders in environment variables.');
}
if (!recipient || !textContent || !mediaUrl) {
throw new Error('Recipient, text content, and media URL are required.');
}
const mmsApiUrl = `https://${INFOBIP_BASE_URL}/mms/1/single`; // MMS Single message endpoint
const form = new FormData();
// 1. Head Part (Required by Infobip MMS API structure)
const head = {
from: SENDER_NUMBER,
to: recipient,
subject: subject || 'MMS Message', // Provide a default subject if none given
};
form.append('head', JSON.stringify(head), { contentType: 'application/json' });
// 2. Text Part (Optional, but usually included)
if (textContent) {
form.append('text', textContent, {
contentType: 'text/plain',
filename: 'text.txt', // Filename is arbitrary but helps structure
});
}
// 3. Media Part (Fetch from URL and append)
let mediaResponse;
try {
mediaResponse = await axios.get(mediaUrl, {
responseType: 'stream', // Fetch as a readable stream
});
} catch (error) {
console.error(`Error fetching media from URL: ${mediaUrl}`, error.message);
throw new Error(`Failed to fetch media from URL: ${mediaUrl}. Status: ${error.response?.status}`);
}
const contentType = mediaResponse.headers['content-type'] || 'application/octet-stream'; // Get content type from response
const filename = mediaUrl.substring(mediaUrl.lastIndexOf('/') + 1) || 'media_file'; // Extract filename
// Append the stream directly to the form-data
form.append('file', mediaResponse.data, {
filename: filename,
contentType: contentType,
});
// --- Axios Request Configuration ---
const axiosConfig = {
headers: {
...form.getHeaders(), // Includes 'Content-Type: multipart/form-data; boundary=...'
'Authorization': `App ${INFOBIP_API_KEY}`, // Use 'App' prefix for API Key auth
},
maxContentLength: Infinity, // Allow large file uploads if necessary
maxBodyLength: Infinity,
};
// --- Send the Request ---
console.log(`Sending MMS to ${recipient} via Infobip...`);
try {
const response = await axios.post(mmsApiUrl, form, axiosConfig);
console.log('Infobip API Response Status:', response.status);
console.log('Infobip API Response Data:', JSON.stringify(response.data, null, 2));
return response.data; // Return the successful response body
} catch (error) {
console.error('Error sending MMS via Infobip:');
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.error('Status:', error.response.status);
console.error('Headers:', JSON.stringify(error.response.headers, null, 2));
console.error('Data:', JSON.stringify(error.response.data, null, 2));
// Re-throw a more specific error using response data
throw new Error(`Infobip API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`);
} else if (error.request) {
// The request was made but no response was received
console.error('Request Error:', error.request);
throw new Error('Infobip API Error: No response received from server.');
} else {
// Something happened in setting up the request that triggered an Error
console.error('Setup Error:', error.message);
throw new Error(`Infobip API Error: ${error.message}`);
}
}
}
export { sendMms };
Why this approach?
multipart/form-data
: Infobip's MMS API requires this format, unlike the JSON typically used for SMS. Theform-data
library correctly structures the request.- Streaming Media: Fetching the media URL as a stream (
responseType: 'stream'
) and passing it directly into the form data is memory-efficient, especially for larger files. It avoids loading the entire media file into memory. - Content Parts: The code constructs the distinct
head
,text
, andfile
parts as required by the Infobip MMS specification found in their documentation. - Headers:
form-data
generates the correctContent-Type
header with the boundary. We add theAuthorization
header using theApp
scheme as specified by Infobip. - Error Handling: Specific error handling for fetching media and for the Infobip API call provides better debugging information, including extracting details from Infobip's error response when available.
3. Building the API Layer (Express)
Now, let's create the Express route and controller to handle incoming requests and use our infobipService
.
Controller (src/controllers/mmsController.js
):
// src/controllers/mmsController.js
import { sendMms } from '../services/infobipService.js';
/**
* Handles incoming requests to send an MMS.
* Expects body: { recipient: string, subject?: string, text: string, mediaUrl: string }
*/
async function handleSendMmsRequest(req, res, next) {
const { recipient, subject, text, mediaUrl } = req.body;
// Basic Input Validation
if (!recipient || !text || !mediaUrl) {
return res.status(400).json({
error: 'Bad Request',
message: 'Missing required fields: recipient, text, mediaUrl.',
});
}
// Simple E.164 format check (can be enhanced with a library like libphonenumber-js)
if (!/^\+?[1-9]\d{1,14}$/.test(recipient)) {
return res.status(400).json({
error: 'Bad Request',
message: 'Invalid recipient phone number format. Use E.164 format (e.g., +12223334444).',
});
}
// Basic URL validation
try {
new URL(mediaUrl);
} catch (_) {
return res.status(400).json({
error: 'Bad Request',
message: 'Invalid mediaUrl format.',
});
}
try {
console.log(`Received request to send MMS to ${recipient}`);
const result = await sendMms(recipient, subject, text, mediaUrl);
console.log(`Successfully sent MMS request for ${recipient}. Result:`, result);
// Respond with the result from Infobip
// Note: The result contains bulkId and messageId(s) which are crucial
// for tracking message status later (e.g., via webhooks).
res.status(200).json({
message: 'MMS send request accepted by Infobip.',
infobipResponse: result,
});
} catch (error) {
console.error(`Failed to process MMS request for ${recipient}:`, error.message);
// Pass error to the central error handler
next(error);
}
}
export { handleSendMmsRequest };
Routes (src/routes/mmsRoutes.js
):
// src/routes/mmsRoutes.js
import express from 'express';
import { handleSendMmsRequest } from '../controllers/mmsController.js';
const router = express.Router();
// Define the POST endpoint for sending MMS
router.post('/send-mms', handleSendMmsRequest);
export default router;
Server Setup (src/server.js
):
// src/server.js
import express from 'express';
import dotenv from 'dotenv';
import mmsRoutes from './routes/mmsRoutes.js';
// Load environment variables from .env file
dotenv.config();
const app = express();
const PORT = process.env.PORT || 3000;
// --- Middlewares ---
// Enable parsing JSON request bodies
app.use(express.json());
// --- Routes ---
app.get('/health', (req, res) => {
res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() });
}); // Basic health check endpoint
// Mount the MMS routes
app.use('/api', mmsRoutes); // Prefix routes with /api
// --- Central Error Handler ---
// This middleware catches errors passed via next(error)
// eslint-disable-next-line no-unused-vars
app.use((err, req, res, next) => {
console.error('Unhandled Error:', err.stack || err);
// Default error response
let statusCode = 500;
let errorMessage = 'Internal Server Error';
let errorDetails = {};
// Check if it's an Axios error with response from Infobip
if (err.message.startsWith('Infobip API Error:') && err.message.includes(' - ')) {
const parts = err.message.split(' - ');
if (parts.length >= 2) {
const statusMatch = parts[0].match(/(\d{3})/);
if (statusMatch) {
statusCode = parseInt(statusMatch[1], 10);
}
errorMessage = `Infobip API Error: ${statusCode}`;
try {
// Attempt to parse the JSON details part
errorDetails = JSON.parse(parts.slice(1).join(' - '));
} catch (parseError) {
console.error(""Could not parse Infobip error details:"", parseError);
// Fallback: provide the raw string details if JSON parsing fails
errorDetails = { rawError: parts.slice(1).join(' - ') };
}
} else {
// Fallback if parsing the specific format fails
errorMessage = err.message;
}
} else if (err.message.includes('Failed to fetch media') || err.message.includes('Invalid recipient') || err.message.includes('Missing required fields') || err.message.includes('credentials or sender number are missing')) {
statusCode = 400; // Bad Request for input/media/config issues
errorMessage = err.message;
} else if (err.message.includes('No response received from server')) {
statusCode = 503; // Service Unavailable (maybe Infobip down or network issue)
errorMessage = err.message;
}
// Add more specific error checks if needed
res.status(statusCode).json({
error: errorMessage,
details: errorDetails, // Include parsed Infobip details or raw error string
message: err.message // Keep original message for wider context if needed
});
});
// --- Start Server ---
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
console.log(`API Endpoint available at POST http://localhost:${PORT}/api/send-mms`);
});
Add Start Script and Ensure type: module
in package.json
:
Modify your package.json
to include a start script and ensure ""type"": ""module""
is present to enable ES Module syntax (import
/export
).
{
// ... other package.json contents (name, version, etc.)
""type"": ""module"",
""main"": ""src/server.js"",
""scripts"": {
""start"": ""node src/server.js"",
""test"": ""echo \""Error: no test specified\"" && exit 1""
},
// ... dependencies, devDependencies, etc.
}
Testing the API Endpoint:
-
Start the server:
npm start
-
Send a POST request using
curl
(or Postman/Insomnia): Remember to replace+1_REPLACE_WITH_RECIPIENT_NUMBER
with an actual E.164 formatted recipient phone number.curl -X POST http://localhost:3000/api/send-mms \ -H ""Content-Type: application/json"" \ -d '{ ""recipient"": ""+1_REPLACE_WITH_RECIPIENT_NUMBER"", ""subject"": ""Hello from Express!"", ""text"": ""Here is a picture sent via Infobip MMS API."", ""mediaUrl"": ""https://www.infobip.com/wp-content/uploads/2021/09/01-infobip-logo-scaled.jpg"" }'
Expected Response (Success):
{
""message"": ""MMS send request accepted by Infobip."",
""infobipResponse"": {
""bulkId"": ""some-unique-bulk-id"",
""messages"": [
{
""to"": ""+1_REPLACE_WITH_RECIPIENT_NUMBER"",
""status"": {
""groupId"": 1,
""groupName"": ""PENDING"",
""id"": 7,
""name"": ""PENDING_ENROUTE"",
""description"": ""Message sent to next instance""
},
""messageId"": ""some-unique-message-id""
}
]
}
}
Expected Response (Error - e.g., Invalid API Key):
{
""error"": ""Infobip API Error: 401"",
""details"": {
""requestError"": {
""serviceException"": {
""messageId"": ""UNAUTHORIZED"",
""text"": ""Invalid login details""
}
}
},
""message"": ""Infobip API Error: 401 - {\""requestError\"":{\""serviceException\"":{\""messageId\"":\""UNAUTHORIZED\"",\""text\"":\""Invalid login details\""}}}""
}
4. Integrating with Infobip (Credentials & Setup)
- Obtaining Credentials:
- Log in to your Infobip Portal.
- Base URL: Your unique Base URL is often displayed prominently on the dashboard/home page after login. It looks like
xxxxx.api.infobip.com
. - API Key: Navigate to the ""Developers"" or ""API Keys"" section (exact naming might vary). Generate a new API key if you don't have one. Copy this key securely.
- Sender Number: Go to ""Channels and Numbers"" > ""Numbers"". Ensure you have an MMS-capable number acquired here. Copy the number in E.164 format.
- Secure Storage: We are using a
.env
file loaded bydotenv
. This file is listed in.gitignore
to prevent accidental commits of your secret credentials. In production environments, use your hosting provider's mechanism for managing environment variables securely (e.g., AWS Secrets Manager, Heroku Config Vars, Docker Secrets). .env
Variables (Recap):INFOBIP_BASE_URL
: (String) Replace placeholder. Base domain for Infobip API calls.INFOBIP_API_KEY
: (String) Replace placeholder. Your secret API key.SENDER_NUMBER
: (String) Replace placeholder. Your MMS-capable number from Infobip (E.164 format).PORT
: (Number) Local port for the Express server.
5. Error Handling, Logging, and Retry Mechanisms
- Error Handling Strategy:
- Service layer (
infobipService.js
) throws errors for config issues, input validation, media fetching failures, and Infobip API errors (extracting status and data). - Controller layer (
mmsController.js
) performs input validation and catches errors, passing them vianext(error)
. - Central error handler (
server.js
) catches errors, logs them, attempts to parse Infobip details from the error message (usingerror.response
data if available via the service layer), and sends a structured JSON response.
- Service layer (
- Logging:
- Retry Mechanisms:
- Not implemented by default.
- Consideration: For transient issues (network errors, 503/504 from Infobip), implement retries using libraries like
async-retry
around theaxios.post
call ininfobipService.js
. Use exponential backoff. - Caution: Be mindful of idempotency. Sending messages is often safe to retry on failure, but use
messageId
from Infobip's response (if a partial failure occurred) or implement your own idempotency keys if needed.
6. Database Schema and Data Layer (Optional)
While not required for basic sending, tracking message status often involves a database.
- Potential Schema (e.g., PostgreSQL):
CREATE TABLE mms_log ( id SERIAL PRIMARY KEY, infobip_message_id VARCHAR(255) UNIQUE, -- Store Infobip's ID infobip_bulk_id VARCHAR(255), recipient VARCHAR(20) NOT NULL, sender VARCHAR(20) NOT NULL, subject TEXT, text_content TEXT, media_url VARCHAR(2048), sent_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, infobip_status_group_name VARCHAR(50), -- Track status (e.g., PENDING, DELIVERED) infobip_status_description TEXT, last_status_update_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_mms_log_status ON mms_log(infobip_status_group_name); CREATE INDEX idx_mms_log_recipient ON mms_log(recipient);
- Data Access: Use an ORM (Prisma, Sequelize, TypeORM) or query builder (Knex.js) to insert logs after sending and update status via webhooks (Section 11).
- Migrations: Use tools provided by the ORM/builder for schema management.
7. Security Features
- Input Validation and Sanitization:
- Basic validation in the controller.
- Enhancement: Use libraries like
joi
orexpress-validator
for more robust schema validation and sanitization in routes/controllers.
- API Key Security: Managed via
.env
(local) and secure environment variables (production). Never hardcode keys. - Rate Limiting: Use middleware like
express-rate-limit
to prevent abuse. Apply it before your API routes inserver.js
. - HTTPS: Essential in production. Use a reverse proxy (Nginx, Caddy) or platform service (Heroku, AWS ELB) for TLS termination.
- Helmet: Use
helmet
middleware inserver.js
(app.use(helmet());
) to set security-related HTTP headers.
8. Handling Special Cases
- Character/Size Limits (Infobip Recommendations):
- Subject: < 50 chars recommended.
- Text: Max 1600 chars possible_ but < 500 recommended. UTF-8.
- Media: JPEG_ GIF_ PNG_ MP4 common. Total MMS size ideally < 300KB (up to 1MB possible but less reliable). Ensure
mediaUrl
points to suitable files.
- International Number Formatting: E.164 format (
+1...
) expected. Uselibphonenumber-js
for robust validation/formatting if needed. - Media URL Accessibility: Service handles fetch errors. Ensure URLs are public. Consider adding checks for allowed domains or protocols if necessary.
- Infobip Free Trial Limitations: Sending usually restricted to your verified number.
- 10DLC/Sender Requirements (US A2P): Ensure your
SENDER_NUMBER
is correctly registered and provisioned for A2P MMS traffic if applicable.
9. Implementing Performance Optimizations
- Media Handling: Streaming media via
axios
(responseType: 'stream'
) is key for memory efficiency. - Asynchronous Operations:
async/await
prevents blocking the Node.js event loop. - Connection Pooling: Axios/Node.js handle TCP connection reuse automatically.
- Caching: Caching fetched media content (if
mediaUrl
is reused often) is possible but adds complexity. Use in-memory (node-cache
) or external (Redis) caches. - Load Testing: Use tools like
k6
_artillery
_autocannon
to test throughput and identify bottlenecks under load. - Profiling: Use Node.js profiler or
clinic.js
to analyze CPU/memory usage.
10. Adding Monitoring_ Observability_ and Analytics
- Health Checks:
/health
endpoint for basic checks. Enhance for production (check dependencies). Integrate with uptime monitors. - Performance Metrics: Use
prom-client
for Prometheus metrics (request latency_ rate_ errors) or integrate with APM tools. Monitor Infobip API call latency specifically. Track system metrics (CPU_ memory). - Error Tracking: Integrate with Sentry_ Bugsnag_ Datadog APM for real-time error capture and alerting.
# Example Sentry setup npm install @sentry/node @sentry/tracing
// src/server.js import * as Sentry from ""@sentry/node""; // Corrected import import * as Tracing from ""@sentry/tracing""; // Corrected import import express from 'express'; import dotenv from 'dotenv'; import mmsRoutes from './routes/mmsRoutes.js'; dotenv.config(); Sentry.init({ dsn: process.env.SENTRY_DSN_ // Get DSN from Sentry project settings (set in .env or env vars) integrations: [ new Sentry.Integrations.Http({ tracing: true })_ // Initialize Express integration later_ after 'app' is defined ]_ tracesSampleRate: 1.0_ // Adjust in production environment: process.env.NODE_ENV || 'development'_ }); const app = express(); // Define app first // Add Express integration now that 'app' exists Sentry.addIntegration(new Tracing.Integrations.Express({ app })); // Sentry handlers MUST be registered BEFORE any other error handler // and AFTER all controllers/routers app.use(Sentry.Handlers.requestHandler()); app.use(Sentry.Handlers.tracingHandler()); // --- Middlewares --- (Your existing ones) app.use(express.json()); // --- Routes --- (Your existing ones) app.get('/health'_ (req_ res) => { res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() }); }); app.use('/api', mmsRoutes); // The Sentry error handler must be before other error middleware app.use(Sentry.Handlers.errorHandler()); // Your custom error handler (must be AFTER Sentry's) // eslint-disable-next-line no-unused-vars app.use((err, req, res, next) => { // Your existing error handler logic from Section 3 console.error('Unhandled Error:', err.stack || err); let statusCode = 500; let errorMessage = 'Internal Server Error'; let errorDetails = {}; // ... (rest of your error parsing logic) ... res.status(statusCode).json({ error: errorMessage, details: errorDetails, message: err.message }); }); // --- Start Server --- const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server running on http://localhost:${PORT}`); console.log(`API Endpoint available at POST http://localhost:${PORT}/api/send-mms`); });
- Dashboards: Visualize metrics using Grafana, Datadog, Kibana, etc. Track MMS sent rate, error rate, latency.
- Alerting: Set up alerts in your monitoring system for high error rates, latency spikes, health check failures.
11. Troubleshooting and Caveats
- Common Errors & Solutions:
401 Unauthorized
(Infobip): CheckINFOBIP_API_KEY
andAuthorization: App <key>
format.400 Bad Request
(Infobip): Check recipient format (E.164), request structure, or Infobip's detailed error message.403 Forbidden
(Infobip): Check MMS enablement, free trial limits, A2P/10DLC compliance. Contact Infobip support if needed.Failed to fetch media...
: VerifymediaUrl
accessibility and correctness.ECONNREFUSED
/ENOTFOUND
: CheckINFOBIP_BASE_URL
, DNS, network connectivity.- Large File Errors/Timeouts: Optimize media (<300KB recommended). Consider increasing Axios timeout (
timeout: 60000
) cautiously.
- Platform Limitations:
- Carrier/handset MMS support varies.
- Infobip API rate limits apply.
- SMIL Files: While Infobip supports SMIL for complex layouts, note that iPhones often ignore them. Avoid SMIL for simple text + media messages for broader compatibility.
- Version Compatibility: Ensure Node.js version (>=14) is compatible with dependencies.
- Delivery Status Updates (Webhooks): This guide focuses on sending. To track delivery, configure Infobip Delivery Reports to POST status updates to an endpoint you create in your application. Store these updates (e.g., in the
mms_log
table). ThebulkId
andmessageId
returned in the initial send response are key to correlating these updates.
12. Deployment and CI/CD
- Environments: Development (local), Staging/Production (cloud/server).
- Deployment Steps: Build (if needed), Package, Configure Environment Variables securely (NO
.env
file in production), Install Prod Dependencies (npm ci --only=production
), Run with Process Manager (PM2), Setup Reverse Proxy (Nginx/Caddy for HTTPS, etc.). - Containerization (Docker):
Build/Run:
# Dockerfile FROM node:18-alpine AS base WORKDIR /app # Install production dependencies based on package-lock.json COPY package*.json ./ RUN npm ci --only=production # Copy application code COPY ./src ./src # Expose the application port EXPOSE 3000 # Set Node environment to production ENV NODE_ENV=production # Set PORT (can be overridden at runtime) ENV PORT=3000 # User security best practice USER node # Command to run the application CMD [""node"", ""src/server.js""]
docker build -t infobip-mms-express . # Run passing env vars securely (e.g., via -e, --env-file, or orchestration secrets) docker run -p 3000:3000 \ -e INFOBIP_BASE_URL='your_actual_base_url' \ -e INFOBIP_API_KEY='your_actual_api_key' \ -e SENDER_NUMBER='+1_your_actual_sender' \ -e NODE_ENV='production' \ --name mms-app infobip-mms-express
- CI/CD Pipeline (e.g., GitHub Actions):
Automate testing, building (Docker image), pushing (to registry like Docker Hub), and deploying (e.g., SSH to server, update service/container).
# .github/workflows/deploy.yml (Conceptual Example) name: Deploy MMS API on: push: branches: [ main ] jobs: build-and-deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Node.js uses: actions/setup-node@v3 with: node-version: '18' # Match Dockerfile version - name: Install Dependencies run: npm ci # Add steps for linting, testing here - name: Login to Docker Hub uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push Docker image uses: docker/build-push-action@v4 with: context: . push: true tags: ${{ secrets.DOCKERHUB_USERNAME }}/infobip-mms-express:latest # Add deployment steps here (e.g., SSH, kubectl apply, etc.) # Example: SSH and restart docker container # - name: Deploy to Server # uses: appleboy/ssh-action@master # with: # host: ${{ secrets.SERVER_HOST }} # username: ${{ secrets.SERVER_USER }} # key: ${{ secrets.SSH_PRIVATE_KEY }} # script: | # docker pull ${{ secrets.DOCKERHUB_USERNAME }}/infobip-mms-express:latest # docker stop mms-app || true # docker rm mms-app || true # docker run -d --restart always -p 3000:3000 \ # -e INFOBIP_BASE_URL='${{ secrets.INFOBIP_BASE_URL }}' \ # -e INFOBIP_API_KEY='${{ secrets.INFOBIP_API_KEY }}' \ # -e SENDER_NUMBER='${{ secrets.SENDER_NUMBER }}' \ # -e NODE_ENV='production' \ # --name mms-app ${{ secrets.DOCKERHUB_USERNAME }}/infobip-mms-express:latest