code examples

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

Send MMS Messages with Vite, React/Vue, Node.js and Infobip API

Complete full-stack guide to building an MMS messaging application using Vite (React/Vue), Node.js/Express, and Infobip API. Includes file upload handling, validation, and production deployment.

Developer Guide: Sending Infobip MMS with Vite (React/Vue) and Node.js

This guide walks you through building a system to send Multimedia Messaging Service (MMS) messages via the Infobip API. You'll create a web interface using Vite (with React examples, easily adaptable for Vue) and a Node.js backend API using Express.

Project Overview and Goals

What You'll Build:

A full-stack application that enables users to send MMS messages (text + image) to a specified phone number using Infobip's communication platform.

  • Frontend: A web form built with Vite and React (or Vue) to input the recipient's phone number, message text, and select an image file.
  • Backend: A Node.js/Express API that receives data from the frontend, securely handles the media file, validates input, and interacts with the Infobip MMS API to send the message.

Problem Solved:

This guide provides a straightforward, secure, and reliable method to programmatically send MMS messages from a web application, leveraging Infobip's infrastructure. It delivers a practical blueprint for integrating rich media messaging into notification systems, marketing tools, or customer support platforms.

Technologies Used:

  • Frontend: Vite, React (or Vue), Axios (for HTTP requests)
  • Backend: Node.js, Express.js, Axios (chosen for direct control over the multipart request), Multer (for file uploads), dotenv (for environment variables), cors, libphonenumber-js (for phone number validation)
  • Third-Party Service: Infobip API (specifically the MMS endpoint)

Why these technologies?

  • Vite: Delivers an extremely fast development server and optimized build process for modern frontend frameworks like React and Vue.
  • React/Vue: Popular, component-based libraries for building interactive user interfaces.
  • Node.js/Express: The standard, efficient stack for building backend APIs with JavaScript, well-suited for handling web requests and integrating with external services.
  • Axios: A promise-based HTTP client that simplifies requests to the backend API and the Infobip API. Axios provides fine-grained control over constructing the multipart/form-data requests required by the Infobip binary MMS endpoint. (The official infobip-api-node-sdk is an alternative but may require separate investigation for binary MMS upload handling.)
  • Multer: Essential Node.js middleware for handling multipart/form-data file uploads.
  • libphonenumber-js: Robust library for parsing and validating phone numbers.
  • Infobip: A leading Communications Platform as a Service (CPaaS) provider with robust APIs for various channels, including MMS.

System Architecture:

text
+-----------------+      +----------------------+      +----------------+
|  Vite Frontend  |----->|  Node.js/Express API |----->|  Infobip MMS API |
| (React/Vue)     |      |  (Handles Auth,      |      |                |
| - Input Form    |      |   File Upload,       |      |                |
| - File Selection|      |   Validation, API Call)|    |                |
+-----------------+      +----------------------+      +----------------+
     | User selects file      | API receives form data | API sends request
     | & submits form       | & file, validates,     | with media & data
     | (multipart/form-data)| forwards to Infobip    | to recipient

Prerequisites:

  • Node.js and npm (or yarn): Install Node.js on your development machine. Download from https://nodejs.org/
  • Infobip Account: Register for an Infobip account. A free trial account works for testing. You'll need your API Key and Base URL. Sign up at https://www.infobip.com/signup
  • Basic knowledge: Familiarity with JavaScript, Node.js, React or Vue, command-line usage, and REST APIs
  • Test Phone Number: For free trial accounts, use the verified phone number associated with your Infobip account
  • Git & GitHub Account: Optional, for version control and deployment

Final Outcome:

By the end of this guide, you will have:

  1. A functional Vite frontend allowing users to specify a recipient, text message, and image.
  2. A secure Node.js backend API endpoint (/send-mms) that handles file uploads, validates input, and communicates with Infobip.
  3. Working integration with the Infobip MMS API to send messages.
  4. Basic error handling, input validation, and configuration management using environment variables.

1. Setting up the Project

Create a monorepo-like structure with separate client and server directories within a root project folder. This keeps dependencies and configurations distinct.

