This guide provides a step-by-step walkthrough for building a Node.js application using the Express framework to send SMS messages containing links to media files via AWS Simple Notification Service (SNS). While AWS SNS primarily sends SMS text messages, including a publicly accessible URL to an image or video in the message body effectively simulates an MMS experience for many modern smartphones.
Project Goal: To create a robust API endpoint that accepts a phone number and a media URL, then uses AWS SNS to send an SMS containing the URL to the specified recipient.
Problem Solved: Enables applications to programmatically send notifications or messages that include visual content without managing complex carrier integrations directly. This is ideal for sending alerts with images, promotional messages with product photos, or verification codes alongside visual instructions.
Technologies Used:
- Node.js: A JavaScript runtime environment for building server-side applications.
- Express: A minimal and flexible Node.js web application framework for building APIs.
- AWS SDK for JavaScript (v2): Enables Node.js applications to interact with AWS services, including SNS. (Note: This guide uses AWS SDK v2 for simplicity. AWS recommends v3 for new projects due to its modularity. Migration guides are available on the AWS documentation website.)
- AWS SNS: A managed messaging service for sending messages (including SMS) to various endpoints.
- dotenv: A module to load environment variables from a
.env
file intoprocess.env
.
System Architecture:
graph LR
Client[Client Application/User] -- HTTPS POST --> API{Node.js/Express API}
API -- Uses AWS SDK --> SNS[AWS SNS Service]
SNS -- Delivers SMS --> Phone[Recipient's Phone]
API -- Reads Credentials --> ENV[.env File / Environment Variables]
MediaHost[Media Hosting Service e.g., S3] -- Serves Media --> Phone
subgraph AWS Cloud
SNS
end
subgraph Your Infrastructure
API
ENV
end
subgraph External
Client
Phone
MediaHost
end
Prerequisites:
- An AWS account with appropriate permissions to use SNS and create IAM users.
- Node.js and npm (or yarn) installed on your development machine.
- AWS CLI installed and configured (optional but recommended for managing AWS resources).
- A publicly accessible URL for the media file you intend to send (e.g., hosted on AWS S3, Cloudinary, or another CDN). SNS does not host media files.
- A text editor or IDE (like VS Code).
Setting up the Project
This section covers initializing the Node.js project, installing dependencies, and structuring the application.
-
Create Project Directory: Open your terminal and create a new directory for your project, then navigate into it.
mkdir node-sns-mms-sender cd node-sns-mms-sender
-
Initialize npm Project: This command creates a
package.json
file to manage project dependencies and scripts.npm init -y
-
Install Dependencies: We need Express for the API framework,
aws-sdk
to interact with AWS SNS, anddotenv
to manage environment variables securely.npm install express aws-sdk dotenv
-
Create Project Structure: Organize the code into logical directories.
mkdir src mkdir src/routes mkdir src/services mkdir src/middleware touch src/server.js touch src/routes/mms.js touch src/services/snsService.js touch src/middleware/auth.js touch .env touch .env.example touch .gitignore
src/
: Contains the main application code.src/routes/
: Defines API endpoints.src/services/
: Contains business logic, like interacting with AWS SNS.src/middleware/
: Holds custom middleware functions (e.g., authentication).src/server.js
: The main entry point for the Express application..env
: Stores sensitive credentials (API keys, AWS keys). Never commit this file to Git..env.example
: A template showing required environment variables. Commit this file..gitignore
: Specifies files and directories that Git should ignore.
-
Configure
.gitignore
: Addnode_modules
and.env
to prevent committing them to version control.# .gitignore node_modules/ .env npm-debug.log* yarn-debug.log* yarn-error.log*
-
Configure
.env.example
: List the required environment variables as a template for other developers (or yourself later).# .env.example # AWS Credentials AWS_ACCESS_KEY_ID=YOUR_AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY=YOUR_AWS_SECRET_ACCESS_KEY AWS_REGION=YOUR_AWS_REGION # e.g., us-east-1 # Application Settings PORT=3000 API_KEY=YOUR_SECURE_API_KEY_FOR_THIS_SERVICE # A secret key clients must send
-
Populate
.env
: Copy.env.example
to.env
and fill in your actual AWS credentials and a secure API key. You will obtain AWS credentials in the ""Integrating with AWS SNS"" section. ForAPI_KEY
, generate a strong, random string.cp .env.example .env # Now edit .env with your actual values
AWS_REGION
: The AWS region where you plan to use SNS (e.g.,us-east-1
,eu-west-1
). Ensure this region supports SMS messaging.
Implementing Core Functionality (SNS Service)
This service module encapsulates the logic for interacting with AWS SNS.
-
Create the SNS Service (
src/services/snsService.js
): This file will contain the function to publish the SMS message via SNS.// src/services/snsService.js const AWS = require('aws-sdk'); require('dotenv').config(); // Load environment variables // Configure AWS SDK // Credentials will be automatically picked up from environment variables: // AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION // Ensure these are set in your .env file or environment AWS.config.update({ region: process.env.AWS_REGION }); // Create SNS service object const sns = new AWS.SNS({ apiVersion: '2010-03-31' }); /** * Sends an SMS message containing text and a media URL via AWS SNS. * @param {string} phoneNumber - The recipient's phone number in E.164 format (e.g., +12125551234). * @param {string} messageBody - The main text part of the message. * @param {string} mediaUrl - The publicly accessible URL of the media file. * @returns {Promise<string>} - A promise that resolves with the SNS Message ID on success. * @throws {Error} - Throws an error if the message fails to send. */ async function sendMmsViaSns(phoneNumber, messageBody, mediaUrl) { // Construct the full message including the media URL // Ensure there's a space or newline before the URL for better rendering on phones const fullMessage = `${messageBody}\n${mediaUrl}`; // Basic validation if (!phoneNumber || !/^(\+)[1-9]\d{1,14}$/.test(phoneNumber)) { throw new Error('Invalid phone number format. Use E.164 format (e.g., +12125551234).'); } if (!mediaUrl || !mediaUrl.startsWith('http')) { throw new Error('Invalid media URL. Must be a valid HTTP/HTTPS URL.'); } if (fullMessage.length > 1600) { // SNS SMS limit is 1600 bytes, but keeping it shorter is safer for carrier compatibility console.warn('Message length is long, may be split or rejected by carriers.'); } const params = { Message: fullMessage, PhoneNumber: phoneNumber, MessageAttributes: { 'AWS.SNS.SMS.SMSType': { DataType: 'String', StringValue: 'Transactional' // Use 'Transactional' for high priority, 'Promotional' for lower cost/priority } // You might add 'AWS.SNS.SMS.SenderID' here if you have a custom Sender ID configured // 'AWS.SNS.SMS.SenderID': { DataType: 'String', StringValue: 'MySenderID' } } }; console.log(`Attempting to send SMS to ${phoneNumber}`); try { // Use .promise() for async/await syntax const publishResponse = await sns.publish(params).promise(); console.log(`Message sent successfully to ${phoneNumber}. Message ID: ${publishResponse.MessageId}`); return publishResponse.MessageId; } catch (error) { console.error(`Failed to send message to ${phoneNumber}:`, error); // Re-throw a more specific error or handle it as needed throw new Error(`SNS Publish Error: ${error.message}`); } } module.exports = { sendMmsViaSns, };
Why this approach?
- Encapsulation: Keeps AWS-specific logic separate from API route handlers.
- Async/Await: Uses modern JavaScript for cleaner asynchronous code (
.promise()
withasync/await
). - Configuration: Loads AWS credentials securely from environment variables via
dotenv
. - Validation: Includes basic checks for phone number format (E.164 standard is crucial for SNS) and URL validity.
- Message Attributes: Sets
SMSType
toTransactional
. This optimizes for delivery reliability, which is generally preferred for messages containing important links or information, though it might cost slightly more thanPromotional
. UsingTransactional
can also help bypass Do-Not-Disturb settings in some regions/cases. - Error Handling: Includes
try...catch
and logs errors for easier debugging.
Building the API Layer
This involves setting up the Express server and defining the endpoint to trigger the MMS sending.
-
Create Basic Authentication Middleware (
src/middleware/auth.js
): This is a very basic example using a static API key. In production, you'd likely use JWT, OAuth, or a more robust method.// src/middleware/auth.js require('dotenv').config(); const API_KEY = process.env.API_KEY; if (!API_KEY) { // Corrected console error message quotes console.error(""FATAL ERROR: API_KEY environment variable is not set.""); process.exit(1); // Exit if the API key isn't configured } function simpleApiKeyAuth(req, res, next) { const providedApiKey = req.headers['x-api-key']; // Check a custom header if (!providedApiKey || providedApiKey !== API_KEY) { console.warn('Authentication failed: Invalid or missing API key.'); return res.status(401).json({ error: 'Unauthorized: Invalid API Key' }); } // If key is valid, proceed to the next middleware or route handler next(); } module.exports = simpleApiKeyAuth;
-
Define the MMS Route (
src/routes/mms.js
): This file defines the/send
endpoint.// src/routes/mms.js const express = require('express'); const { sendMmsViaSns } = require('../services/snsService'); const simpleApiKeyAuth = require('../middleware/auth'); // Import the auth middleware const router = express.Router(); // Apply the API key authentication middleware to this route router.post('/send', simpleApiKeyAuth, async (req, res) => { // Basic Request Validation const { phoneNumber, messageBody, mediaUrl } = req.body; if (!phoneNumber || !mediaUrl) { return res.status(400).json({ error: 'Bad Request: Missing required fields phoneNumber or mediaUrl.' }); } // Optional: Add more specific validation for messageBody if needed const finalMessageBody = messageBody || ' '; // Use a default if not provided try { console.log(`Received request to send MMS to ${phoneNumber} with media ${mediaUrl}`); const messageId = await sendMmsViaSns(phoneNumber, finalMessageBody, mediaUrl); res.status(200).json({ success: true, message: 'Message submitted to SNS successfully.', phoneNumber: phoneNumber, messageId: messageId }); } catch (error) { console.error('Error in /send route:', error.message); // Determine appropriate status code based on error type if possible // Note: Checking error messages directly can be brittle. In production, consider using // custom error classes propagated from the service layer or checking specific `error.code` // properties if provided by the AWS SDK or underlying libraries for more robust error differentiation. if (error.message.includes('Invalid phone number') || error.message.includes('Invalid media URL')) { res.status(400).json({ success: false, error: `Bad Request: ${error.message}` }); } else if (error.message.includes('SNS Publish Error')) { res.status(500).json({ success: false, error: `Internal Server Error: Failed to send message via SNS. ${error.message}` }); } else { res.status(500).json({ success: false, error: 'Internal Server Error: An unexpected error occurred.' }); } } }); module.exports = router;
-
Set up the Express Server (
src/server.js
): This ties everything together.// src/server.js require('dotenv').config(); // Must be at the top const express = require('express'); const mmsRoutes = require('./routes/mms'); const app = express(); const PORT = process.env.PORT || 3000; // Use port from .env or default to 3000 // Middleware app.use(express.json()); // Enable parsing JSON request bodies // Basic Logging Middleware (optional, consider Morgan for more detail) app.use((req, res, next) => { console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); next(); }); // Routes app.use('/api/mms', mmsRoutes); // Mount the MMS routes under /api/mms // Simple Health Check Endpoint app.get('/health', (req, res) => { res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() }); }); // Global Error Handler (Basic Example) // This catches errors not handled in specific routes app.use((err, req, res, next) => { console.error('Global Error Handler:', err.stack); res.status(500).json({ error: 'Something went wrong!' }); }); // Start the server app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); console.log(`API documentation endpoint: POST /api/mms/send`); console.log(`Requires 'x-api-key' header for authentication.`); });
-
Update Run Scripts in
package.json
: Update thescripts
section in yourpackage.json
.// package.json (update the ""scripts"" section) ""scripts"": { ""start"": ""node src/server.js"", ""test"": ""echo \""Error: no test specified\"" && exit 1"" },
-
Testing the API Endpoint: You can now start the server:
npm start
. Usecurl
or Postman to test thePOST /api/mms/send
endpoint. Replace placeholders with your actual API key, a verified phone number (see ""Integrating with AWS SNS"" section - AWS Sandbox), and a media URL.curl -X POST http://localhost:3000/api/mms/send \ -H ""Content-Type: application/json"" \ -H ""x-api-key: YOUR_SECURE_API_KEY_FOR_THIS_SERVICE"" \ -d '{ ""phoneNumber"": ""+12065551234"", ""messageBody"": ""Check out this cool picture!"", ""mediaUrl"": ""https://via.placeholder.com/150.png"" }'
Expected Success Response (JSON):
{ ""success"": true, ""message"": ""Message submitted to SNS successfully."", ""phoneNumber"": ""+12065551234"", ""messageId"": ""some-unique-message-id-from-sns"" }
Expected Error Response (JSON):
// Example: Missing API Key { ""error"": ""Unauthorized: Invalid API Key"" }
// Example: Bad Request { ""error"": ""Bad Request: Missing required fields phoneNumber or mediaUrl."" }
// Example: Server Error { ""success"": false, ""error"": ""Internal Server Error: Failed to send message via SNS. SNS Publish Error: The security token included in the request is invalid."" }
Integrating with AWS SNS
This section details configuring AWS for SNS access.
-
Create an IAM User:
- Navigate to the IAM service in the AWS Management Console.
- Go to Users and click Add users.
- Enter a User name (e.g.,
SNSSmsMmsSenderApp
). - Select Access key - Programmatic access for the AWS credential type. Click Next: Permissions.
- Choose Attach existing policies directly.
- Search for
AmazonSNSFullAccess
. Select the checkbox next to it.- Security Best Practice: For production, create a custom policy granting only the
sns:Publish
permission, potentially restricted to specific regions or topics if applicable. Why?AmazonSNSFullAccess
grants excessive permissions like creating/deleting topics and subscriptions, which this application doesn't need, violating the principle of least privilege.
- Security Best Practice: For production, create a custom policy granting only the
- Click Next: Tags (add tags if desired, optional).
- Click Next: Review.
- Click Create user.
- Crucial: On the final screen, copy the Access key ID and Secret access key. Store these securely in your
.env
file. You cannot retrieve the secret key again after leaving this screen.AWS_ACCESS_KEY_ID=COPIED_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY=COPIED_SECRET_ACCESS_KEY
-
Configure AWS Region:
- Set the
AWS_REGION
in your.env
file to the region where you want to operate SNS (e.g.,us-east-1
). Ensure this region supports SMS. Check the AWS documentation for supported regions.
- Set the
-
Understand the SNS Sandbox:
- By default, new AWS accounts are placed in the SNS SMS sandbox.
- Sandbox Limitation: You can only send messages to phone numbers that you have explicitly verified within the SNS console for that region.
- Verification: Go to SNS in the AWS Console -> Text messaging (SMS) -> Sandbox destination phone numbers -> Add phone number. Follow the prompts to verify your number via an SMS code.
- Moving out of Sandbox: To send messages to any valid number, you must request to be moved out of the sandbox. Go to SNS -> Text messaging (SMS) -> Production access requests (or similar wording). You'll need to provide details about your use case. Approval typically takes around 24 hours. This step is essential for any real-world application.
-
Set Default SMS Type (Optional but Recommended): You can set the default SMS type account-wide or region-wide.
- Go to SNS -> Text messaging (SMS) -> Edit account preferences (or similar).
- Set Default message type to
Transactional
. This ensures high reliability is the default, matching our code's setting. Alternatively, keep itPromotional
if cost is the primary concern and reliability is secondary for most messages. Our code explicitly sets it per-message, which overrides this default if specified.
Error Handling, Logging, and Retry Mechanisms
Robust error handling is critical for production systems.
-
Consistent Error Strategy:
- Our
snsService.js
andmms.js
route usetry...catch
blocks to handle errors gracefully. - Specific errors (like validation failures) return 4xx status codes, while unexpected or SNS errors return 5xx.
- Errors are logged to the console using
console.error
.
- Our
-
Logging:
- We added basic request logging middleware in
server.js
. - For production, use a more structured logging library like
winston
orpino
. These enable:- Different log levels (debug, info, warn, error).
- Logging to files or external services (like CloudWatch Logs, Datadog, etc.).
- JSON formatting for easier parsing by log analysis tools.
- Log Analysis: Use AWS CloudWatch Logs to monitor SNS delivery status. SNS can be configured to log delivery success/failure events to CloudWatch, providing detailed insights into carrier issues or invalid numbers. Navigate to SNS -> Text messaging (SMS) -> Delivery status logging -> Create IAM roles (if needed) and enable logging for success/failure.
- We added basic request logging middleware in
-
Retry Mechanisms:
-
AWS SNS handles some level of retries internally, especially for transient network issues.
-
For application-level retries (e.g., if SNS returns a temporary error like
ThrottlingException
), you can implement a retry strategy with exponential backoff. -
First, install the
async-retry
package:npm install async-retry
-
Example using
async-retry
insnsService.js
:
// src/services/snsService.js (Modified sendMmsViaSns function) const AWS = require('aws-sdk'); require('dotenv').config(); const retry = require('async-retry'); // Import async-retry // ... (AWS config and SNS instance creation remain the same) ... async function sendMmsViaSns(phoneNumber, messageBody, mediaUrl) { // ... (validation code remains the same) ... const params = { Message: `${messageBody}\n${mediaUrl}`, PhoneNumber: phoneNumber, MessageAttributes: { 'AWS.SNS.SMS.SMSType': { DataType: 'String', StringValue: 'Transactional' } } }; console.log(`Attempting to send SMS to ${phoneNumber}`); try { const publishResponse = await retry( async (bail, attemptNumber) => { console.log(`SNS Publish attempt ${attemptNumber} for ${phoneNumber}`); try { // Try publishing the message return await sns.publish(params).promise(); } catch (error) { // Decide if the error is retryable if (error.code === 'ThrottlingException' || error.code === 'InternalFailure' || error.statusCode === 503) { console.warn(`Retryable SNS error encountered (attempt ${attemptNumber}): ${error.message}. Retrying...`); throw error; // Throw error to trigger retry } else { // Don't retry for non-retryable errors (e.g., invalid number, auth error) console.error(`Non-retryable SNS error encountered: ${error.message}`); // Stop retrying and pass the original error wrapped in a new error bail(new Error(`SNS Publish Error (Non-retryable): ${error.message}`)); } } }, { retries: 3, // Number of retries factor: 2, // Exponential backoff factor minTimeout: 1000, // Initial delay 1 second maxTimeout: 5000, // Max delay 5 seconds randomize: true, } ); // Check if publishResponse is valid (might be undefined if bail was called without an error) if (!publishResponse || !publishResponse.MessageId) { // This case might occur if bail was called in an unexpected way. throw new Error('SNS Publish failed after retries or due to non-retryable error.'); } console.log(`Message sent successfully to ${phoneNumber}. Message ID: ${publishResponse.MessageId}`); return publishResponse.MessageId; } catch (error) { // This catches errors after retries are exhausted or if bail was used with an Error console.error(`Failed to send message to ${phoneNumber} after retries:`, error); // Rethrow the final error, which might already be prefixed by the retry logic. throw new Error(`SNS Publish failed: ${error.message}`); } } module.exports = { sendMmsViaSns };
- Testing Errors: Temporarily use invalid AWS credentials or an incorrectly formatted phone number to test error paths. Simulate throttling by sending many requests quickly (though actual throttling depends on account limits).
-
Database Schema and Data Layer (Optional)
While not strictly required for sending messages, storing a log of sent messages is often useful for tracking, auditing, or debugging.
-
Schema Idea (e.g., using PostgreSQL):
CREATE TABLE message_logs ( id SERIAL PRIMARY KEY, recipient_number VARCHAR(20) NOT NULL, message_body TEXT, media_url VARCHAR(2048), sns_message_id VARCHAR(100) UNIQUE, -- Store the ID returned by SNS status VARCHAR(20) DEFAULT 'submitted', -- e.g., submitted, failed, delivered (requires delivery status logging) submitted_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, error_message TEXT, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); -- Index for querying by phone number or SNS message ID CREATE INDEX idx_message_logs_recipient ON message_logs(recipient_number); CREATE INDEX idx_message_logs_sns_id ON message_logs(sns_message_id);
-
Implementation: Use an ORM like Prisma or Sequelize to interact with the database. Modify the
/api/mms/send
route handler to insert a record before callingsendMmsViaSns
(status 'submitted'), and update it with thesns_message_id
on success or anerror_message
on failure. If implementing delivery status logging (via SNS -> CloudWatch -> Lambda -> DB), you could update thestatus
field later. -
Decision: We'll omit the database implementation in this core guide to keep focus, but this is a common next step for production systems.
Security Features
Security is paramount, especially when handling user data and external service credentials.
-
Input Validation:
- We added basic validation for
phoneNumber
(E.164 format) andmediaUrl
(starts with http/https) insnsService.js
. - The API route (
mms.js
) checks for the presence of required fields. - Enhancement: Use libraries like
joi
orexpress-validator
for more robust request body validation schemas. Sanitize inputs to prevent injection attacks, although SNS parameters are generally less susceptible than SQL queries.
- We added basic validation for
-
Secure Credential Management:
- AWS credentials and the API key are stored in
.env
, which is not committed to Git (enforced by.gitignore
). - In production environments (like AWS Elastic Beanstalk, ECS, Lambda), use the platform's built-in mechanisms for managing secrets (e.g., IAM Roles for EC2/ECS/Lambda, AWS Secrets Manager, Parameter Store) instead of
.env
files. IAM Roles are generally the most secure method as they avoid hardcoding credentials.
- AWS credentials and the API key are stored in
-
Authentication:
- Implemented basic API key authentication (
x-api-key
header). Ensure the key is strong and kept secret. - Enhancement: Use more standard and secure methods like OAuth 2.0 or JWT for client authentication in real-world applications.
- Implemented basic API key authentication (
-
Rate Limiting:
-
Protect the API endpoint from abuse and brute-force attacks. First, install
express-rate-limit
:npm install express-rate-limit
-
Apply it in
server.js
:
// src/server.js // ... other imports const rateLimit = require('express-rate-limit'); // ... app setup ... // Apply rate limiting to the API const apiLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per windowMs message: 'Too many requests from this IP, please try again after 15 minutes', standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers }); // Apply the rate limiting middleware to API calls only // Make sure this is placed before mounting the routes it should protect app.use('/api/mms', apiLimiter); // ... rest of the server setup (routes, error handler, listen) ...
-
-
HTTPS:
- Always deploy your API behind a load balancer or reverse proxy (like Nginx, Caddy, or AWS ALB/API Gateway) that handles TLS/SSL termination, ensuring all communication is over HTTPS. Do not run raw HTTP in production.
Handling Special Cases
Real-world messaging involves edge cases.
- Character Encoding & Limits: SNS SMS messages have limits (e.g., 160 GSM-7 characters, 70 UCS-2 characters for non-Latin alphabets, or longer segmented messages). While SNS handles segmentation, very long URLs combined with message bodies might exceed practical limits or render poorly. Keep messages concise. Our code includes a basic length warning.
- URL Shorteners: Consider using URL shorteners (like Bitly or a self-hosted one) if
mediaUrl
s are very long, but be aware that some carriers might flag messages containing shortener links as spam. - Carrier Filtering: Mobile carriers employ spam filters that might block messages based on content, sender ID, URL reputation, or sending volume. Using
Transactional
SMSType and maintaining a good sender reputation helps. Getting a dedicated Short Code or Sender ID (where available/required) can improve deliverability but involves extra setup and cost. - International Nuances: E.164 format handles country codes. Be mindful of varying regulations regarding SMS/MMS content and opt-in requirements in different countries.
- Opt-Out Handling: SNS automatically handles standard opt-out replies (e.g., STOP). You can use the
checkIfPhoneNumberIsOptedOut
andlistPhoneNumbersOptedOut
SNS API calls if you need to check status programmatically before sending.
Performance Optimizations
While SNS itself is highly scalable, the API layer can be optimized.
- Efficient AWS SDK Usage: Our current setup creates a new
AWS.SNS
client instance on module load, which is generally efficient. Avoid creating new clients per request. - Asynchronous Operations: The use of
async/await
ensures the Node.js event loop is not blocked during the AWS API call. - Load Testing: Use tools like
k6
,Artillery
, orApacheBench
to test the/api/mms/send
endpoint under load. Monitor resource usage (CPU, memory) on the server and watch for SNSThrottlingException
errors. Adjust server resources or request higher SNS quotas from AWS if needed. - Caching: Caching is generally not applicable to the sending action itself. However, if you frequently look up user data or templates before sending, caching that data could improve performance.
Monitoring, Observability, and Analytics
Monitoring ensures the service is healthy and performing as expected.
- Health Checks: The
/health
endpoint provides a basic check. Monitoring systems (like AWS CloudWatch Synthetics, UptimeRobot) can periodically hit this endpoint to verify the service is running. - Performance Metrics (Application):
- Monitor CPU, memory, network I/O, and Node.js event loop latency using tools like PM2, Datadog APM, or New Relic.
- Log request latency and error rates for the
/api/mms/send
endpoint.
- AWS SNS Metrics & Logs:
- Monitor SNS metrics in CloudWatch (e.g.,
NumberOfMessagesPublished
,NumberOfNotificationsFailed
). - Enable SNS Delivery Status Logging (as mentioned in the Logging section) to get detailed logs on message delivery success/failure per recipient, including carrier reasons for failure. This is crucial for debugging deliverability issues.
- Monitor SNS metrics in CloudWatch (e.g.,
- Analytics:
- Track how often messages are sent, potentially correlating sends with user actions if applicable.
- Analyze SNS delivery logs to understand delivery rates to different regions or carriers.