code examples
code examples
Send MMS with Vonage in RedwoodJS: Complete Implementation Guide
Integrate Vonage MMS capabilities into your RedwoodJS app. Step-by-step guide for sending multimedia messages with images using the Vonage Messages API, GraphQL, and RedwoodJS services.
Integrate Vonage Multimedia Messaging Service (MMS) capabilities into your RedwoodJS application with this step-by-step walkthrough. Set up your environment, configure the Vonage Node.js SDK, create a RedwoodJS service and GraphQL mutation to send MMS messages, handle errors, and test your implementation.
Build a functional RedwoodJS backend capable of sending MMS messages containing images via the Vonage Messages API, complete with necessary configurations and error handling.
Project Overview and Goals
Goal: To build a feature within a RedwoodJS application that allows sending MMS messages (specifically, images with captions) to US phone numbers using the Vonage Messages API.
<!-- EXPAND: Could benefit from concrete business use cases and ROI considerations (Type: Enhancement) -->Problem Solved: Provides a programmatic way to send rich media messages directly from your application backend, useful for notifications, alerts, or user engagement requiring images.
Technologies Used:
- RedwoodJS: A full-stack JavaScript/TypeScript framework for the web. Provides structure (API services, GraphQL layer) and tooling. This guide requires RedwoodJS v7.0.0 or later (latest v8.x as of January 2025).
- Node.js: The underlying runtime environment for the RedwoodJS API side. RedwoodJS v8.x requires Node.js v20.17.0 or later (Node.js >=20.x required).
- Yarn: Package manager for RedwoodJS. Version v4.1.1 or later required for RedwoodJS v8.x.
- Vonage Messages API: The third-party service used for sending MMS messages.
@vonage/messagesSDK: The official Vonage Node.js client library for interacting with the Messages API (v1.20.3 as of January 2025).- GraphQL: Used by RedwoodJS for API communication between the frontend and backend.
Architecture:
+-----------------+ +---------------------+ +-----------------+ +-------------+
| RedwoodJS Frontend | ---> | RedwoodJS GraphQL API | ---> | MMS Service (API) | ---> | Vonage Messages API |
| (e.g., Web UI) | | (Mutation) | | (@vonage/messages)| | |
+-----------------+ +---------------------+ +-----------------+ +-------------+
| ^
|------------------- Env Variables (.env) ---------------|
(Credentials)Prerequisites:
- Basic understanding of JavaScript/TypeScript and Node.js.
- Node.js v20.17.0 or later (Node.js >=20.x required for RedwoodJS) and Yarn v4.1.1 or later installed. Verify versions with
node --versionandyarn --version. - RedwoodJS CLI installed (
yarn global add redwoodjs/cli). - A Vonage API account. (Sign up for free credit if needed).
- A Vonage US phone number capable of sending SMS & MMS (10DLC, Toll-Free, or Short Code – check Vonage docs for specifics). All 10DLC campaigns are MMS-enabled as of January 2025. Purchase via the Vonage Dashboard (
Numbers>Buy Numbers) or CLI. - Your Vonage API Key and API Secret (found on the Vonage API Dashboard).
- A publicly accessible URL for the image you want to send. Vonage supports
.jpg,.jpeg,.png, and.gifformats. Maximum file size: 600KB recommended (200KB optimal to avoid compression; up to 1MB for Short Codes only, though this may compromise delivery/quality). Source: Vonage MMS File Types and Vonage MMS File Size. For testing,https://placekitten.com/200/300works.
Outcome: A RedwoodJS application with a GraphQL mutation endpoint that accepts a recipient phone number, image URL, and caption, then uses the Vonage SDK to send an MMS message.
1. Set Up the RedwoodJS Project
First, create a new RedwoodJS project if you don't have one already.
# Create a new RedwoodJS project (choose TypeScript)
yarn create redwood-app ./vonage-mms-app
cd vonage-mms-app
# Install the Vonage Messages SDK
yarn workspace api add @vonage/messagesEnvironment Variables:
Securely store your Vonage credentials using environment variables. Create a .env file in the root of your project:
# .env
VONAGE_API_KEY=YOUR_VONAGE_API_KEY
VONAGE_API_SECRET=YOUR_VONAGE_API_SECRET
VONAGE_APPLICATION_ID=YOUR_VONAGE_APPLICATION_ID # Will get this in the next step
VONAGE_SENDER_NUMBER=YOUR_VONAGE_US_NUMBER # e.g., +15551234567
VONAGE_PRIVATE_KEY_PATH=./private.key # Path relative to the api directory<Callout type=""warn"" title=""Important"">
Add .env and private.key (which we'll generate soon) to your .gitignore file to prevent accidentally committing credentials.
</Callout>
# .gitignore (add these lines if not present)
.env
api/private.keyProject Structure:
api/: Contains your backend code (services, GraphQL schema, functions, libs).api/src/lib/: Place for shared library code, like logger config.api/src/services/: Where your business logic resides (we'll createmms/mms.ts).api/src/graphql/: Defines your GraphQL schema (mms.sdl.ts).web/: Contains your frontend code (optional for this guide, we'll test via GraphQL)..env: Stores environment variables (ignored by Git).private.key: Vonage private key file (will be placed inapi/, ignored by Git).
2. Configure Vonage Application and Credentials
To use the Vonage Messages API for sending MMS via the SDK with JWT authentication (required), you need a Vonage Application.
<!-- DEPTH: Missing explanation of why JWT authentication is required vs basic auth (Priority: Medium) -->Steps:
- Navigate to Vonage Dashboard: Go to your Vonage API Dashboard > Applications.
- Create New Application: Click ""Create a new application"".
- Name Application: Give it a descriptive name (e.g., ""RedwoodJS MMS Sender"").
- Enable Messages: Toggle the ""Messages"" capability ON.
- Configure Webhooks:
- The SDK method we're using doesn't strictly require webhooks for sending, but the Application setup does. You must provide valid URLs. For testing, you can use a service like MockBin:
- Go to MockBin.org.
- Click ""Create Bin"".
- Copy the generated endpoint URL (e.g.,
https://mockbin.org/bin/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx). - Paste this URL into both the ""Inbound URL"" and ""Status URL"" fields under the Messages capability.
- <Callout type=""info"" title=""Production Note""> In a real application, these URLs would point to endpoints on your server to handle incoming messages and delivery status updates. </Callout>
- The SDK method we're using doesn't strictly require webhooks for sending, but the Application setup does. You must provide valid URLs. For testing, you can use a service like MockBin:
- 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
private.keyfile to your computer. Move thisprivate.keyfile into theapi/directory of your RedwoodJS project. (Ensure theVONAGE_PRIVATE_KEY_PATHin your.envfile matches this location, e.g.,./private.keyif it's directly inapi/).
- Create Application: Click ""Create Application"".
- Get Application ID: You'll be redirected to the application's page. Copy the Application ID and paste it into your
.envfile for theVONAGE_APPLICATION_IDvariable. - Link Your Number: Scroll down to ""Link numbers to application"". Find your purchased Vonage US MMS-capable number and click ""Link"". This authorizes the application to send messages using that number.
Environment Variable Summary:
VONAGE_API_KEY: Your main account API key (Vonage Dashboard).VONAGE_API_SECRET: Your main account API secret (Vonage Dashboard).VONAGE_APPLICATION_ID: The ID of the Vonage Application you just created/configured.VONAGE_SENDER_NUMBER: The linked, MMS-capable Vonage US number (include country code, e.g.,+1...).VONAGE_PRIVATE_KEY_PATH: The path to yourprivate.keyfile, relative to theapidirectory (e.g.,./private.key).
3. Implement the MMS Sending Service
Now, let's create the RedwoodJS service that contains the core logic for sending the MMS.
# Generate the mms service and GraphQL schema files
yarn rw g service mms
yarn rw g sdl mms # Generate the SDL file (we'll define the schema manually)This creates:
api/src/services/mms/mms.tsapi/src/services/mms/mms.scenarios.tsapi/src/services/mms/mms.test.tsapi/src/graphql/mms.sdl.ts
Edit the Service File (api/src/services/mms/mms.ts):
Replace the contents with the following code:
<!-- DEPTH: Code lacks inline comments explaining RedwoodJS-specific patterns (Priority: Medium) -->// api/src/services/mms/mms.ts
import type { MutationResolvers } from 'types/graphql'
import { Messages, MMSImage } from '@vonage/messages'
import { logger } from 'src/lib/logger'
import path from 'path' // Import path module
// Input type definition (matches the GraphQL input type)
interface SendMmsInput {
to: string
imageUrl: string
caption?: string // Optional caption
}
// Initialize Vonage client ONCE outside the handler function for efficiency
// Ensure environment variables are loaded correctly
const vonageClient = new Messages({
apiKey: process.env.VONAGE_API_KEY,
apiSecret: process.env.VONAGE_API_SECRET,
applicationId: process.env.VONAGE_APPLICATION_ID,
// Resolve the private key path relative to the 'api' directory base
privateKey: path.resolve(
__dirname,
'..', // Adjust relative path ('..') if build output moves this file deeper (e.g., 'dist/services/mms' might need '../..')
process.env.VONAGE_PRIVATE_KEY_PATH || './private.key'
),
})
// Internal function containing the core sending logic
const performMmsSend = async ({ to, imageUrl, caption }: SendMmsInput) => {
logger.info(`Attempting to send MMS to ${to}`)
// Validate inputs (basic example)
if (!to || !imageUrl || !process.env.VONAGE_SENDER_NUMBER) {
logger.error('Missing required parameters for sending MMS.')
throw new Error('Missing required parameters: to, imageUrl, or sender number.')
}
// Basic US number format check/normalization
if (!to.startsWith('+1')) {
logger.warn(`Recipient number ${to} might not be a valid US number. Prepending +1.`)
to = to.startsWith('+') ? to : `+${to}` // Ensure it starts with +
// WARNING: Use a proper validation library (e.g., google-libphonenumber) for production!
if (!to.match(/^\+1\d{10}$/)) { // Basic check for +1 and 10 digits
logger.error(`Invalid recipient US phone number format: ${to}`)
throw new Error(`Invalid recipient US phone number format: ${to}`)
}
}
try {
const mmsPayload = new MMSImage({
to: to,
from: process.env.VONAGE_SENDER_NUMBER,
image: {
url: imageUrl,
caption: caption || '', // Use provided caption or empty string
},
// You can add client_ref for tracking purposes
// client_ref: `mms-send-${Date.now()}`
})
const response = await vonageClient.send(mmsPayload)
logger.info({ response }, `MMS message submitted successfully to ${to}`)
return {
success: true,
messageId: response.message_uuid,
message: `MMS successfully submitted to ${to}. Message ID: ${response.message_uuid}`,
}
} catch (error) {
logger.error({ error }, `Failed to send MMS to ${to}`)
// Provide more context on the error if possible
const errorMessage =
error.response?.data?.title || error.message || 'Unknown error occurred'
const errorDetail = error.response?.data?.detail || 'No details provided'
// Rethrow a standard Error (simpler, caught by GraphQL layer) or return a structured error object (more GraphQL idiomatic, requires schema change):
throw new Error(`Vonage API Error: ${errorMessage} - ${errorDetail}`)
/*
// Example of returning structured error (requires schema changes in SDL and response type)
return {
success: false,
messageId: null,
message: `Failed to send MMS: ${errorMessage} - ${errorDetail}`,
// error: { code: 'VONAGE_ERROR', details: errorDetail } // Example structure
}
*/
}
}
// GraphQL Mutation Resolver
export const sendMms: MutationResolvers['sendMms'] = async ({ input }) => {
// Delegate the actual sending logic to the internal function
return performMmsSend(input)
}Explanation:
- Imports: We import necessary types, the Vonage
MessagesandMMSImageclasses, Redwood's logger, and Node.jspathmodule. - Input Interface: Defines the expected shape of data for sending an MMS.
- Vonage Client Initialization:
- We create a single instance of the
Messagesclient outside the resolver function. This is more efficient than creating it on every request. - Credentials (
apiKey,apiSecret,applicationId) are read fromprocess.env. privateKey: We usepath.resolveto correctly locate theprivate.keyfile relative to the current file's directory (__dirname) within theapistructure. This is crucial for it to work correctly both in development and potentially in production builds where file paths might change. Adjust the relative path ('..') if your build process alters the directory structure significantly (e.g., outputting to adistfolder).
- We create a single instance of the
performMmsSendFunction:- Contains the core logic for input validation and sending the MMS.
- Logs the attempt using Redwood's
logger. - Includes basic validation for required fields (
to,imageUrl, sender number) and a basic check/warning for thetonumber format (ensuring it starts with+1and has the correct length). <Callout type=""warn"" title=""Warning""> Production applications should implement more robust validation, potentially using a dedicated library likegoogle-libphonenumberto handle various formats and ensure validity. </Callout>
* Creates an `MMSImage` payload instance with `to`, `from` (from env vars), and `image` (URL and optional caption).
* Calls `vonageClient.send(mmsPayload)`.
* Uses a `try...catch` block for error handling (detailed below).
* Logs success or failure.
* Returns a structured object indicating success/failure and including the `message_uuid` from Vonage on success.
5. sendMms Resolver: This is the function RedwoodJS exposes via GraphQL. It simply takes the input object from the GraphQL mutation and passes it to the performMmsSend function.
4. Build the GraphQL API Layer
Now, define the GraphQL mutation that the frontend (or testing tools) will use to trigger the MMS sending service.
Edit the SDL File (api/src/graphql/mms.sdl.ts):
Replace the contents with the following schema definition:
# api/src/graphql/mms.sdl.ts
export const schema = gql`
""""""Input type for the sendMms mutation""""""
input SendMmsInput {
""""""Recipient phone number (US format, e.g., +15551234567)""""""
to: String!
""""""Publicly accessible URL of the image to send (.jpg, .jpeg, .png)""""""
imageUrl: String!
""""""Optional caption for the image""""""
caption: String
}
""""""Response type for the sendMms mutation""""""
type SendMmsResponse {
success: Boolean!
""""""Vonage message UUID on success""""""
messageId: String
""""""User-friendly status message""""""
message: String!
}
type Mutation {
""""""Sends an MMS message via Vonage""""""
sendMms(input: SendMmsInput!): SendMmsResponse! @skipAuth # Or use @requireAuth
}
`Explanation:
SendMmsInput: Defines the input object structure for the mutation, matching the interface in our service. Fields are marked as required (!) where necessary. Descriptions clarify the expected format.SendMmsResponse: Defines the structure of the object returned by the mutation, indicating success, the Vonage message ID (if successful), and a status message.Mutation:- Defines the
sendMmsmutation. - It takes one argument:
inputof typeSendMmsInput!. - It returns a
SendMmsResponse!. @skipAuth: This directive tells RedwoodJS not to enforce authentication for this mutation. <Callout type=""warn"" title=""Security Warning""> For production, you should almost always use@requireAuth(or implement role-based access control) to ensure only authorized users can send MMS messages, especially if it incurs costs. We use@skipAuthhere for easier testing. </Callout>
- Defines the
5. Implement Error Handling and Logging
Our service code already includes basic error handling and logging:
try...catchBlock: The call tovonageClient.send()is wrapped in atry...catch.- Logging: Redwood's built-in
logger(import { logger } from 'src/lib/logger') is used to log informational messages (logger.info) and errors (logger.error). Error logs include the caught error object for detailed debugging. - Error Propagation:
- If
vonageClient.send()throws an error, thecatchblock logs it. - It attempts to extract a user-friendly error message and detail from the Vonage API response (
error.response.data). - It then throws a new
Errorwhich will be caught by Redwood's GraphQL layer and returned to the client as a GraphQL error. Alternatively, as noted in the code comment, you could modify theSendMmsResponsetype to include anerrorfield and return the structured error object instead of throwing (this is often considered more idiomatic for GraphQL but requires schema changes).
- If
Common Vonage Errors & Troubleshooting:
401 Unauthorized:- Cause: Incorrect API Key/Secret, incorrect Application ID, missing/incorrect Private Key, Private Key path incorrect in
.envor code, Application ID not linked to the sending number, or the Vonage Application wasn't configured correctly for JWT authentication (using Application ID + Private Key is essential for Messages API). - Solution: Double-check all credentials in
.env, ensureprivate.keyis in the correct location (api/) and the path inmms.ts(path.resolve) is correct. Verify the number is linked to the application in the Vonage dashboard. Ensure the ""Messages"" capability is enabled for the application.
- Cause: Incorrect API Key/Secret, incorrect Application ID, missing/incorrect Private Key, Private Key path incorrect in
- Invalid
fromNumber: EnsureVONAGE_SENDER_NUMBERis correctly formatted (+1...) and is the number linked to your Vonage Application. - Invalid
toNumber: Ensure the recipient number is a valid US number (+1...). The service includes basic validation, but robust validation (e.g., using a library) is strongly recommended for production. - Invalid Image URL: Ensure
imageUrlis publicly accessible and points directly to a.jpg,.jpeg, or.pngfile. Private URLs or URLs requiring logins will fail. - Feature Not Enabled: MMS sending might need specific provisioning on your Vonage account or number, especially for 10DLC compliance. Contact Vonage support if you suspect this.
- Rate Limiting: Vonage may impose rate limits. Implement delays or use queues if sending bulk messages.
Log Analysis:
When troubleshooting, check the console output where you run yarn rw dev. Redwood's logger will print info and error messages, including details captured from Vonage API errors, which are invaluable for debugging.
Retry Mechanisms (Advanced):
For critical messages, you could implement a retry strategy (e.g., using a queue like BullMQ or integrating with RedwoodJS Background Jobs) with exponential backoff for transient network errors or Vonage API issues. This is beyond the scope of this basic guide.
<!-- GAP: Missing code example of implementing exponential backoff retry logic (Type: Substantive) -->6. Database Schema and Data Layer (Optional)
For this specific guide (simply sending an MMS on demand), no database interaction is strictly required.
However, in a real-world application, you might want to:
- Log MMS send attempts and statuses to a database table (
MmsLog?). - Trigger MMS sends based on database events (e.g., new order confirmation).
- Store user preferences related to notifications.
If needed, you would use Prisma (Redwood's default ORM):
- Define your model in
api/db/schema.prisma. - Run
yarn rw prisma migrate devto create/update the database schema. - Use the Prisma client (
import { db } from 'src/lib/db') within your service to interact with the database.
7. Add Security Features
- Credential Security:
- NEVER commit API keys, secrets, or private keys to version control. Use
.envand.gitignore. - Ensure
private.keyfile permissions are restricted in production environments.
- NEVER commit API keys, secrets, or private keys to version control. Use
- Authentication/Authorization:
- Use
@requireAuthor role-based directives on thesendMmsmutation inmms.sdl.tsto restrict access. Define who can send messages and under what conditions.
- Use
- Input Validation:
- Sanitize and validate all inputs (
to,imageUrl,caption). - Use libraries like
zodor a dedicated phone number validation library (e.g.,google-libphonenumber) for robust checks, especially for production. - Specifically validate phone number formats and potentially check against blocklists.
- Validate
imageUrlto ensure it's a valid URL format (though Vonage validates accessibility).
- Sanitize and validate all inputs (
- Rate Limiting:
- Implement rate limiting on the GraphQL endpoint (e.g., using RedwoodJS directives or middleware if needed) to prevent abuse and manage costs. Vonage also has account-level rate limits.
- Protect Against Common Vulnerabilities: While less critical for an outgoing-only API, always follow standard web security practices (OWASP Top 10).
8. Handle Special Cases
- US Number Formatting: The Vonage Messages API generally expects E.164 format for US numbers (
+1xxxxxxxxxx). The provided service code adds a basic check and normalization attempt, but production use requires more robust validation. - Image Requirements: Only publicly accessible
.jpg,.jpeg,.pngURLs are supported by Vonage MMS. Ensure the server hosting the image allows access from Vonage servers.
- Character Limits: Captions might have character limits imposed by carriers (though less strict than SMS). Keep them reasonably concise.
- Non-US Numbers: This setup using the standard Messages API with US numbers typically only supports sending to US numbers. Check Vonage documentation for international MMS capabilities if needed (often requires different setups/APIs).
- Delivery Failures: MMS delivery isn't guaranteed. Implement status webhooks (as configured in the Vonage Application setup) to receive delivery receipts (DLRs) and handle failures (e.g., notify admins, retry via SMS using Vonage Dispatch API as suggested in their blog).
9. Optimize Performance
- Client Initialization: The Vonage
Messagesclient is initialized once outside the resolver, preventing unnecessary setup on each request. - Asynchronous Operations: The
send()method is asynchronous (async/await), preventing the Node.js event loop from being blocked during the API call. - Payload Size: While image URLs are used, be mindful if generating images on the fly – keep processing efficient.
- Bulk Sending: For high-volume sending, consider queuing mechanisms (like BullMQ with Redis) to manage load and retries, rather than direct synchronous calls within the request handler.
10. Monitor, Observe, and Analyze
- Logging: Redwood's default logger provides basic observability. Configure log levels (
api/src/lib/logger.ts) and potentially ship logs to a centralized logging service (e.g., Datadog, Logtail) in production. - Error Tracking: Integrate an error tracking service (e.g., Sentry, Bugsnag) to capture and analyze runtime errors from the API side.
- Vonage Dashboard: Monitor MMS usage, delivery rates, and costs directly within the Vonage API Dashboard.
- Health Checks: Implement a basic health check endpoint in your RedwoodJS API (e.g., a simple GraphQL query or REST endpoint) that confirms the API is running.
11. Troubleshoot and Review Caveats
- 401 Unauthorized: Check all credentials (API Key/Secret, App ID, Private Key path/content) and number linking. Ensure Messages capability is enabled and JWT auth is used (implicit with App ID/Private Key).
- Invalid
from/to: Verify number formats (E.164) and ensure thefromnumber is linked and MMS-capable. Use robust validation fortonumbers in production. - Invalid Image: URL must be public, direct link to jpg/jpeg/png.
- Private Key Path: Double-check
path.resolveinmms.tsand the location ofprivate.key. Ensure the key file content is exactly as downloaded. Check build output paths if deploying. - Environment Variables: Ensure
.envis loaded correctly (Redwood usually handles this) and variables are accessible inprocess.env. Restart the dev server after changing.env. - US-Only: Standard setup primarily targets sending to US numbers.
- Delivery Not Guaranteed: Implement webhook handlers for delivery status updates for reliable tracking.
- Costs: Sending MMS incurs costs. Monitor usage via the Vonage dashboard.
12. Deploy with CI/CD
- Standard RedwoodJS Deployment: Follow RedwoodJS deployment guides for platforms like Vercel, Netlify, Render, or AWS Serverless.
- Environment Variables: Crucially, you must configure the same environment variables (
VONAGE_API_KEY,VONAGE_API_SECRET,VONAGE_APPLICATION_ID,VONAGE_SENDER_NUMBER,VONAGE_PRIVATE_KEY_PATH) in your deployment environment's settings.- Private Key Handling: For
VONAGE_PRIVATE_KEY_PATH, you have options:- Store as Multi-line Env Var: Some providers let you paste the entire key content into an environment variable. You'd then need to adjust the
mms.tscode to read the key content fromprocess.env.VONAGE_PRIVATE_KEY_CONTENTinstead of a file path. - Secure File Storage: Some platforms allow uploading secure files. Upload
private.keyand setVONAGE_PRIVATE_KEY_PATHto its location in the deployment environment (e.g.,/path/to/secure/files/private.key). This often requires build script adjustments to ensure the path is correct relative to the running code (consider thepath.resolvelogic). - Base64 Encode: Encode the key content as Base64, store it in an env var, and decode it in your code before passing it to the Vonage client.
- Store as Multi-line Env Var: Some providers let you paste the entire key content into an environment variable. You'd then need to adjust the
- Private Key Handling: For
- CI/CD: Set up pipelines (e.g., using GitHub Actions) to automate testing and deployment. Ensure environment variables (except possibly the private key content itself) are configured as secrets in your CI/CD environment for tests.
13. Verify and Test Your Implementation
Manual Verification:
-
Start Dev Server:
yarn rw dev -
Access GraphQL Playground: Open
http://localhost:8911/graphqlin your browser. -
Run Mutation: Execute the following mutation, replacing placeholders with your test recipient number (your own mobile is good for testing), a valid public image URL, and an optional caption:
graphqlmutation SendTestMms { sendMms( input: { to: ""+15559876543"" # Use a real US test number imageUrl: ""https://placekitten.com/200/300"" caption: ""Test MMS from RedwoodJS!"" } ) { success messageId message } } -
Check Phone: You should receive the MMS on the specified
tonumber shortly. -
Check Logs: Observe the console output from
yarn rw devfor success or error logs from the service.
Automated Testing (Unit/Integration):
RedwoodJS uses Jest for testing. Let's test the service logic.
Edit the Test File (api/src/services/mms/mms.test.ts):
Replace the contents with the following:
<!-- DEPTH: Test code lacks explanation of testing strategy and coverage goals (Priority: Medium) -->// api/src/services/mms/mms.test.ts
import { Messages, MMSImage } from '@vonage/messages'
import { sendMms } from './mms' // Import the resolver function
import { logger } from 'src/lib/logger'
// Mock the Vonage Messages SDK
// We keep track of the send function mock to assert calls
const mockSend = jest.fn()
jest.mock('@vonage/messages', () => {
// Mock the constructor and the send method
return {
Messages: jest.fn().mockImplementation(() => {
return { send: mockSend } // Return an object with the mocked send method
}),
MMSImage: jest.fn().mockImplementation((args) => ({ ...args })), // Mock MMSImage constructor
}
})
// Mock the logger to prevent actual logging during tests
jest.mock('src/lib/logger', () => ({
logger: {
info: jest.fn(),
error: jest.fn(),
warn: jest.fn(), // Add warn if you use it
},
}))
describe('mms service', () => {
// Define sample input matching the GraphQL input type
const validInput = {
to: '+15551234567',
imageUrl: 'https://valid.com/image.png',
caption: 'Test Caption',
}
// Set required environment variables for the tests
beforeAll(() => {
process.env.VONAGE_SENDER_NUMBER = '+15550001111'
// Mock other env vars if your client initialization depends on them being present,
// even though the mock replaces the actual client.
process.env.VONAGE_API_KEY = 'test-key'
process.env.VONAGE_API_SECRET = 'test-secret'
process.env.VONAGE_APPLICATION_ID = 'test-app-id'
process.env.VONAGE_PRIVATE_KEY_PATH = './mock-private.key' // Path doesn't matter due to mock
})
// Clear mocks after each test
afterEach(() => {
jest.clearAllMocks()
})
it('should call Vonage SDK send with correct parameters on success', async () => {
// Mock the Vonage API successful response
const mockApiResponse = { message_uuid: 'mock-uuid-12345' }
mockSend.mockResolvedValue(mockApiResponse)
// Call the resolver function (which calls the internal logic)
const result = await sendMms({ input: validInput })
// Assertions
expect(result.success).toBe(true)
expect(result.messageId).toBe(mockApiResponse.message_uuid)
expect(result.message).toContain('successfully submitted')
// Check if Vonage client constructor was called (verifies mock setup)
expect(Messages).toHaveBeenCalledTimes(1); // Verifies the mock constructor ran once during module load
// Check if MMSImage constructor was called with correct args
expect(MMSImage).toHaveBeenCalledWith({
to: validInput.to,
from: process.env.VONAGE_SENDER_NUMBER,
image: {
url: validInput.imageUrl,
caption: validInput.caption,
},
})
// Check if the send mock was called once with the MMSImage instance
expect(mockSend).toHaveBeenCalledTimes(1)
// Check the payload passed to send
expect(mockSend).toHaveBeenCalledWith(
expect.objectContaining({
to: validInput.to,
from: process.env.VONAGE_SENDER_NUMBER,
image: { url: validInput.imageUrl, caption: validInput.caption },
})
)
// Check logger calls (optional)
expect(logger.info).toHaveBeenCalledWith(
expect.stringContaining(`Attempting to send MMS to ${validInput.to}`)
)
expect(logger.info).toHaveBeenCalledWith(
{ response: mockApiResponse },
expect.stringContaining(`MMS message submitted successfully`)
)
expect(logger.error).not.toHaveBeenCalled()
})
it('should throw an error and log if Vonage SDK fails', async () => {
// Mock the Vonage API error response
const mockError = new Error('Vonage API Error') as any
mockError.response = {
data: {
title: 'Authentication Failed',
detail: 'Your credentials are wrong',
},
}
mockSend.mockRejectedValue(mockError)
// Expect the resolver call to throw
await expect(sendMms({ input: validInput })).rejects.toThrow(
'Vonage API Error: Authentication Failed - Your credentials are wrong'
)
// Check logger calls
expect(logger.error).toHaveBeenCalledTimes(1)
expect(logger.error).toHaveBeenCalledWith(
{ error: mockError },
expect.stringContaining(`Failed to send MMS to ${validInput.to}`)
)
expect(mockSend).toHaveBeenCalledTimes(1) // Ensure send was still attempted
})
it('should throw error for missing recipient number', async () => {
const invalidInput = { ...validInput, to: '' } // Missing 'to'
await expect(sendMms({ input: invalidInput })).rejects.toThrow(
'Missing required parameters'
)
expect(mockSend).not.toHaveBeenCalled()
expect(logger.error).toHaveBeenCalledWith(expect.stringContaining('Missing required parameters'))
})
it('should throw error for invalid US recipient number format (too short)', async () => {
const invalidInput = { ...validInput, to: '+112345' } // Invalid format
await expect(sendMms({ input: invalidInput })).rejects.toThrow(
'Invalid recipient US phone number format'
)
expect(mockSend).not.toHaveBeenCalled()
expect(logger.error).toHaveBeenCalledWith(expect.stringContaining('Invalid recipient US phone number format'))
})
it('should normalize and accept US number without +1 prefix', async () => {
const inputWithoutPlus1 = { ...validInput, to: '15551234567' };
const mockApiResponse = { message_uuid: 'mock-uuid-67890' };
mockSend.mockResolvedValue(mockApiResponse);
const result = await sendMms({ input: inputWithoutPlus1 });
expect(result.success).toBe(true);
expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('might not be a valid US number. Prepending +1.'));
expect(mockSend).toHaveBeenCalledWith(
expect.objectContaining({
to: '+15551234567', // Expect normalized number
})
);
});
// Add more tests for edge cases: missing image URL, missing sender number
})14. Frequently Asked Questions (FAQ)
<!-- DEPTH: FAQ lacks troubleshooting decision tree and common error codes (Priority: Medium) -->How do I send MMS messages with Vonage in RedwoodJS?
Send MMS messages with Vonage in RedwoodJS by: 1) Installing the @vonage/messages SDK in your API workspace (yarn workspace api add @vonage/messages), 2) Creating a Vonage Application with Messages capability enabled and generating a private key, 3) Creating a RedwoodJS service that initializes the Vonage Messages client with your Application ID and private key, 4) Creating a GraphQL mutation that accepts recipient phone number and image URL, and 5) Using vonageClient.send() with an MMSImage payload. The Vonage Messages client uses JWT authentication and requires a US-based 10DLC, Toll-Free, or Short Code number.
What file formats and sizes does Vonage MMS support?
Vonage MMS supports .jpg, .jpeg, .png, and .gif image formats. The maximum file size is 600KB recommended for reliable delivery. For optimal quality without compression, use 200KB or smaller. Short Codes can support up to 1MB, but this may compromise delivery quality and success rates. Images must be publicly accessible URLs – private URLs or those requiring authentication will fail. Source: Vonage MMS File Types and Vonage MMS File Size.
What Node.js and RedwoodJS versions are required for this implementation?
RedwoodJS v8.x (latest as of January 2025) requires Node.js v20.17.0 or later and Yarn v4.1.1 or later. Earlier RedwoodJS versions (v7.0.0+) work with Node.js >=20.x. Verify your versions with node --version and yarn --version. The @vonage/messages SDK (v1.20.3 as of January 2025) is compatible with these Node.js versions. RedwoodJS uses Node.js for its API side where the Vonage integration runs.
How do I fix "401 Unauthorized" errors with Vonage Messages API?
Fix "401 Unauthorized" errors by verifying: 1) Your VONAGE_APPLICATION_ID is correct (not your API Key), 2) The private.key file is in the correct location specified by VONAGE_PRIVATE_KEY_PATH, 3) The private key content matches exactly what was downloaded (no extra spaces or line breaks), 4) Your Vonage phone number is linked to the Application in the Vonage Dashboard, 5) The Messages capability is enabled on your Application, and 6) The path.resolve() logic in your service correctly locates the key file. JWT authentication requires the Application ID and private key – API Key/Secret alone won't work for the Messages API.
Can I send MMS to international numbers with Vonage?
Vonage MMS primarily supports sending to US phone numbers only when using US-based 10DLC, Toll-Free, or Short Code numbers. International MMS capabilities are limited and may require different API configurations or number types. Check the Vonage Messages API documentation for current international MMS support. For international messaging, consider using WhatsApp, Viber, or other channels supported by the Vonage Messages API, which handle multimedia content globally.
<!-- GAP: Missing list of countries where MMS is supported (Type: Substantive) -->How do I handle MMS delivery failures in RedwoodJS?
Handle MMS delivery failures by: 1) Configuring status webhooks in your Vonage Application to receive delivery receipts (DLRs), 2) Creating a RedwoodJS function or GraphQL subscription to process webhook callbacks, 3) Storing message status in your database using Prisma, 4) Implementing retry logic for transient failures using RedwoodJS Background Jobs (available in v8+), and 5) Monitoring delivery rates in the Vonage Dashboard. MMS delivery isn't guaranteed – carriers may reject messages due to content filters, recipient device limitations, or network issues. Always provide fallback options like SMS for critical notifications.
<!-- GAP: Missing code example of webhook handler implementation (Type: Critical) -->What are the costs for sending MMS with Vonage?
Vonage charges per MMS message sent, with pricing varying by destination carrier and number type (10DLC, Toll-Free, Short Code). New accounts receive free credits for testing. Check your current balance and per-message rates in the Vonage API Dashboard. MMS is more expensive than SMS due to multimedia content. Monitor usage in production to prevent unexpected costs – implement rate limiting, user quotas, and cost alerts. Consider the Vonage Pricing page for detailed pricing by country and message type.
<!-- GAP: Missing specific price ranges and cost comparison table (Type: Substantive) -->Do I need 10DLC registration to send MMS in the US?
Yes, 10DLC (10-Digit Long Code) registration is required to send MMS and SMS to US recipients using standard long code numbers. As of January 2025, all 10DLC campaigns are automatically MMS-enabled. Registration requires: 1) Brand registration with The Campaign Registry, 2) Campaign registration describing your use case (minimum 40 characters), 3) Sample messages, and 4) Reseller ID (required from January 2025). The process can take several days to weeks. Alternatively, use Toll-Free Numbers or Short Codes, which have different registration requirements. See Vonage 10DLC Documentation for complete registration guides.
<!-- GAP: Missing timeline estimates and approval rates for 10DLC registration (Type: Substantive) -->