1. Create Project Root Directory:

bash
mkdir infobip-mms-app
cd infobip-mms-app
# Optional: Initialize Git
# git init
# echo "node_modules/" >> .gitignore
# echo ".env*" >> .gitignore
# echo "client/dist/" >> .gitignore
# echo "client/node_modules/" >> .gitignore
# echo "server/node_modules/" >> .gitignore

Add .env files in both server and client (if used) to your .gitignore.

2. Set up the Backend (Node.js/Express):

bash
# Create the server directory
mkdir server
cd server

# Initialize Node.js project
npm init -y

# Install dependencies
npm install express axios dotenv multer cors libphonenumber-js form-data

# Install development dependencies (optional but recommended)
npm install --save-dev nodemon jest supertest
  • express: Web framework for Node.js
  • axios: HTTP client to call the Infobip API
  • dotenv: Loads environment variables from a .env file
  • multer: Middleware for handling multipart/form-data (file uploads)
  • cors: Middleware to enable Cross-Origin Resource Sharing
  • libphonenumber-js: For validating recipient phone numbers
  • form-data: Library to construct multipart/form-data payloads programmatically
  • nodemon: Utility that automatically restarts the node application when file changes are detected
  • jest, supertest: For backend testing (Section 13)

3. Configure Backend Scripts (server/package.json):

Open server/package.json and modify the scripts section. Ensure main points to server.js or your entry file.

json
{
  "name": "server",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js",
    "test": "jest"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^...",
    "cors": "^...",
    "dotenv": "^...",
    "express": "^...",
    "form-data": "^...",
    "libphonenumber-js": "^...",
    "multer": "^..."
  },
  "devDependencies": {
    "jest": "^...",
    "nodemon": "^...",
    "supertest": "^..."
  }
}

4. Create Backend Environment File (server/.env):

Create a file named .env in the server directory. Never commit this file to version control.

dotenv
# server/.env

# Infobip Credentials (Get from Infobip Portal: https://portal.infobip.com/settings/accounts/api-keys)
INFOBIP_API_KEY=YOUR_INFOBIP_API_KEY
INFOBIP_BASE_URL=YOUR_INFOBIP_BASE_URL

# Your Infobip registered phone number (sender ID for MMS, check Infobip docs for specifics)
INFOBIP_SENDER_NUMBER=YOUR_INFOBIP_SENDER_PHONE_NUMBER

# Server Configuration
PORT=5001 # Port the backend API will run on
CLIENT_URL=http://localhost:5173 # Development URL of the Vite frontend
  • Replace placeholders with your actual Infobip credentials and sender number. The Base URL typically looks like xxxxxx.api.infobip.com.
  • CLIENT_URL is used for CORS configuration. 5173 is the default Vite dev server port. Adjust if needed. For production, this should be your deployed frontend URL.

5. Set up the Frontend (Vite + React):

Navigate back to the root project directory:

bash
# From the server directory
cd ..

Create the Vite project (choose React when prompted):

bash
# Creates a 'client' directory with the Vite project
npm create vite@latest client -- --template react
# Or for Vue: npm create vite@latest client -- --template vue

cd client

# Install dependencies
npm install

# Install axios for making API calls from the frontend
npm install axios

6. Frontend Environment File (Optional but Recommended):

For deployment flexibility, create a .env file in the client directory for the backend URL:

dotenv
# client/.env

# URL of the backend API
VITE_BACKEND_API_URL=http://localhost:5001

Add client/.env to your .gitignore.

7. Project Structure:

Your project structure should now look similar to this:

text
infobip-mms-app/
├── .gitignore
├── client/
│   ├── node_modules/
│   ├── public/
│   ├── src/
│   │   ├── App.css
│   │   ├── App.jsx
│   │   ├── index.css
│   │   └── main.jsx
│   ├── .env          # Optional: Frontend env vars (add to .gitignore!)
│   ├── index.html
│   ├── package.json
│   └── vite.config.js
└── server/
    ├── node_modules/
    ├── tests/        # For tests (Section 13)
    ├── .env          # IMPORTANT: Contains secrets (add to .gitignore!)
    ├── package.json
    └── server.js     # Will be created in next step

