How to Send MMS with MessageBird API in Node.js & Fastify (2025 Guide)
Learn to send MMS messages using MessageBird API with Node.js and Fastify. Complete tutorial with code examples, webhook handling, error management, and deployment for US/Canada multimedia messaging.
Code examples24 min read
Frequently asked questions
Use a Node.js web framework like Fastify and the MessageBird MMS API. Create an endpoint that handles MMS requests, including recipient numbers, message body, media URLs, and other parameters. Then, use a library like Axios to send the request to MessageBird's API.
Fastify is a high-performance web framework for Node.js known for its speed and developer-friendly experience. Its efficiency makes it suitable for handling API requests, like those required for sending MMS messages with MessageBird.
The article specifies that MessageBird's focus for MMS is the US and Canada. Ensure your recipients are in these regions. Check MessageBird's official documentation for the most up-to-date coverage information.
Use MMS when you need to send rich media content like images or short videos along with text. MMS is ideal for notifications, marketing campaigns, and user engagement that benefits from visual elements, primarily in the US and Canada.
The article primarily describes MMS functionality with MessageBird for the US and Canada. It does not cover international MMS. Consult MessageBird's documentation for their current international MMS capabilities if you need to send messages outside the US and Canada.
MessageBird requires public URLs for media files. Host your media on a publicly accessible service like AWS S3 or Cloudinary. Include an array of these URLs in the 'mediaUrls' field of your API request to MessageBird. Each file must be under 1MB, and you can include up to 10 URLs.
The 'originator' is your MMS-enabled virtual mobile number (VMN) purchased from MessageBird, formatted in E.164 (e.g., +12015550123). This number must be specifically enabled for MMS in your MessageBird settings and is the number messages appear to come from.
Store your MessageBird API key and other sensitive credentials as environment variables. Load them using the dotenv package in development, but use secure configuration management in production deployments. Never commit these keys directly to your code repository.
Axios is a promise-based HTTP client that simplifies making API requests. It's used in this example application to send POST requests containing the MMS payload (recipient, message, media URLs, etc.) to the MessageBird MMS API endpoint.
Configure a webhook URL in your MessageBird dashboard's number settings. MessageBird will send GET requests to this URL with status updates. In your application, create a route that handles these requests, logs the updates, and importantly, sends an immediate 200 OK response to acknowledge receipt.
Use a combination of unit tests (with mocking libraries like Nock) to test your application logic and integration tests (with tools like curl or Postman) to send real MMS messages to test numbers. Ensure your .env file is configured correctly and use test credentials if possible.
The x-api-key header provides an additional layer of security for your Fastify application's /send-mms endpoint. This prevents unauthorized access. The key is set in your .env file.
Choose a hosting provider like Heroku, AWS, Google Cloud, etc. Set environment variables securely on the platform, ensure NODE_ENV is set to 'production', and configure logging and a process manager. Containerization with Docker and Kubernetes or managed container services is also recommended.
Each individual media file included in your MMS message must be 1MB (1024KB) or less, according to the MessageBird API specifications.
You can include a maximum of 10 'mediaUrls' in a single MMS message request to the MessageBird API.
One API. Every channel.
Contact Sales
Build a production-ready Node.js MMS messaging application using the high-performance Fastify framework and MessageBird API. This comprehensive tutorial shows you how to send Multimedia Messaging Service (MMS) messages with images, videos, and rich media content to recipients in the US and Canada. MessageBird is now branded as Bird, but the API endpoints and functionality remain unchanged.
This guide walks you through building a secure API endpoint that accepts recipient information and media URLs, then delivers MMS messages via MessageBird's REST API. You'll implement configuration management, authentication, error handling, webhook status tracking, testing, and deployment strategies.
What You'll Learn: MMS Integration Overview
Goal: Create a reliable and scalable Node.js service to send MMS messages programmatically using Fastify and MessageBird.
Problem Solved: Automate sending rich media messages (images, videos, PDFs) to users in the US and Canada, enabling richer communication for notifications, marketing campaigns, or user engagement compared to SMS-only messaging.
Technologies:
.envfile intoprocess.env.System Architecture:
Prerequisites:
originatornumber.Expected Outcome: A Fastify application with a secure endpoint (
/send-mms) that accepts MMS requests and relays them to the MessageBird API for delivery. The application also has an endpoint (/mms-status) to receive delivery status updates from MessageBird.1. Node.js Project Setup for MessageBird MMS
Initialize your Node.js project and install the necessary dependencies for sending MMS with Fastify.
Create Project Directory: Open your terminal and create a new directory for the project, then navigate into it.
mkdir fastify-messagebird-mms cd fastify-messagebird-mmsInitialize Node.js Project: Create a
package.jsonfile.(Use
yarn init -yif you prefer Yarn)Install Dependencies: Install Fastify for the web server,
axiosto call the MessageBird API, anddotenvfor managing environment variables.(Use
yarn add fastify axios dotenvfor Yarn)Install Development Dependencies: Install
pino-prettyto format logs nicely during development.(Use
yarn add --dev pino-prettyfor Yarn)Create Project Structure: Create the basic files and directories.
touch server.js .env .env.example .gitignoreserver.js: Main application code..env: Stores sensitive credentials (API keys, etc.). Do not commit this file..env.example: A template showing required environment variables. Commit this file..gitignore: Specifies files/directories Git should ignore.Configure
.gitignore: Addnode_modulesand.envto prevent committing them.Set up
package.jsonScripts: Add scripts for easily running the application.{ "scripts": { "start": "node server.js", "dev": "node server.js | pino-pretty" } }npm start: Runs the server in production mode (standard logs).npm run dev: Runs the server with human-readable logs viapino-pretty.2. MessageBird API Configuration and Authentication
Manage credentials and settings securely using environment variables loaded via
dotenv.Define Environment Variables (
.env.example): List the required variables in.env.example.Populate
.envFile: Create a.envfile (or copy.env.exampleto.env) and fill in the actual values:MESSAGEBIRD_API_KEY: Your Live Access Key from the MessageBird Dashboard (Developers > API access).MESSAGEBIRD_ORIGINATOR: Your MMS-enabled US or Canadian number purchased from MessageBird, in E.164 format (e.g.,+12015550123). This number must be explicitly enabled for MMS within your MessageBird settings.PORT: The port your Fastify server listens on (default: 3000).HOST: The host address (default:0.0.0.0to listen on all available network interfaces).FASTIFY_API_KEY: A secret key you define. Clients calling your/send-mmsendpoint need to provide this key for authentication. Generate a strong, random string.LOG_LEVEL: Controls log verbosity (e.g.,info,debug,warn,error).Load Environment Variables in
server.js: At the very top of yourserver.js, load the variables.// server.js require('dotenv').config();3. Implementing the MMS Sending Route with Fastify
Now, build the core logic in
server.jsto send MMS messages using the MessageBird API.Basic Fastify Server Setup: Initialize Fastify and configure basic logging.
// server.js require('dotenv').config(); const Fastify = require('fastify'); const axios = require('axios'); const fastify = Fastify({ logger: { level: process.env.LOG_LEVEL || 'info', // Use pino-pretty only in development for readability ...(process.env.NODE_ENV !== 'production' && { transport: { target: 'pino-pretty', options: { translateTime: 'HH:MM:ss Z', ignore: 'pid,hostname', }, }, }), }, }); // Simple health check route fastify.get('/health', async (request, reply) => { return { status: 'ok' }; }); // --- MMS Sending Route will go here --- // --- Status Webhook Route will go here --- // Start the server const start = async () => { try { const port = parseInt(process.env.PORT || '3000', 10); const host = process.env.HOST || '0.0.0.0'; await fastify.listen({ port, host }); fastify.log.info(`Server listening on ${fastify.server.address().port}`); } catch (err) { fastify.log.error(err); process.exit(1); } }; start();Add Authentication Hook: We'll protect our
/send-mmsroute using a simple API key check via a Fastify hook.// server.js (Add this before defining routes that need protection) fastify.decorate('authenticate', async function (request, reply) { const apiKey = request.headers['x-api-key']; if (!apiKey || apiKey !== process.env.FASTIFY_API_KEY) { fastify.log.warn('Authentication failed: Invalid or missing API key'); reply.code(401).send({ error: 'Unauthorized' }); return Promise.reject(new Error('Unauthorized')); // Stop processing } });Define the
/send-mmsRoute: This route will handle POST requests to send MMS messages.// server.js (Add this after the authenticate hook) const sendMmsSchema = { body: { type: 'object', required: [], // Dynamically determined by oneOf/anyOf properties: { recipient: { type: 'string', description: 'Single recipient phone number in E.164 format.' }, recipients: { type: 'array', items: { type: 'string' }, description: 'Array of recipient phone numbers in E.164 format. Max 50 per request. Use either `recipient` or `recipients`, not both in the same call for clarity.' }, subject: { type: 'string', maxLength: 256, description: 'MMS subject line.' }, body: { type: 'string', maxLength: 2000, description: 'Text body of the MMS.' }, mediaUrls: { type: 'array', items: { type: 'string', format: 'url' }, maxItems: 10, description: 'Array of public URLs for media attachments (max 10).' }, reference: { type: 'string', description: 'Optional client reference string.' }, scheduledDatetime: { type: 'string', format: 'date-time', description: 'Optional schedule time (RFC3339 format: YYYY-MM-DDTHH:mm:ssZ).' } }, // Ensure at least body or mediaUrls is provided anyOf: [ { required: ['body'] }, { required: ['mediaUrls'] } ], // Ensure only one recipient definition method is used and at least one is present oneOf: [ { required: ['recipient'], properties: { recipient: { type: 'string'} }, not: { required: ['recipients']} }, { required: ['recipients'], properties: { recipients: { type: 'array'} }, not: { required: ['recipient']} } ] }, response: { 200: { description: 'MMS message accepted by MessageBird for delivery.', type: 'object', properties: { id: { type: 'string' }, href: { type: 'string' }, direction: { type: 'string' }, originator: { type: 'string' }, subject: { type: ['string', 'null'] }, // Subject can be null if not sent body: { type: ['string', 'null'] }, // Body can be null if not sent mediaUrls: { type: 'array', items: { type: 'string' } }, reference: { type: ['string', 'null'] }, scheduledDatetime: { type: ['string', 'null'], format: 'date-time' }, createdDatetime: { type: 'string', format: 'date-time' }, recipients: { type: 'object' } // Detailed recipient status omitted for brevity } }, 400: { description: 'Bad Request - Invalid input data or validation failure.', type: 'object', properties: { error: { type: 'string', example: 'Bad Request' }, message: { type: 'string' }, details: { type: 'object' } } }, 401: { description: 'Unauthorized - Missing or invalid API key for this service.', type: 'object', properties: { error: { type: 'string', example: 'Unauthorized' } } }, 500: { description: 'Internal Server Error - Failure during processing or communicating with MessageBird.', type: 'object', properties: { error: { type: 'string', example: 'Internal Server Error'}, message: { type: 'string'}, details: { type: 'object'} } }, 503: { description: 'Service Unavailable - Could not reach MessageBird API.', type: 'object', properties: { error: { type: 'string', example: 'Service Unavailable'}, message: { type: 'string'} } } } }; fastify.post('/send-mms', { schema: sendMmsSchema, preHandler: [fastify.authenticate] }, async (request, reply) => { const { recipient, recipients, subject, body, mediaUrls, reference, scheduledDatetime } = request.body; // Determine the recipient list const recipientList = recipients || (recipient ? [recipient] : []); // Handle both cases if (recipientList.length === 0) { reply.code(400).send({ error: 'Bad Request', message: 'At least one recipient is required.' }); return; } if (recipientList.length > 50) { reply.code(400).send({ error: 'Bad Request', message: 'Maximum of 50 recipients allowed per request.' }); return; } const messageBirdPayload = { originator: process.env.MESSAGEBIRD_ORIGINATOR, recipients: recipientList, subject: subject, body: body, mediaUrls: mediaUrls, reference: reference, scheduledDatetime: scheduledDatetime }; // Remove undefined/null fields cleanly before sending Object.keys(messageBirdPayload).forEach(key => (messageBirdPayload[key] === undefined || messageBirdPayload[key] === null) && delete messageBirdPayload[key]); try { fastify.log.info({ msg: 'Sending MMS request to MessageBird', payload: messageBirdPayload }); const response = await axios.post('https://rest.messagebird.com/mms', messageBirdPayload, { headers: { 'Authorization': `AccessKey ${process.env.MESSAGEBIRD_API_KEY}`, 'Content-Type': 'application/json' // Explicitly set content type } }); fastify.log.info({ msg: 'MessageBird API response received', messageId: response.data.id, status: response.status }); reply.code(200).send(response.data); // Forward MessageBird's response } catch (error) { let statusCode = 500; let responseBody = { error: 'Internal Server Error', message: 'Failed to send MMS via MessageBird.', details: {} }; if (error.response) { // Request made, server responded with non-2xx status fastify.log.error({ msg: 'MessageBird API Error', status: error.response.status, data: error.response.data }); statusCode = error.response.status >= 500 ? 500 : 400; // Treat 4xx as 400, 5xx as 500 responseBody.message = `MessageBird API error: ${error.response.data?.errors?.[0]?.description || 'Unknown error'}`; responseBody.details = error.response.data; responseBody.error = statusCode === 400 ? 'Bad Request' : 'Service Error'; } else if (error.request) { // Request made, no response received fastify.log.error({ msg: 'MessageBird API No Response', error: error.message }); responseBody.message = 'No response received from MessageBird API.'; responseBody.error = 'Service Unavailable'; statusCode = 503; } else { // Error setting up the request fastify.log.error({ msg: 'MMS Sending Error', error: error.message }); responseBody.message = error.message; } reply.code(statusCode).send(responseBody); } });Explanation:
recipient/recipients,body/mediaUrls), types, and constraints (lengths, max items) before our handler logic runs. It also specifies the expected success (200) and error (400, 401, 500, 503) response formats.anyOfensures eitherbodyormediaUrlsis present.oneOfensures onlyrecipientorrecipientsis used and that at least one is provided.preHandler: [fastify.authenticate]: This applies our authentication hook to this specific route, handling potential 401 errors./mmsendpoint, using theoriginatorfrom environment variables and data from the incoming request. We clean up undefined or null optional fields. We add checks for recipient limits and presence (returning 400).axios.post: We make the POST request to MessageBird's API endpoint.Authorization: AccessKey ...header is crucial for authenticating with MessageBird.Content-Type: application/json.axiosthrows an error (network issue, non-2xx response from MessageBird), we catch it, log detailed information, determine an appropriate HTTP status code (400 for MessageBird client errors, 503 for timeouts, 500 for server errors), and send a structured error response back to our client.4. How to Handle Media Attachments for MMS
MessageBird does not accept direct file uploads via the
/mmsendpoint. You must provide publicly accessible URLs.mediaUrlscan be included in a single MMS request.Content-Typevalues. Common types includeimage/jpeg,image/png,image/gif,image/avif,video/mp4,audio/mpeg,audio/m4a, andapplication/pdf. Messages that fail due to unsupported attachments will record error codeUNSUPPORTED_MEDIA_TYPE: 14004.Example
mediaUrlsArray in Request Body:{ "recipient": "+14155550100", "subject": "Check this out!", "mediaUrls": [ "https://your-cdn.com/images/logo.png", "https://your-public-bucket.s3.amazonaws.com/videos/promo.mp4" ] }5. Error Handling, Logging, and Retry Strategies
/send-mmsroute includes robust error handling for API calls to MessageBird. It catches different error types (API errors, network errors) and returns informative JSON responses with appropriate HTTP status codes (400, 401, 500, 503). Fastify's default error handler catches other unexpected errors.pino) is configured inserver.js.LOG_LEVELenvironment variable controls verbosity. Set todebugfor more detailed logs during troubleshooting.npm run dev), logs are human-readable thanks topino-pretty. In production (npm start), JSON logs are standard, which is better for log aggregation tools (like Datadog, Splunk, ELK stack).axios.postcall in a simple loop or use a library likeasync-retry. Be cautious with retries for non-idempotent POST requests – ensure MessageBird handles duplicate requests gracefully if a retry occurs after the initial request succeeded but the response was lost. Using a uniquereferencefield might help MessageBird deduplicate, but verify this behavior.6. Security Best Practices for MessageBird MMS Integration
/send-mmsroute definition. This prevents malformed requests and basic injection attempts./send-mmsendpoint is protected by thex-api-keyheader check (fastify.authenticatehook). EnsureFASTIFY_API_KEYis strong and kept secret.MESSAGEBIRD_API_KEYis sent securely via HTTPS in theAuthorizationheader to MessageBird..envlocally, secure configuration management in deployment). Rotate keys periodically. API access keys should be treated like passwords and never shared in publicly accessible areas like GitHub.// server.js (Add near the top, after Fastify initialization) fastify.register(require('@fastify/rate-limit'), { max: 100, // Max requests per windowMs timeWindow: '1 minute' });maxandtimeWindowbased on expected usage. Note: The@fastify/rate-limitplugin is compatible with both Fastify 4 and Fastify 5 (latest as of 2025).npm audit fixoryarn audit) to patch known vulnerabilities.7. Receiving MMS Delivery Status Updates via Webhooks
MessageBird can notify your application about the delivery status of sent MMS messages via webhooks.
Configure Webhook URL in MessageBird:
https://your-app-domain.com/mms-status).Create the Status Webhook Route in Fastify: MessageBird sends status updates as GET requests to your configured URL.
// server.js (Add this alongside other routes) fastify.get('/mms-status', async (request, reply) => { const { id, reference, recipient, status, statusDatetime } = request.query; // SECURITY: Verify webhook signature if configured // MessageBird uses MessageBird-Signature-JWT header with HMAC-SHA256 signature // Example verification (implement based on your signingKey): const signature = request.headers['messagebird-signature-jwt']; const signingKey = process.env.MESSAGEBIRD_SIGNING_KEY; // Optional: Set this in .env if you configure webhook signing if (signingKey && signature) { // TODO: Implement signature verification // Parse JWT, verify timestamp, compute HMAC-SHA256 of request URL + body hash // Compare computed signature with received signature // Reject request if verification fails fastify.log.debug('Webhook signature verification enabled (implement verification logic)'); } // Log the received status update // In a real application, you would likely: // 1. Validate the request source (e.g., check IP, use signed requests if MessageBird supports it) // 2. Look up the message ID or reference in your database // 3. Update the message status in your database // 4. Trigger any necessary downstream actions based on the status (e.g., notify admins on failure) fastify.log.info({ msg: 'Received MMS status update', messageId: id, reference: reference, recipient: recipient, status: status, statusTimestamp: statusDatetime }); // IMPORTANT: Respond with 200 OK quickly! // MessageBird expects a 200 OK to acknowledge receipt. // Failure to respond promptly may cause MessageBird to retry the webhook. reply.code(200).send('OK'); });Explanation:
/mms-statusfor GET requests.request.query).MessageBird-Signature-JWTheader. MessageBird signs HTTP requests using HMAC-SHA256 with your signing key to allow verification of authentication and integrity. If you create your webhook subscription using asigningKey, validate the authenticity by verifying the request signature.200 OKresponse. This is vital for acknowledging receipt to MessageBird.Making Webhooks Accessible:
ngrok(ngrok http 3000) to expose your local server (running on port 3000) to the internet with a public URL (e.g.,https://<unique-id>.ngrok.io). Use this ngrok URL in the MessageBird dashboard for testing.8. Testing Your MessageBird MMS Integration
Thorough testing ensures your MMS integration works correctly with MessageBird and Fastify.
Unit Tests (Example using
tap- Fastify's default):npm install --save-dev tap nock # nock for mocking HTTPAdd test script to
package.json:{ "scripts": { "start": "node server.js", "dev": "node server.js | pino-pretty", "test": "tap test/**/*.test.js" } }Create a test file
test/routes/mms.test.js:// test/routes/mms.test.js const { test } = require('tap'); const Fastify = require('fastify'); const nock = require('nock'); // For mocking HTTP requests const dotenv = require('dotenv'); const axios = require('axios'); // Import axios to be used within the route handler // Load test environment variables if needed (e.g., from a .env.test file) // Ensure required env vars are set for tests process.env.FASTIFY_API_KEY = 'test-api-key'; process.env.MESSAGEBIRD_API_KEY = 'mock_mb_key'; process.env.MESSAGEBIRD_ORIGINATOR = '+10000000000'; process.env.LOG_LEVEL = 'silent'; // Keep test output clean // Mock the authenticate decorator for testing routes in isolation function build(t) { const app = Fastify({ logger: { level: process.env.LOG_LEVEL } }); app.decorate('authenticate', async function (request, reply) { // In tests, we can bypass actual auth check or mock it if (request.headers['x-api-key'] !== process.env.FASTIFY_API_KEY) { reply.code(401).send({ error: 'Unauthorized' }); return Promise.reject(new Error('Unauthorized - Mock')); } }); // Register your route (assuming server.js exports the app or setup function) // Simplified example: Directly defining the route logic here for clarity // Ideally, import and register the actual route handler from server.js const sendMmsSchema = { /* ... include schema if testing validation ... */ }; // Simplified for brevity app.post('/send-mms', { // schema: sendMmsSchema, // Add schema if needed preHandler: [app.authenticate] }, async (request, reply) => { // Replicate route logic for testing purposes const { recipient, recipients, subject, body, mediaUrls } = request.body; const recipientList = recipients || (recipient ? [recipient] : []); if (recipientList.length === 0) { return reply.code(400).send({ error: 'Bad Request', message: 'At least one recipient is required.' }); } if (!body && !mediaUrls) { return reply.code(400).send({ error: 'Bad Request', message: 'Either body or mediaUrls is required.'}); } const messageBirdPayload = { originator: process.env.MESSAGEBIRD_ORIGINATOR, recipients: recipientList, ...(subject && { subject: subject }), ...(body && { body: body }), ...(mediaUrls && { mediaUrls: mediaUrls }), }; try { // Actual axios call will be intercepted by nock in tests const response = await axios.post('https://rest.messagebird.com/mms', messageBirdPayload, { headers: { 'Authorization': `AccessKey ${process.env.MESSAGEBIRD_API_KEY}`, 'Content-Type': 'application/json' } }); reply.code(response.status).send(response.data); } catch (error) { if (error.response) { // Simulate the error handling logic from the actual route const statusCode = error.response.status >= 500 ? 500 : 400; const errorType = statusCode === 400 ? 'Bad Request' : 'Service Error'; reply.code(statusCode).send({ error: errorType, message: `MessageBird API error: ${error.response.data?.errors?.[0]?.description || 'Unknown error'}`, details: error.response.data }); } else if (error.request) { reply.code(503).send({ error: 'Service Unavailable', message: 'No response received from MessageBird API.' }); } else { reply.code(500).send({ error: 'Internal Server Error', message: error.message }); } } }); t.teardown(() => { app.close(); nock.cleanAll(); // Clean up nock interceptors after tests }); return app; } test('/send-mms route', async (t) => { t.test('should return 401 without API key', async (t) => { const app = build(t); const response = await app.inject({ method: 'POST', url: '/send-mms', payload: { recipient: '+11112223344', body: 'Test' } }); t.equal(response.statusCode, 401); t.match(response.json(), { error: 'Unauthorized' }); }); t.test('should return 401 with incorrect API key', async (t) => { const app = build(t); const response = await app.inject({ method: 'POST', url: '/send-mms', headers: { 'x-api-key': 'wrong-key' }, payload: { recipient: '+11112223344', body: 'Test' } }); t.equal(response.statusCode, 401); t.match(response.json(), { error: 'Unauthorized' }); }); t.test('should return 400 if recipient and recipients are missing', async (t) => { const app = build(t); const response = await app.inject({ method: 'POST', url: '/send-mms', headers: { 'x-api-key': 'test-api-key' }, payload: { body: 'Test' } // Missing recipient }); t.equal(response.statusCode, 400); t.match(response.json(), { message: 'At least one recipient is required.' }); }); t.test('should return 400 if body and mediaUrls are missing', async (t) => { const app = build(t); const response = await app.inject({ method: 'POST', url: '/send-mms', headers: { 'x-api-key': 'test-api-key' }, payload: { recipient: '+11112223344' } // Missing body/mediaUrls }); t.equal(response.statusCode, 400); t.match(response.json(), { message: 'Either body or mediaUrls is required.' }); }); t.test('should return 200 on successful mock MessageBird call', async (t) => { const app = build(t); const testPayload = { recipient: '+11112223344', body: 'Unit Test Message', mediaUrls: ['https://example.com/image.jpg'] }; const expectedMbPayload = { originator: process.env.MESSAGEBIRD_ORIGINATOR, recipients: [testPayload.recipient], body: testPayload.body, mediaUrls: testPayload.mediaUrls }; const mockMbResponse = { id: 'mb-fake-id', status: 'sent', recipients: { totalSentCount: 1 } }; // Use nock to intercept the outgoing HTTP request to MessageBird nock('https://rest.messagebird.com') .post('/mms', expectedMbPayload) // Match the payload axios would send .reply(200, mockMbResponse); const response = await app.inject({ method: 'POST', url: '/send-mms', headers: { 'x-api-key': 'test-api-key' }, // Use the key expected by mock authenticate payload: testPayload }); t.equal(response.statusCode, 200, 'Should return status code 200'); t.match(response.json(), mockMbResponse, 'Response body should match mock MessageBird response'); t.ok(nock.isDone(), 'MessageBird API mock endpoint should have been called'); // Ensure the mocked endpoint was called }); t.test('should return 400 on MessageBird API client error (e.g., invalid recipient)', async (t) => { const app = build(t); const testPayload = { recipient: '+12223334455', body: 'Bad request test' }; const mockMbErrorResponse = { errors: [{ code: 21, description: 'Recipient not valid', parameter: 'recipients' }] }; const expectedMbPayload = { originator: process.env.MESSAGEBIRD_ORIGINATOR, recipients: [testPayload.recipient], body: testPayload.body }; nock('https://rest.messagebird.com') .post('/mms', expectedMbPayload) .reply(422, mockMbErrorResponse); // MessageBird often uses 422 for validation const response = await app.inject({ method: 'POST', url: '/send-mms', headers: { 'x-api-key': 'test-api-key' }, payload: testPayload }); t.equal(response.statusCode, 400, 'Should return status code 400'); // Our app maps 4xx to 400 t.match(response.json(), { error: 'Bad Request', message: 'MessageBird API error: Recipient not valid', details: mockMbErrorResponse }, 'Response body should contain MessageBird error details'); t.ok(nock.isDone(), 'MessageBird API mock endpoint should have been called'); }); t.test('should return 503 when MessageBird API does not respond', async (t) => { const app = build(t); const testPayload = { recipient: '+13334445566', body: 'Timeout test' }; nock('https://rest.messagebird.com') .post('/mms') .delayConnection(100) // Simulate a delay .replyWithError({ code: 'ETIMEDOUT' }); // Simulate a timeout/network error const response = await app.inject({ method: 'POST', url: '/send-mms', headers: { 'x-api-key': 'test-api-key' }, payload: testPayload }); t.equal(response.statusCode, 503, 'Should return status code 503'); t.match(response.json(), { error: 'Service Unavailable' }); t.ok(nock.isDone(), 'MessageBird API mock endpoint should have been called'); }); // Add more tests for other scenarios: // - Using 'recipients' array instead of 'recipient' // - Missing 'body' but providing 'mediaUrls' // - Providing 'subject', 'reference', 'scheduledDatetime' // - Exceeding recipient limit (should return 400 before calling MessageBird) // - MessageBird 5xx errors (should return 500) });nock: Mocks HTTP requests to the MessageBird API, preventing actual calls during tests and allowing you to simulate success/error responses.app.inject: Fastify's utility to simulate HTTP requests to your application without needing a running server.Integration Testing:
npm run dev).curlor a tool like Postman/Insomnia to send requests tohttp://localhost:3000/send-mms(or your configured port)..env: Ensure your.envfile is populated with test credentials if possible, or be prepared to use live (but potentially costly) credentials carefully.curl -X POST http://localhost:3000/send-mms \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_SECURE_API_KEY_FOR_THIS_APP" \ -d '{ "recipient": "+1xxxxxxxxxx", "body": "Hello from Fastify & MessageBird!", "mediaUrls": ["https://www.messagebird.com/assets/images/og/messagebird.png"] }'Webhook Testing (using
ngrok):npm run devngrok http 3000(replace 3000 if using a different port). Note the publichttps://*.ngrok.ioURL./mms-statusroute when MessageBird sends status updates.ngrokweb interface (http://localhost:4040by default) to inspect incoming webhook requests.9. Deploying Your Fastify MMS Application to Production
Deploying the application makes it accessible publicly for sending MMS messages via MessageBird.
Choose a Hosting Provider:
Prepare for Production:
.env). Use the provider's secrets management.NODE_ENV=production: Ensure theNODE_ENVenvironment variable is set toproduction. This disables development features (likepino-pretty) and enables optimizations in Fastify and other libraries.package.jsonstartscript (node server.js) is correct.pm2or similar to manage the Node.js process (restarts on crash, clustering).Deployment Methods (Examples):
heroku loginheroku createheroku config:set MESSAGEBIRD_API_KEY=... FASTIFY_API_KEY=...etc.Procfileexists (usually inferred for Node.js):web: npm startgit push heroku mainDockerfile:docker build -t fastify-mms-app .docker run -p 3000:3000 -e MESSAGEBIRD_API_KEY=... -e FASTIFY_API_KEY=... fastify-mms-app(Pass secrets via-eor volume mounts).Conclusion
You have successfully built a Node.js application using Fastify to send MMS messages via the MessageBird API. This service includes essential features like configuration management, authentication, robust error handling, logging, and webhook support for delivery status updates. Remember to prioritize security, thoroughly test your implementation, and choose a deployment strategy that fits your needs. This foundation allows you to integrate rich multimedia messaging into your applications effectively for users in the US and Canada.
For further MessageBird integration options, explore our guides on two-way MMS messaging and bulk broadcast campaigns.