This guide provides a complete walkthrough for building a production-ready Node.js application using the Fastify framework to send Multimedia Messaging Service (MMS) messages via the Vonage Messages API. We will cover everything from initial project setup and Vonage configuration to implementing the core sending logic, error handling, security, deployment, and verification.
By the end of this tutorial, you will have a robust API endpoint capable of accepting requests to send images as MMS messages to specified recipients within the United States.
Project Goals:
- Build a simple, performant API endpoint using Fastify.
- Integrate with the Vonage Messages API to send MMS messages.
- Implement proper configuration management using environment variables.
- Incorporate basic error handling, logging, and security measures.
- Provide clear steps for setup, deployment, and verification.
Technologies Used:
- Node.js: The runtime environment for our JavaScript application.
- Fastify: A high-performance, low-overhead web framework for Node.js, chosen for its speed, extensibility, and developer experience.
- Vonage Messages API: The service used to send MMS messages programmatically.
@vonage/messages
: The official Vonage Node.js SDK for interacting with the Messages API.dotenv
: A utility to load environment variables from a.env
file intoprocess.env
.
Prerequisites:
- Node.js and npm (or yarn): Installed on your development machine. (Verify with
node -v
andnpm -v
). - Vonage API Account: Sign up if you don't have one. You get free credit to start.
- Vonage API Key and Secret: Found on your Vonage API Dashboard.
- A US Vonage Number: Purchase a US number capable of sending SMS and MMS through the Vonage dashboard (
Numbers
>Buy Numbers
). Note: MMS via the Messages API is currently supported for A2P use cases from US 10DLC, Toll-Free, and Short Code numbers to US destinations. - Vonage Application: You'll need to create a Messages API application in the Vonage dashboard. This process generates:
- Application ID: A unique identifier for your application.
- Private Key: A file (
private.key
) used for JWT authentication (required for Messages API v1).
- Basic understanding of Node.js, APIs, and the command line.
System Architecture:
The basic flow of our application will be:
+--------+ +-----------------+ +-----------------------+ +-----------+
| Client |------>| Fastify API App |------>| Vonage Messages API |----->| Recipient |
| (e.g., | HTTP | (Node.js) | HTTPS | (api.nexmo.com) | MMS | (Mobile) |
| curl) | POST | | API | | | |
+--------+ +-----------------+ +-----------------------+ +-----------+
| |
| Reads .env file |
| Uses Vonage SDK |
+-----------------+
1. Setting up the Project
Let's initialize our Node.js project and install the necessary dependencies.
-
Create Project Directory: Open your terminal and create a new directory for the project, then navigate into it.
mkdir fastify-vonage-mms cd fastify-vonage-mms
-
Initialize Node.js Project: Create a
package.json
file. The-y
flag accepts default settings.npm init -y
-
Install Dependencies: Install Fastify, the Vonage Messages SDK, and
dotenv
.npm install fastify @vonage/messages dotenv
-
Create Project Structure: Set up a basic file structure.
touch server.js .env .gitignore
server.js
: This will contain our Fastify application code..env
: This file will store our sensitive credentials and configuration (API keys, numbers, etc.). Never commit this file to version control..gitignore
: Specifies intentionally untracked files that Git should ignore.
-
Configure
.gitignore
: Addnode_modules
and.env
to your.gitignore
file to prevent committing them.# .gitignore node_modules .env *.log private.key
Why
.gitignore
? It prevents sensitive data (in.env
andprivate.key
) and large dependency folders (node_modules
) from being accidentally tracked by Git and pushed to repositories like GitHub.
2. Integrating with Vonage: Application Setup
Before writing code, we need to configure our Vonage account and application correctly. This is crucial for authentication, especially for the Messages API.
- Navigate to Vonage Dashboard: Log in to your Vonage API Dashboard.
- Create a New Application:
- Go to
Applications
>Create a new application
. - Give your application a name (e.g.,
Fastify MMS Sender
). - Enable
Messages
Capability: Toggle this capability on. - Webhook URLs (Inbound and Status): The Messages API requires webhook URLs even if you only plan to send outbound messages. For this simple guide, you can use a placeholder service like Mockbin:
- Go to Mockbin.com and click
Create Bin
. - Leave the defaults (Status Code 200 OK) and click
Create Bin
. - Copy the generated URL (e.g.,
https://mockbin.org/bin/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
). - Paste this URL into both the
Inbound URL
andStatus URL
fields under the Messages capability in the Vonage dashboard. In a production scenario, these would point to endpoints on your server to receive delivery receipts and inbound messages.
- Go to Mockbin.com and click
- Generate Public/Private Key Pair: Click the
Generate public/private key pair
link. This will:- Populate the
Public key
field in the dashboard. - Automatically download a file named
private.key
to your computer. Move thisprivate.key
file into your project's root directory (fastify-vonage-mms/
).
- Populate the
- Click
Create a new application
.
- Go to
- Link Your Vonage Number:
- After creating the application, you'll be taken to its configuration page. Scroll down to
Link numbers
. - Find the US MMS-capable number you purchased earlier and click the
Link
button next to it. This step is essential – the API will reject requests from numbers not linked to the Application ID being used.
- After creating the application, you'll be taken to its configuration page. Scroll down to
- Collect Credentials: Note down the following from your dashboard:
- API Key & API Secret: Found at the top of the main Dashboard page.
- Application ID: Found on the settings page of the application you just created.
- Your Vonage Number (FROM_NUMBER): The US number you linked to the application (use E.164 format, e.g.,
14155550100
).
3. Configuring Environment Variables
We use environment variables to manage configuration and keep sensitive credentials out of the codebase.
-
Edit the
.env
file: Open the.env
file in your editor and add the following variables, replacing the placeholder values with your actual credentials obtained in the previous step.# .env # Vonage API Credentials (From Dashboard Home) VONAGE_API_KEY=YOUR_API_KEY VONAGE_API_SECRET=YOUR_API_SECRET # Vonage Application Credentials (From Application Settings) VONAGE_APPLICATION_ID=YOUR_APPLICATION_ID VONAGE_APPLICATION_PRIVATE_KEY_PATH=./private.key # Vonage Number (Must be linked to Application ID, E.164 format) VONAGE_FROM_NUMBER=14155550100 # Server Configuration PORT=3000 HOST=0.0.0.0
-
Explanation of Variables:
VONAGE_API_KEY
,VONAGE_API_SECRET
: Your main account credentials. Used by the SDK for some underlying authentication checks.VONAGE_APPLICATION_ID
: Identifies the specific Vonage application configuration to use (linking, keys, webhooks). Essential for Messages API v1.VONAGE_APPLICATION_PRIVATE_KEY_PATH
: The relative path fromserver.js
to your downloadedprivate.key
file. The SDK uses this to generate JWTs for authenticating Messages API requests.VONAGE_FROM_NUMBER
: The Vonage number (in E.164 format) that will send the MMS. Must be linked to theVONAGE_APPLICATION_ID
.PORT
: The port your Fastify server will listen on.HOST
: The host address the server will bind to.0.0.0.0
makes it accessible from outside localhost (useful in containers or VMs).
4. Implementing the Fastify API Endpoint
Now, let's write the code for our Fastify server and the MMS sending endpoint.
server.js
// server.js
// 1. Load Environment Variables
require('dotenv').config();
// 2. Import Dependencies
const Fastify = require('fastify');
const { Messages, MMSImage } = require('@vonage/messages');
// 3. Initialize Fastify
// Enable logging for better debugging
const fastify = Fastify({
logger: {
level: 'info', // Log informational messages and above
transport: {
target: 'pino-pretty', // Make logs more readable during development
options: {
translateTime: 'HH:MM:ss Z',
ignore: 'pid,hostname',
},
},
}
});
// 4. Initialize Vonage Messages Client
// Ensure all required environment variables are present
const requiredEnv = [
'VONAGE_API_KEY',
'VONAGE_API_SECRET',
'VONAGE_APPLICATION_ID',
'VONAGE_APPLICATION_PRIVATE_KEY_PATH',
'VONAGE_FROM_NUMBER'
];
const missingEnv = requiredEnv.filter(envVar => !process.env[envVar]);
if (missingEnv.length > 0) {
console.error(`Error: Missing required environment variables: ${missingEnv.join(', ')}`);
console.error(""Please check your .env file or environment configuration."");
process.exit(1); // Exit if configuration is incomplete
}
const vonageMessages = new Messages({
apiKey: process.env.VONAGE_API_KEY,
apiSecret: process.env.VONAGE_API_SECRET,
applicationId: process.env.VONAGE_APPLICATION_ID,
privateKey: process.env.VONAGE_APPLICATION_PRIVATE_KEY_PATH, // SDK reads the file content
});
// 5. Define the MMS Sending Route Schema (for Validation)
const sendMmsSchema = {
body: {
type: 'object',
required: ['to', 'imageUrl'], // 'to' and 'imageUrl' are mandatory
properties: {
to: {
type: 'string',
description: 'Recipient phone number, ideally in E.164 format (e.g., +14155550101 or 14155550101 for US)',
// Pattern checks for optional '+' followed by 1-15 digits (non-zero first digit)
pattern: '^\\+?[1-9]\\d{1,14}$'
},
imageUrl: {
type: 'string',
description: 'Publicly accessible URL of the image (.jpg, .jpeg, .png)',
format: 'url' // Check if it looks like a URL
},
caption: {
type: 'string',
description: 'Optional text caption for the image',
maxLength: 1000 // Example max length, check Vonage docs for specifics
}
},
},
response: { // Define expected response structures
200: {
type: 'object',
properties: {
success: { type: 'boolean' },
messageId: { type: 'string' },
message: { type: 'string' }
}
},
400: { // Bad Request (Validation Failed)
type: 'object',
properties: {
success: { type: 'boolean', default: false },
error: { type: 'string' },
details: { type: 'object' } // Include validation details
}
},
500: { // Server Error (e.g., Vonage API issue)
type: 'object',
properties: {
success: { type: 'boolean', default: false },
error: { type: 'string' }
}
}
}
};
// 6. Implement the API Endpoint (/send-mms)
fastify.post('/send-mms', { schema: sendMmsSchema }, async (request, reply) => {
const { to, imageUrl, caption } = request.body; // Destructure validated body
const from = process.env.VONAGE_FROM_NUMBER;
fastify.log.info(`Attempting to send MMS from ${from} to ${to} with image: ${imageUrl}`);
try {
// Construct the MMS payload using the Vonage SDK
const mmsPayload = new MMSImage({
to: to,
from: from,
image: {
url: imageUrl,
caption: caption || '', // Use caption if provided, else empty string
},
// Optional: client_ref for tracking
// client_ref: `my-internal-id-${Date.now()}`
});
// Send the message via Vonage API
const response = await vonageMessages.send(mmsPayload);
fastify.log.info(`MMS submitted successfully to Vonage. Message UUID: ${response.message_uuid}`);
// Send success response back to the client
reply.code(200).send({
success: true,
messageId: response.message_uuid,
message: 'MMS submitted successfully.'
});
} catch (error) {
fastify.log.error(`Error sending MMS to ${to}: ${error.message}`);
// Log the full error object for detailed debugging if needed
// fastify.log.error(error);
let errorMessage = 'Failed to send MMS.';
let statusCode = 500;
// Check for specific Vonage API error details if available
if (error.response && error.response.data) {
fastify.log.error(`Vonage API Error Details: ${JSON.stringify(error.response.data)}`);
errorMessage = `Vonage API Error: ${error.response.data.title || error.message}`;
if (error.response.status === 401) {
errorMessage = ""Vonage authentication failed. Check API Key/Secret, Application ID, Private Key, and linked number."";
statusCode = 401; // Reflect the actual error status
} else if (error.response.status === 400) {
errorMessage = `Vonage Bad Request: ${error.response.data.detail || error.response.data.title || 'Check parameters.'}`;
statusCode = 400; // It might be a client-side parameter issue
} else {
statusCode = error.response.status || 500; // Use Vonage status if available
}
}
// Send error response back to the client
reply.code(statusCode).send({
success: false,
error: errorMessage
});
}
});
// 7. Add a simple Health Check endpoint
fastify.get('/health', async (request, reply) => {
return { status: 'ok', timestamp: new Date().toISOString() };
});
// 8. Start the Fastify 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: port, host: host });
fastify.log.info(`Server listening on http://${host}:${port}`);
} catch (err) {
fastify.log.error('Error starting server:', err);
process.exit(1);
}
};
start();
Code Explanation:
- Load Environment Variables:
require('dotenv').config()
loads the variables from your.env
file intoprocess.env
. This must be done early. - Import Dependencies: Bring in
fastify
and the specific classes needed from@vonage/messages
. - Initialize Fastify: Create a Fastify instance. We enable
logger
for better visibility during development and production.pino-pretty
makes logs easier to read locally. - Initialize Vonage Client: We check if all necessary Vonage-related environment variables are set. If not, the application logs an error and exits. Then, we instantiate the
Messages
client using the credentials fromprocess.env
. The SDK handles reading theprivate.key
file content. - Route Schema: Fastify's built-in JSON Schema validation is used. We define the expected structure (
body
) and constraints (required
,type
,pattern
,format
,maxLength
) for incoming requests to/send-mms
. We also define the structure of potential success (200
) and error (400
,500
) responses. This provides automatic request validation and response serialization. Thepattern
forto
validates numbers generally conforming to E.164 format (optional+
, up to 15 digits). - API Endpoint (
/send-mms
):fastify.post('/send-mms', { schema: sendMmsSchema }, ...)
defines a POST route. Theschema
option automatically validates incoming requests againstsendMmsSchema.body
. If validation fails, Fastify sends a 400 Bad Request response automatically.- Inside the
async
handler, we destructure the validatedrequest.body
. - We construct the
MMSImage
payload, passing theto
,from
(from env), andimage
details (URL and optional caption). vonageMessages.send()
makes the actual API call to Vonage.- A
try...catch
block handles potential errors during the API call. - On success, we log the
message_uuid
returned by Vonage and send a 200 OK response with the UUID. - On failure, we log the error and send an appropriate error response (using the status code from the Vonage error if available, otherwise 500 Internal Server Error). We try to provide more specific error messages based on common Vonage issues (like 401 Unauthorized).
- Health Check: A standard
/health
endpoint that returns a 200 OK status, useful for monitoring systems to check if the service is running. - Start Server: The
start
function usesfastify.listen()
to bind the server to the specified host and port (from.env
or defaults). Errors during startup are caught and logged.
5. Error Handling and Logging
- Logging: Fastify's built-in Pino logger is configured for
info
level. Errors are explicitly logged in thecatch
block usingfastify.log.error()
. In production, you might configure different log levels or transports (e.g., sending logs to a centralized service). - Request Validation: Fastify's schema validation handles invalid input formats before our route handler code runs, returning informative 400 errors.
- API Errors: The
try...catch
block handles errors from thevonageMessages.send()
call. We attempt to parse common Vonage error responses (error.response.data
,error.response.status
) to provide more helpful feedback to the client. - Retry Mechanisms: This guide doesn't implement automatic retries. For production, consider adding a retry mechanism (e.g., using libraries like
async-retry
orp-retry
) with exponential backoff for transient network issues or temporary Vonage API unavailability when callingvonageMessages.send()
. However, be cautious retrying 4xx errors (client errors) as they likely won't succeed without changes.
6. Database Schema and Data Layer
This specific guide focuses solely on the stateless action of sending an MMS and does not require a database.
If you needed to track message status, store sending history, or manage user data, you would:
- Choose a Database: PostgreSQL, MySQL, MongoDB, etc.
- Design Schema: Define tables/collections (e.g.,
messages
table withmessage_uuid
,to_number
,from_number
,status
,image_url
,timestamp
,vonage_response
). - Implement Data Layer: Use an ORM (like Prisma, Sequelize, TypeORM) or a database client library (like
pg
,mysql2
) to interact with the database. - Integrate: Save message details before sending to Vonage, and update the status based on the API response or status webhooks.
Adding a database is beyond the scope of this initial MMS sending guide.
7. Adding Security Features
- Input Validation: Handled robustly by Fastify's schema validation, preventing unexpected data types or missing fields. The pattern matching for
to
andformat: 'url'
forimageUrl
add further checks. Always sanitize or validate any data before using it. - Credential Security: Sensitive keys are stored in
.env
and kept out of version control via.gitignore
. Ensure theprivate.key
file has restrictive file permissions on the server (e.g.,chmod 400 private.key
). In production, use a dedicated secret management system (like HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager) instead of.env
files. - Rate Limiting: Protects against abuse and brute-force attempts. Install and configure
fastify-rate-limit
:Add this registration near the top ofnpm install fastify-rate-limit
server.js
, after initializing Fastify:Adjust// server.js (near top) fastify.register(require('fastify-rate-limit'), { max: 100, // Max requests per windowMs per IP timeWindow: '1 minute' // Time window });
max
andtimeWindow
based on expected traffic. - HTTPS: In production, always run your Node.js application behind a reverse proxy (like Nginx or Caddy) that handles TLS/SSL termination (HTTPS). Do not handle TLS directly in Node.js unless necessary.
- Image URL Validation: Be cautious about the
imageUrl
provided. A malicious user could provide a URL pointing to an internal resource or attempt SSRF. While this guide assumes public URLs, production systems might need stricter validation, domain whitelisting, or fetching the image server-side first. - Helmet: Consider using
@fastify/helmet
to set various security-related HTTP headers.npm install @fastify/helmet
// server.js (near top) fastify.register(require('@fastify/helmet'));
8. Handling Special Cases
- Number Formatting: The Vonage API typically expects numbers in E.164 format (e.g.,
+14155550100
). The schema includes a basic pattern (^\\+?[1-9]\\d{1,14}$
) that accepts this format, as well as the format without the+
(e.g.,14155550100
) which Vonage often accepts for US numbers. For maximum compatibility, sending in E.164 format is recommended. Robust validation might involve a dedicated library likelibphonenumber-js
. - Image URL Accessibility: The
imageUrl
must be publicly accessible over the internet for Vonage to fetch it. Internal URLs or URLs requiring authentication will fail. - Supported Image Types: Vonage Messages API for MMS primarily supports
.jpg
,.jpeg
, and.png
. Sending other types may result in errors or unexpected behavior. The maximum file size limits should also be considered (check Vonage documentation). - Captions: Character limits exist for captions. Check the latest Vonage documentation for specifics. The schema adds a basic
maxLength
. - Vonage Number Capabilities: Ensure the
VONAGE_FROM_NUMBER
is actually enabled for MMS sending in the US. Check the number's capabilities in the Vonage dashboard. - A2P Compliance: Remember MMS is for Application-to-Person use cases and subject to carrier filtering and compliance rules (like 10DLC registration for local numbers).
9. Implementing Performance Optimizations
- Fastify's Speed: Fastify is inherently fast due to its architecture and optimized JSON handling.
- Asynchronous Operations: The use of
async/await
ensures Node.js's event loop is not blocked during the Vonage API call. - Logging: While essential, excessive synchronous logging in high-traffic scenarios can impact performance. Pino is designed to be fast, but be mindful.
- Payload Size: Keep request/response payloads reasonably sized.
- Caching: Not directly applicable for the sending action itself, but if you were fetching data to decide whether to send an MMS, caching that data (e.g., using Redis with
fastify-redis
) could improve performance. - Load Testing: Use tools like
k6
,autocannon
, orwrk
to test the endpoint's performance under load and identify bottlenecks.# Example using autocannon (install with: npm install -g autocannon) # Note the use of single quotes for the JSON body to avoid shell interpretation issues autocannon -m POST -H 'Content-Type: application/json' -b '{""to"": ""14155550101"", ""imageUrl"": ""https://placekitten.com/200/300"", ""caption"": ""Test Cat""}' http://127.0.0.1:3000/send-mms
10. Adding Monitoring, Observability, and Analytics
- Health Checks: The
/health
endpoint provides a basic check for uptime monitoring tools (like Pingdom, UptimeRobot, or Kubernetes liveness probes). - Logging: Centralized logging (ELK stack, Datadog Logs, Grafana Loki) is crucial in production to aggregate logs from multiple instances. Structure your logs (JSON format helps) for easier parsing. Fastify/Pino logs in JSON by default when not using
pino-pretty
. - Metrics: Instrument your application to collect key metrics:
- Request rate and duration (Fastify plugins like
fastify-metrics
can help). - Error rates (track 4xx and 5xx responses).
- Vonage API call latency and success/error rates.
- Node.js process metrics (CPU, memory, event loop lag - e.g., using
prom-client
). - Export these metrics to a system like Prometheus and visualize them in Grafana or Datadog.
- Request rate and duration (Fastify plugins like
- Error Tracking: Use services like Sentry (
@sentry/node
) or Datadog APM to capture, aggregate, and alert on runtime errors with stack traces and context. - Distributed Tracing: For more complex microservice architectures, implement distributed tracing (OpenTelemetry, Jaeger, Zipkin) to follow requests across service boundaries.
11. Troubleshooting and Caveats
401 Unauthorized
Error: This is common with the Messages API.- Check: API Key/Secret (
.env
). - Check: Application ID (
.env
). - Check:
private.key
file path (.env
) and ensure the file exists and is readable by the Node.js process. - Check: Ensure the
VONAGE_FROM_NUMBER
is correctly linked to theVONAGE_APPLICATION_ID
in the dashboard. - Check: Account permissions – ensure your Vonage account/subaccount has Messages API enabled.
- Check: API Key/Secret (
- Invalid
from
Number: EnsureVONAGE_FROM_NUMBER
is correct, in E.164 format (or the format expected), and linked to the Application ID. - Invalid
to
Number: Ensure the recipient number is valid and in the expected format. Check Vonage logs for specific errors. Check schema validation pattern (^\\+?[1-9]\\d{1,14}$
). Image URL cannot be retrieved
/ Fetch Errors:- Verify the
imageUrl
is publicly accessible (try opening it in an incognito browser window). - Check for typos in the URL.
- Ensure the image format is supported (
.jpg
,.jpeg
,.png
). - Check Vonage status page for potential issues retrieving media.
- Verify the
- MMS Not Received:
- Check Vonage Dashboard Logs: Go to Logs > Messages API logs for detailed delivery status and error codes from carriers.
- Carrier Filtering: MMS can be subject to carrier filtering. Ensure compliance with regulations.
- Device Issues: The recipient device might have issues receiving MMS (data off, unsupported device).
- Use Delivery Receipts: Implement the Status Webhook URL properly to receive delivery status updates programmatically.
- Schema Validation Errors (400 Bad Request): Check the
details
field in the error response – Fastify provides specific information about which validation rule failed (e.g., missing required field, incorrect type, pattern mismatch). - Rate Limiting Errors (429 Too Many Requests): You've exceeded the rate limit configured in
fastify-rate-limit
or potentially Vonage's own API rate limits. .env
Not Loading: Ensurerequire('dotenv').config();
is called before accessingprocess.env
variables. Check the.env
file is in the correct directory relative to where you runnode server.js
.- Private Key Permissions: On Linux/macOS, if the Node.js process cannot read the
private.key
file due to permissions, you'll likely get an authentication error. Ensure the user running the Node process has read access.
12. Deployment and CI/CD
Basic Deployment (using PM2):
PM2 is a process manager for Node.js that helps keep your application alive and manage clustering.
- Install PM2 Globally:
npm install pm2 -g
- Create Ecosystem File (
ecosystem.config.js
): This file defines how PM2 should run your app.// ecosystem.config.js module.exports = { apps : [{ name : ""fastify-mms-sender"", script : ""./server.js"", instances: ""max"", // Or a specific number like 2 exec_mode: ""cluster"", // Enable clustering for better performance watch : false, // Set to true to restart on file changes (dev only) env : { // Environment variables PM2 will set NODE_ENV: ""production"", // PORT, HOST, VONAGE vars can also be set here, // but using system env variables or secret management is better } }] }
- Upload Code: Transfer your project files (
server.js
,package.json
,package-lock.json
,ecosystem.config.js
,private.key
) to your server. Do NOT upload.env
. - Install Dependencies on Server:
npm install --omit=dev # Install only production dependencies
- Set Environment Variables: Configure the required environment variables (
VONAGE_API_KEY
,VONAGE_API_SECRET
, etc.) directly on the server environment or use a secret management tool. Do not rely on the.env
file in production. - Start Application with PM2:
pm2 start ecosystem.config.js
- Manage Application:
pm2 list
: Show running processes.pm2 logs fastify-mms-sender
: View logs.pm2 stop fastify-mms-sender
: Stop the app.pm2 restart fastify-mms-sender
: Restart the app.pm2 save
: Save the current process list for reboot persistence.pm2 startup
: Generate command to run on system boot.
Containerization (Docker):
Create a Dockerfile
to package your application.
# Dockerfile
# Use an official Node.js runtime as a parent image
FROM node:18-alpine AS builder
WORKDIR /app
# Copy package files and install dependencies
COPY package*.json ./
RUN npm install --omit=dev
# Copy the rest of the application code
COPY . .
# --- Final Stage ---
FROM node:18-alpine
WORKDIR /app
# Copy only necessary files from the builder stage
COPY /app/node_modules ./node_modules
COPY /app/server.js ./server.js
COPY /app/private.key ./private.key # Copy private key
# Expose the port the app runs on
EXPOSE 3000
# Define environment variables (can be overridden at runtime)
# Best practice: Provide these via docker run -e or orchestration tools
ENV NODE_ENV=production
ENV PORT=3000
ENV HOST=0.0.0.0
# ENV VONAGE_API_KEY=... (Set these at runtime!)
# ENV VONAGE_API_SECRET=...
# ENV VONAGE_APPLICATION_ID=...
ENV VONAGE_APPLICATION_PRIVATE_KEY_PATH=./private.key
# ENV VONAGE_FROM_NUMBER=...
# Command to run the application
CMD [ ""node"", ""server.js"" ]
Security Note: The Dockerfile above copies the private.key
directly into the image. While simple, this is generally not recommended for production environments as it bundles a sensitive secret within the image artifact. Preferable approaches include:
- Volume Mounting: Mount the
private.key
file from the host or a secure volume into the container at runtime. - Orchestration Secrets: Use secret management features provided by your orchestrator (e.g., Kubernetes Secrets, Docker Swarm Secrets, AWS Secrets Manager, GCP Secret Manager) to inject the key file or its content as an environment variable or file at runtime.
Build and run the container, passing environment variables securely.
CI/CD (Conceptual Example - GitHub Actions):
Create a workflow file .github/workflows/deploy.yml
:
# .github/workflows/deploy.yml
name: Deploy MMS Sender
on:
push:
branches:
- main # Trigger deployment on push to main branch
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm install --omit=dev
# Add steps here to:
# 1. Build (if needed, e.g., TypeScript compilation)
# 2. Test (npm test)
# 3. Package (e.g., build Docker image, create zip archive)
# 4. Deploy (e.g., push Docker image, ssh to server and restart pm2, deploy to cloud platform)
# Example: SSH and restart PM2 (requires setting up secrets for SSH keys/host)
# - name: Deploy to Server
# uses: appleboy/ssh-action@master
# with:
# host: ${{ secrets.SSH_HOST }}
# username: ${{ secrets.SSH_USERNAME }}
# key: ${{ secrets.SSH_PRIVATE_KEY }}
# script: |
# cd /path/to/your/app
# git pull origin main
# npm install --omit=dev
# pm2 restart fastify-mms-sender || pm2 start ecosystem.config.js
# pm2 save