Architectural Decisions & Configuration:

  • Separation of Concerns: Keeping frontend (client) and backend (server) code separate clarifies responsibilities and simplifies dependency management.
  • Environment Variables (.env): Storing secrets (API keys) and configuration (ports, URLs) outside the codebase is crucial for security and deployment flexibility.
  • CORS: The cors middleware on the backend explicitly allows requests only from the frontend URL specified in server/.env.
  • Multer: Used for parsing multipart/form-data requests containing file uploads.
  • Phone Number Validation: Using libphonenumber-js on the backend ensures recipient numbers are in a valid format before attempting to send.

2. Implementing Core Functionality (Backend API)

Build the Express server and the endpoint to handle MMS sending. This implementation uses axios for direct control over constructing the multipart/form-data request required by the Infobip binary MMS endpoint.

1. Create the Server File (server/server.js):

javascript
// server/server.js
require('dotenv').config(); // Load .env variables early
const express = require('express');
const cors = require('cors');
const multer = require('multer');
const axios = require('axios');
const FormData = require('form-data'); // To construct multipart payload
const { parsePhoneNumberFromString } = require('libphonenumber-js');
const fs = require('fs');
const path = require('path');

const app = express();
const port = process.env.PORT || 5001;

// --- Middleware ---

// CORS Configuration
const corsOptions = {
    origin: process.env.CLIENT_URL, // Allow only the frontend origin
    optionsSuccessStatus: 200 // For legacy browser compatibility
};
app.use(cors(corsOptions));

// Basic JSON parsing (though less relevant for our multipart endpoint)
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Multer Configuration (for handling file uploads)
// **Warning:** `memoryStorage` loads the entire file into RAM. This is simple but
// not suitable for production environments handling large files or high traffic,
// as it can lead to excessive memory consumption. For production, strongly
// consider using `multer.diskStorage` or streaming uploads if possible.
const storage = multer.memoryStorage();
const upload = multer({
    storage: storage,
    limits: { fileSize: 10 * 1024 * 1024 }, // Example: 10MB limit (Adjust based on Infobip limits)
    fileFilter: (req, file, cb) => {
        // Basic image type check (enhance as needed)
        const allowedTypes = /jpeg|jpg|png|gif/;
        const mimetype = allowedTypes.test(file.mimetype);
        const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());

        if (mimetype && extname) {
            return cb(null, true);
        }
        cb(new Error(`Error: File upload only supports the following filetypes - ${allowedTypes}`));
    }
}).single('mediaFile'); // Expects a single file field named 'mediaFile' in the form data

// --- Infobip Configuration ---
const INFOBIP_API_KEY = process.env.INFOBIP_API_KEY;
const INFOBIP_BASE_URL = process.env.INFOBIP_BASE_URL;
const INFOBIP_SENDER_NUMBER = process.env.INFOBIP_SENDER_NUMBER; // Your registered Infobip sender

if (!INFOBIP_API_KEY || !INFOBIP_BASE_URL || !INFOBIP_SENDER_NUMBER) {
    console.error(""FATAL ERROR: Infobip API Key, Base URL, or Sender Number is missing in .env file."");
    process.exit(1); // Exit if essential config is missing
}

// --- Routes ---

app.get('/health', (req, res) => {
    res.status(200).json({ status: 'UP' });
});

app.post('/send-mms', (req, res) => {
    upload(req, res, async (err) => {
        // Handle Multer errors (file size, type, etc.)
        if (err instanceof multer.MulterError) {
            console.error('Multer Error:', err);
            return res.status(400).json({ success: false, error: 'File upload error: ' + err.message });
        } else if (err) {
            console.error('File Filter Error:', err);
            return res.status(400).json({ success: false, error: err.message });
        }

        // Check if file and other fields exist
        if (!req.file) {
            return res.status(400).json({ success: false, error: 'No media file uploaded.' });
        }
        const { to, text } = req.body;
        if (!to || !text) {
            return res.status(400).json({ success: false, error: 'Recipient number (to) and text message are required.' });
        }

        // Validate phone number
        const phoneNumber = parsePhoneNumberFromString(to); // Assumes E.164 if no default country specified
        if (!phoneNumber || !phoneNumber.isValid()) {
             console.warn(`Invalid phone number received: ${to}`);
             return res.status(400).json({ success: false, error: 'Invalid recipient phone number format. Please use international E.164 format (e.g., +447123456789).' });
        }
        const formattedNumber = phoneNumber.number; // Use the validated E.164 number

        // --- Prepare Infobip API Request ---
        // MMS Binary endpoint (Verify against current Infobip documentation: https://www.infobip.com/docs/api)
        const mmsEndpoint = `${INFOBIP_BASE_URL}/mms/1/binary`;
        // **Crucially, always verify this endpoint and the required payload structure
        // against the current official Infobip API documentation before deployment.**

        // Construct the multipart/form-data payload using 'form-data' library
        const formData = new FormData();

        // Append the JSON part (Infobip typically expects this as 'head') - CHECK DOCS
        const head = {
            from: INFOBIP_SENDER_NUMBER,
            to: formattedNumber, // Use validated number
            subject: 'MMS from App', // Optional subject
            // Add text here if required by the binary endpoint structure, or it might be separate
            // text: text, // Check Infobip docs if text goes in 'head' or as a separate part
        };
        formData.append('head', JSON.stringify(head), { contentType: 'application/json' });

        // Append the text part if needed as a separate field (CHECK DOCS)
        // formData.append('text', text);

        // Append the binary file part (Infobip typically expects this as 'media') - CHECK DOCS
        formData.append('media', req.file.buffer, {
            filename: req.file.originalname,
            contentType: req.file.mimetype,
        });


        // --- Make API Call to Infobip ---
        try {
            console.log(`Attempting to send MMS via Infobip to: ${formattedNumber}`);
            const response = await axios.post(mmsEndpoint, formData, {
                headers: {
                    'Authorization': `App ${INFOBIP_API_KEY}`,
                    ...formData.getHeaders() // IMPORTANT: Let form-data set the Content-Type and boundary
                },
                timeout: 30000, // 30 seconds timeout
            });

            console.log('Infobip API Response Status:', response.status);
            console.log('Infobip API Response Data:', response.data);
            res.status(response.status).json({ success: true, data: response.data });

        } catch (error) {
            const timestamp = new Date().toISOString();
            let errorMsg = 'Infobip API Error';
            let errorDetails = error.message;
            let infobipResponse = null;
            let infobipStatus = 500; // Default to internal server error

            if (error.response) {
                // Error response received from Infobip
                errorMsg = 'Error response from Infobip API';
                errorDetails = error.response.data;
                infobipStatus = error.response.status;
                infobipResponse = error.response.data;
                 console.error(`[${timestamp}] ${errorMsg} - Status: ${infobipStatus}`, errorDetails);
            } else if (error.request) {
                // Request made but no response received (timeout, network issue)
                errorMsg = 'No response received from Infobip API';
                 console.error(`[${timestamp}] ${errorMsg}`, error.request);
                 infobipStatus = 504; // Gateway Timeout might be appropriate
            } else {
                // Error setting up the request
                 console.error(`[${timestamp}] ${errorMsg} - Setup Error:`, error.message);
            }

             // Log structured error
            const errorLog = {
                timestamp: timestamp,
                message: errorMsg,
                errorDetails: errorDetails,
                infobipStatus: infobipStatus,
                requestData: { to: formattedNumber } // Log non-sensitive parts
            };
             console.error(""Detailed Error Log:"", JSON.stringify(errorLog));

            res.status(infobipStatus).json({ success: false, error: infobipResponse || { message: errorMsg } });
        }
    });
});

// --- Start Server ---
// Only start listening if the script is run directly (not required by a test runner)
if (require.main === module) {
    app.listen(port, () => {
        console.log(`Server listening at http://localhost:${port}`);
        console.log(`Accepting requests from: ${process.env.CLIENT_URL}`);
    });
}

// Export the app instance for testing purposes
module.exports = app;

Code Explanation:

  1. Dependencies & Setup: Includes form-data and libphonenumber-js. Loads .env, creates Express app.
  2. Middleware: cors (configured), express.json/urlencoded, multer (with memory storage warning and file filter).
  3. Infobip Config: Reads env vars, exits if missing.
  4. /health Route: Simple health check.
  5. /send-mms Route (POST):
    • Uses upload middleware.
    • Handles Multer errors.
    • Validates presence of mediaFile, to, text.
    • Phone Validation: Uses libphonenumber-js to parse and validate the to number. Rejects if invalid, otherwise uses the formatted E.164 number.
    • Prepare Infobip Request:
      • Defines mmsEndpoint with a comment to verify it. Emphasizes checking official docs.
      • Uses form-data library to build the multipart/form-data payload.
      • Appends the head (JSON part) and media (file part) according to typical Infobip structure. Crucially reinforces checking Infobip docs for the exact required field names ('head', 'media', 'text' placement).
    • Call Infobip API:
      • Uses axios.post.
      • Sets Authorization header.
      • Spreads formData.getHeaders() into axios headers config to correctly set Content-Type: multipart/form-data; boundary=....
      • Includes enhanced try...catch for logging structured error details (distinguishing between Infobip response errors, network errors, and setup errors).
    • Response: Sends Infobip's response or a structured error back to the frontend.
  6. Start Server & Export: Starts the server only if run directly, and exports app for use in tests.

3. Building the Frontend (Vite + React)

Create the React component for the form.

1. Create the Form Component (client/src/App.jsx):

Replace the contents of client/src/App.jsx with the following:

jsx
// client/src/App.jsx
import React, { useState } from 'react';
import axios from 'axios';
import './App.css'; // We'll create this for basic styling

function App() {
  const [to, setTo] = useState('');
  const [text, setText] = useState('');
  const [mediaFile, setMediaFile] = useState(null);
  const [statusMessage, setStatusMessage] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);

  // Get backend URL from environment variable, with fallback for local dev
  const backendUrl = import.meta.env.VITE_BACKEND_API_URL || 'http://localhost:5001';
  const sendMmsEndpoint = `${backendUrl}/send-mms`;

  const handleFileChange = (event) => {
    const file = event.target.files[0];
    // Optional: Add basic client-side size check
    const maxSize = 10 * 1024 * 1024; // 10MB (match backend or slightly lower)
    if (file && file.size > maxSize) {
        setStatusMessage(`Error: File size exceeds ${maxSize / 1024 / 1024}MB limit.`);
        setIsError(true);
        setMediaFile(null);
        event.target.value = null; // Clear the input
        return;
    }
    setMediaFile(file);
    setStatusMessage(''); // Clear status on new file selection
    setIsError(false);
  };

  const handleSubmit = async (event) => {
    event.preventDefault();
    if (!to || !text || !mediaFile) {
      setStatusMessage('Please fill in all fields and select a valid media file.');
      setIsError(true);
      return;
    }

    // Basic client-side check for E.164 format (improves UX, but backend validation is key)
    // This regex is basic, libphonenumber-js on backend is more robust.
    if (!/^\+?[1-9]\d{1,14}$/.test(to)) {
        setStatusMessage('Please enter the recipient number in E.164 format (e.g., +447123456789).');
        setIsError(true);
        return;
    }


    setIsLoading(true);
    setStatusMessage('Sending MMS...');
    setIsError(false);

    // Use FormData to send multipart data
    const formData = new FormData();
    formData.append('to', to);
    formData.append('text', text);
    formData.append('mediaFile', mediaFile); // Key must match Multer config on backend

    try {
      const response = await axios.post(sendMmsEndpoint, formData, {
        headers: {
          // 'Content-Type': 'multipart/form-data' // Axios usually sets this automatically for FormData
        },
        timeout: 45000, // 45 seconds (allow for backend processing + Infobip call)
      });

      console.log('Backend Response:', response.data);
      // Extract a meaningful status from the Infobip response if possible
      const infobipStatus = response.data?.data?.messages?.[0]?.status?.name || 'SUBMITTED';
      setStatusMessage(`Successfully sent MMS! Infobip status: ${infobipStatus}`);
      setIsError(false);
      // Optionally clear the form
      // setTo('');
      // setText('');
      // setMediaFile(null);
      // event.target.reset(); // Reset file input if needed

    } catch (error) {
      console.error('Error sending MMS:', error);
      let errorMessage = 'Failed to send MMS.';
      if (error.response) {
        // Error from backend API
        const backendError = error.response.data?.error;
        errorMessage += ` Server Error (${error.response.status}): ${backendError?.message || JSON.stringify(backendError || error.response.data)}`;
      } else if (error.request) {
        // Request made but no response received (network issue, backend down?)
        errorMessage += ' No response received from the server. Is it running or unreachable?';
      } else {
        // Error setting up the request
        errorMessage += ` Request Setup Error: ${error.message}`;
      }
      setStatusMessage(errorMessage);
      setIsError(true);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div className="app-container">
      <h1>Send Infobip MMS</h1>
      <form onSubmit={handleSubmit} className="mms-form">
        <div className="form-group">
          <label htmlFor="to">Recipient Phone:</label>
          <input
            type="tel"
            id="to"
            value={to}
            onChange={(e) => setTo(e.target.value)}
            placeholder="e.g., +447123456789 (E.164 format)"
            required
            disabled={isLoading}
          />
        </div>
        <div className="form-group">
          <label htmlFor="text">Message Text:</label>
          <textarea
            id="text"
            value={text}
            onChange={(e) => setText(e.target.value)}
            rows="3"
            required
            disabled={isLoading}
          ></textarea>
        </div>
        <div className="form-group">
          <label htmlFor="mediaFile">Media File (Image):</label>
          <input
            type="file"
            id="mediaFile"
            accept="image/jpeg, image/png, image/gif" // Match backend filter
            onChange={handleFileChange}
            required // File input required attribute might behave differently across browsers
            disabled={isLoading}
          />
           {mediaFile && <p className="file-info">Selected: {mediaFile.name} ({(mediaFile.size / 1024 / 1024).toFixed(2)} MB)</p>}
        </div>
        <button type="submit" disabled={isLoading || !mediaFile}>
          {isLoading ? 'Sending...' : 'Send MMS'}
        </button>
      </form>
      {statusMessage && (
        <div className={`status-message ${isError ? 'error' : 'success'}`}>
          {statusMessage}
        </div>
      )}
      <p className="api-url-note">Sending requests to: <code>{sendMmsEndpoint}</code></p>
    </div>
  );
}

export default App;

2. Add Basic Styling (client/src/App.css):

Create a file client/src/App.css (or update the existing one):

css
/* client/src/App.css */
body {
  font-family: sans-serif;
  margin: 0;
  padding: 20px;
  background-color: #f4f7f6;
  color: #333;
}

.app-container {
  max-width: 500px;
  margin: 40px auto;
  padding: 30px;
  background-color: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

h1 {
  text-align: center;
  color: #007bff; /* Infobip-like blue */
  margin-bottom: 30px;
}

.mms-form .form-group {
  margin-bottom: 20px;
}

.mms-form label {
  display: block;
  margin-bottom: 8px;
  font-weight: bold;
  color: #555;
}

.mms-form input[type="tel"],
.mms-form textarea,
.mms-form input[type="file"] {
  width: 100%;
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-sizing: border-box; /* Include padding in width */
  font-size: 1rem;
}

.mms-form textarea {
  resize: vertical; /* Allow vertical resize */
  min-height: 60px;
}

.mms-form input[type="file"] {
  padding: 5px; /* Slightly less padding for file input */
}

.file-info {
    font-size: 0.9em;
    color: #666;
    margin-top: 5px;
}

.mms-form button {
  display: block;
  width: 100%;
  padding: 12px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  font-size: 1.1rem;
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.mms-form button:hover:not(:disabled) {
  background-color: #0056b3;
}

.mms-form button:disabled {
  background-color: #cccccc;
  cursor: not-allowed;
}

.status-message {
  margin-top: 25px;
  padding: 15px;
  border-radius: 4px;
  text-align: center;
  font-weight: bold;
  word-wrap: break-word; /* Prevent long error messages from overflowing */
}

.status-message.success {
  background-color: #e6ffed;
  color: #28a745;
  border: 1px solid #b8e6c3;
}

.status-message.error {
  background-color: #f8d7da;
  color: #dc3545;
  border: 1px solid #f5c6cb;
}

.api-url-note {
    margin-top: 20px;
    font-size: 0.8em;
    color: #888;
    text-align: center;
}
.api-url-note code {
    background-color: #eee;
    padding: 2px 4px;
    border-radius: 3px;
}

3. Import CSS (client/src/main.jsx):

Ensure the App.css file is imported in your main entry point:

jsx
// client/src/main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css' // Keep default index.css if needed
import './App.css'   // Import your App specific styles

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

Frontend Code Explanation:

  1. State: Manages form inputs (to, text, mediaFile), loading/status messages.
  2. Backend URL: Reads VITE_BACKEND_API_URL from environment variables (via import.meta.env) with a fallback to http://localhost:5001 for easy local development. A note about setting this variable (in client/.env) is added in Section 1.
  3. handleFileChange: Updates mediaFile state, includes an optional client-side size check for better UX.
  4. handleSubmit:
    • Prevents default submission.
    • Performs client-side validation (required fields, basic phone format check - backend validation is still essential).
    • Sets loading state.
    • Creates FormData and appends fields.
    • Uses axios.post to send data to the backend endpoint derived from the environment variable.
    • Handles success and displays status from Infobip via the backend response.
    • Handles errors gracefully, displaying informative messages based on the error type (backend response, network error, etc.).
    • Resets loading state in finally.
  5. JSX: Renders the form with appropriate input types (tel, file with accept), provides feedback during loading, and displays status messages. Includes a note showing the API endpoint being used.

4. Integrating with Infobip (Recap & Credentials)

This section focuses on ensuring the Infobip details are correctly obtained and used.

1. Obtain Credentials:

  • Log in to your Infobip account: https://portal.infobip.com/
  • Navigate to the API Key Management section (e.g., ""Developers"" -> ""API Keys"").
  • Create a new API key if needed. Give it a descriptive name.
  • Copy the API Key: This is your INFOBIP_API_KEY. Treat it securely.
  • Find your Base URL: Usually displayed near API keys or on the developer overview page (e.g., xxxxxx.api.infobip.com). This is your INFOBIP_BASE_URL.
  • Identify Sender Number: For MMS, locate the specific number enabled for MMS sending on your Infobip account (e.g., under ""Numbers""). For free trials, this is often your verified personal number. This is your INFOBIP_SENDER_NUMBER. Check Section 8 for more details on sender ID requirements.

2. Secure Storage (server/.env):

As configured in Step 1, place these values securely in your server/.env file:

dotenv
# server/.env

# Infobip Credentials
INFOBIP_API_KEY=PASTE_YOUR_API_KEY_HERE
INFOBIP_BASE_URL=PASTE_YOUR_BASE_URL_HERE # e.g., abcde.api.infobip.com
INFOBIP_SENDER_NUMBER=PASTE_YOUR_INFOBIP_SENDER_NUMBER_HERE # e.g., +447123456789

# Server Configuration (already defined)
# PORT=5001
# CLIENT_URL=http://localhost:5173

3. Usage in Code (server/server.js):

The backend code (server/server.js) loads these values using require('dotenv').config() and accesses them via process.env.

Environment Variable Explanation:

  • INFOBIP_API_KEY: (String) Your secret API key from the Infobip Portal.
  • INFOBIP_BASE_URL: (String) Your account-specific API domain from the Infobip Portal.
  • INFOBIP_SENDER_NUMBER: (String) The E.164 formatted phone number or sender ID provisioned for MMS sending in your Infobip account.
  • PORT: (Number) Network port for the backend server (default: 5001).
  • CLIENT_URL: (String) Full URL of the allowed frontend origin for CORS (e.g., http://localhost:5173 for dev, your production frontend URL for deployment).

Security: Never hardcode API keys directly in your source code. Always use environment variables loaded from a .env file (which is excluded from version control via .gitignore) or secure configuration management systems in production.

Frequently Asked Questions

how to send mms with infobip and node.js

Set up a Node.js/Express backend with the Infobip API. This involves installing necessary libraries like `axios`, `multer`, and `libphonenumber-js`, configuring your `.env` file with Infobip credentials, and creating an API endpoint to handle MMS sending logic. The backend will manage file uploads, validate inputs, and interact with the Infobip MMS API.

what is the infobip mms api

The Infobip MMS API is part of Infobip's CPaaS (Communications Platform as a Service) offering. It allows developers to send multimedia messages (text and images) programmatically through their platform. This API requires specific request formatting, including `multipart/form-data` for handling binary media files, and authentication using API keys.

why use vite for infobip mms app

Vite is a modern frontend build tool that offers a significantly faster development experience and optimized build process for frameworks like React and Vue. Its speed and efficiency make it a great choice for building the user interface for sending MMS messages through Infobip.

when should I validate phone numbers with libphonenumber-js

Phone number validation using `libphonenumber-js` should always be performed on the backend before sending any messages through the Infobip API. This ensures data integrity and avoids unnecessary API calls with invalid recipient numbers. Client-side validation can enhance UX but shouldn't replace server-side checks.

can I use vue instead of react with vite for this project

Yes, the provided example uses React, but it's easily adaptable to Vue.js. Vite supports both frameworks, and the backend API remains the same regardless of the frontend framework used. The core concepts and setup remain the same, just the frontend components need adjustment.

how to handle file uploads for infobip mms

Use the `multer` middleware in your Node.js backend to handle `multipart/form-data` requests, which are used for file uploads. `multer.memoryStorage` can be used for testing. The frontend should send the image file as part of the form data to the backend endpoint, matching the field name used by Multer.

what is the purpose of axios in this project

Axios is used as the HTTP client for both frontend and backend communication with the Infobip API. It simplifies making HTTP requests and provides control over constructing `multipart/form-data` payloads required by Infobip's MMS endpoint.

why use a monorepo-like structure

The monorepo-like structure, with separate 'client' and 'server' directories, keeps frontend and backend code cleanly separated. This improves code organization, simplifies dependency management, and makes it easier to work on each part of the application independently.

how to configure cors for infobip mms app

Configure CORS (Cross-Origin Resource Sharing) on your backend using the `cors` middleware. Allow requests *only* from the specific URL of your frontend (e.g., `http://localhost:5173` in development) by specifying the `origin` in the `cors` options.

what is the role of environment variables

Environment variables store sensitive data (like API keys) and configuration settings outside of your codebase. This enhances security and makes deployment easier by allowing configuration changes without modifying the code itself. Use a `.env` file locally and a secure system in production.

how to structure the infobip mms api request

The request to the Infobip MMS API must be a `multipart/form-data` request. It typically includes a 'head' section with JSON data (sender, recipient, subject, etc.) and a 'media' section containing the image file. Always check the official Infobip documentation for the latest structure.

where do I get infobip api key and base url

Log in to your Infobip account portal. Go to the API Key Management section (often under 'Developers' or 'Settings'). Create a new API key or use an existing one, copy its value, and locate your account-specific Base URL, which is usually displayed in the same area.

what is the infobip sender number

The Infobip Sender Number is the phone number or alphanumeric sender ID that you have registered and configured within your Infobip account for sending MMS messages. It is a required parameter in the API request and identifies the sender of the message.

how to handle errors when sending infobip mms

Implement robust error handling in both frontend and backend. The backend should catch errors during file upload, validation, and API calls, returning informative messages. The frontend should handle errors from the backend gracefully and display user-friendly error messages.