code examples

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

How to Send MMS with Sinch and RedwoodJS: Complete Node.js Integration Tutorial

Learn how to send MMS messages using Sinch Conversation API in RedwoodJS. Step-by-step guide with GraphQL, Prisma, and Node.js code examples for multimedia messaging.

Send MMS with Sinch, RedwoodJS, and Node.js

Learn how to send MMS (Multimedia Messaging Service) using the Sinch Conversation API in your RedwoodJS application. This complete Node.js tutorial shows you how to integrate multimedia messaging with text and images through GraphQL mutations and Prisma database logging. Follow this step-by-step guide to implement MMS functionality from project setup through testing, including secure API configuration, reusable service functions, and comprehensive error handling.

This MMS integration enables real-world use cases like appointment confirmations with QR codes, e-commerce order updates with product images, visual receipts with branded logos, marketing campaigns with promotional content, and service notifications with instructional diagrams.

What You'll Build: RedwoodJS MMS Application

Build a complete MMS messaging application using RedwoodJS, Node.js, and the Sinch Conversation API:

  1. Set up a RedwoodJS project with TypeScript support
  2. Configure Sinch API credentials and MMS-enabled phone numbers
  3. Create a backend GraphQL service to handle MMS sending requests
  4. Build a frontend React component for message composition
  5. Implement a reusable Sinch API library function
  6. Add database logging and comprehensive error handling

Why Send MMS Messages:

MMS (Multimedia Messaging Service) lets you send rich media content—images, videos, and text—directly to mobile phones. Common applications include:

  • Appointment confirmations: Send QR codes, appointment cards, or location maps
  • E-commerce notifications: Include product images with order and shipping updates
  • Visual receipts: Deliver branded receipts with logos and itemized images
  • Marketing campaigns: Share promotional images, coupons, and special offers
  • Service updates: Attach instructional diagrams, photos, or videos

For SMS-only messaging without multimedia, see our Node.js SMS guide or learn about E.164 phone number formatting for international messaging.

Understanding the RedwoodJS MMS Integration

What You're Building:

Add functionality to a RedwoodJS application enabling it to send MMS messages through the Sinch Conversation API. This involves:

  1. Setting up a RedwoodJS project
  2. Configuring Sinch API credentials securely
  3. Creating a backend service and GraphQL mutation to handle MMS sending requests
  4. Developing a frontend component to capture recipient details, message text, and media URL
  5. Implementing a reusable library function to interact with the Sinch Conversation API
  6. Adding basic logging and error handling

Problem Solved:

Programmatically send rich media content (images) alongside text messages to users directly from a web application, leveraging Sinch's communication infrastructure.

Technologies Used:

  • RedwoodJS: A full-stack JavaScript/TypeScript framework for building modern web applications. It provides structure, conventions, and tools (like GraphQL integration, Prisma ORM, and scaffolding) that accelerate development.
  • Node.js: The runtime environment for the RedwoodJS API side.
  • Sinch Conversation API: A unified API from Sinch for sending messages across various channels, including MMS.
  • Prisma: A next-generation ORM used by RedwoodJS for database interactions.
  • GraphQL: The query language used for API communication between the RedwoodJS frontend and backend.
  • node-fetch (v2): A module to make HTTP requests from the Node.js backend to the Sinch API. Use v2 for broader compatibility within common RedwoodJS setups.

System Architecture:

text
+-----------------+      +-------------------+      +-----------------+      +-------------+
|   User Browser  | ---> | RedwoodJS Web Side| ---> | RedwoodJS API Side| ---> |  Sinch API  |
| (React Frontend)|      | (GraphQL Mutation)|      | (Service + Lib) |      | (Conv. API) |
+-----------------+      +-------------------+      +-----------------+      +-------------+
       ^                           |                      |                      |
       |                           |                      | Log                  v
       |                           |                      v                +----------+
       +---------------------------+------------------ (Prisma) ----------->| Database |
                                                                           +----------+
  1. The user interacts with the React frontend (RedwoodJS Web Side).
  2. Submitting the MMS form triggers a GraphQL mutation.
  3. The RedwoodJS API Side receives the mutation.
  4. The API service calls a library function (lib/sinch.ts).
  5. The library function makes an HTTPS request to the Sinch Conversation API endpoint.
  6. (Optional) The API service logs the transaction details to the database via Prisma.
  7. Sinch processes the request and delivers the MMS message.

Prerequisites:

  • Node.js: Requires Node.js v20+ (minimum). Node.js v22 LTS recommended (Active LTS from October 29, 2024 until October 2025; Maintenance LTS until April 2027). Download from nodejs.org. Note: Node v21.0.0+ may cause incompatibility with some deploy targets like AWS Lambdas.
  • Yarn: Version 1.22.21 or higher (Classic). RedwoodJS uses Yarn by default.
  • RedwoodJS CLI: Install globally: npm install -g @redwoodjs/cli. Latest RedwoodJS version: 8.8.1 (as of January 2025).
  • Sinch Account: A registered account at Sinch.com.
  • MMS-Enabled Sinch Number: A phone number procured through Sinch and configured for MMS within a Conversation API App.

Expected Outcome:

Build a functional RedwoodJS application that sends MMS messages (text + image from URL) to specified phone numbers using the Sinch Conversation API, with basic logging.


1. Set Up Your RedwoodJS Project for MMS

Create a new RedwoodJS project with TypeScript and install the dependencies needed to send MMS messages via the Sinch Conversation API.

  1. Create RedwoodJS App: Open your terminal and run the following command. This guide uses TypeScript.

    bash
    yarn create redwood-app --typescript ./redwood-sinch-mms
    cd redwood-sinch-mms
  2. Install Dependencies: Install node-fetch to make requests from your API service to the Sinch API. Navigate to the api workspace and install node-fetch version 2.

    bash
    yarn workspace api add node-fetch@2

    Why node-fetch@2? Version 3+ uses ESM modules by default, which can sometimes require extra configuration in primarily CommonJS environments like the default RedwoodJS API side. Version 2 provides robust fetch functionality with simpler integration for this guide.

  3. Database Setup (Optional Logging): Create a simple Prisma model to log outgoing MMS messages. Open api/db/schema.prisma:

    • Remove the example UserExample model.
    • Add the MessageLog model:
    prisma
    // api/db/schema.prisma
    
    datasource db {
      provider = "sqlite" // Or postgresql, mysql
      url      = env("DATABASE_URL")
    }
    
    generator client {
      provider      = "prisma-client-js"
      binaryTargets = "native"
    }
    
    model MessageLog {
      id             String   @id @default(cuid())
      createdAt      DateTime @default(now())
      recipient      String
      sender         String
      messageText    String?
      mediaUrl       String?
      sinchMessageId String? // Store the ID returned by Sinch
      status         String   // e.g., 'SENT', 'FAILED'
      errorDetails   String?
    }
  4. Apply Database Migration: Create and apply the migration to your database (creates a SQLite file by default).

    bash
    yarn rw prisma migrate dev --name initial-setup-mms

    This command generates SQL migration files and applies them, creating the MessageLog table.

  5. Generate SDL and Service for Logging (Optional): If you want GraphQL access to the logs (not strictly required for sending), scaffold it:

    bash
    yarn rw g sdl MessageLog
    # No service needed just for logging within other services initially

2. Configure Sinch API Credentials for MMS

Set up your Sinch account, create a Conversation API app, and obtain the API credentials and MMS-enabled phone number required to send multimedia messages.

  1. Log in to Sinch Dashboard: Access your Sinch Customer Dashboard.

  2. Navigate to Conversation API: In the left-hand menu, find "Products" → "Conversation API".

  3. Create a Conversation API App:

    • Click on "Apps".
    • Click "Create app".
    • Give your app a name (e.g., "Redwood MMS Sender").
    • Select the appropriate region (US or EU). Note down this region.
    • Click "Create app".
  4. Obtain Credentials: Once the app is created, you'll land on its configuration page. Find the following:

    • App ID: Located near the top. Copy this value.
    • Project ID: Also near the top, usually linked to your account. Copy this value.
    • Access Keys: Scroll down to the "Authentication" section. Click "Generate access key".
      • Give the key a name (e.g., "RedwoodAppKey").
      • Click "Generate".
      • Crucially: Copy both the Access Key and the Access Secret immediately. The secret is only shown once. Store them securely.
  5. Configure MMS Channel:

    • Within your Conversation API App settings, scroll down to "Channels".
    • Find the "MMS" channel and click "Set up".
    • You will likely need to associate a Service Plan ID from your Sinch SMS product settings. This Service Plan must have an MMS-enabled number assigned to it.
      • Go to "Products" → "SMS".
      • Under "APIs", note your SERVICE_PLAN_ID.
      • Ensure a US/CA number capable of MMS is assigned to this service plan (you might need to purchase one under "Numbers").
    • Enter the SERVICE_PLAN_ID in the MMS channel configuration within the Conversation API App.
    • Save the channel configuration. Note down the MMS-enabled number associated with this service plan – this will be your sender number.

Note: Sinch Dashboard UI may change over time. Refer to the official Sinch Conversation API documentation if the paths described here differ.


3. Build the Sinch MMS Service Library

Create a reusable Node.js library function to send MMS via the Sinch Conversation API, then build a RedwoodJS service and GraphQL schema to expose this functionality to your frontend.

  1. Create the Sinch Library Function: Create a new file api/src/lib/sinch.ts. This function encapsulates the logic for sending the MMS message via the Conversation API.

    typescript
    // api/src/lib/sinch.ts
    import fetch from 'node-fetch' // Use node-fetch v2
    
    import { logger } from 'src/lib/logger' // Redwood's built-in logger
    
    // Type definition for the expected arguments
    interface SendMmsArgs {
      recipient: string // E.164 format, e.g., +15551234567
      sender: string    // Your Sinch MMS number, E.164 format
      text?: string     // Optional text part of the message
      mediaUrl: string  // Publicly accessible URL of the image
    }
    
    // Type definition for a simplified Sinch API success response
    interface SinchSuccessResponse {
      message_id: string
      // Add other relevant fields if needed
    }
    
    // Type definition for a Sinch API error response
    interface SinchErrorResponse {
      error: {
        code: number
        message: string
        details?: any
      }
    }
    
    export const sendSinchMms = async ({
      recipient,
      sender,
      text,
      mediaUrl,
    }: SendMmsArgs): Promise<SinchSuccessResponse> => {
      const projectId = process.env.SINCH_PROJECT_ID
      const appId = process.env.SINCH_APP_ID
      const accessKey = process.env.SINCH_ACCESS_KEY
      const accessSecret = process.env.SINCH_ACCESS_SECRET
      const regionUrl = process.env.SINCH_REGION_URL
    
      if (!projectId || !appId || !accessKey || !accessSecret || !regionUrl || !sender) {
        logger.error('Sinch environment variables are not fully configured.')
        throw new Error('Server configuration error: Sinch credentials missing.')
      }
    
      // Ensure recipient and sender are in E.164 format
      if (!/^\+\d+$/.test(recipient) || !/^\+\d+$/.test(sender)) {
        logger.error(
          { recipient, sender },
          'Invalid phone number format. Requires E.164.'
        )
        throw new Error('Invalid phone number format. Use E.164 (e.g., +15551234567).')
      }
    
      const apiUrl = `${regionUrl}/v1/projects/${projectId}/messages:send`
    
      // Construct the message payload according to Sinch Conversation API spec for MMS
      const payload = {
        app_id: appId,
        recipients: [{ contact_id: recipient }], // Use contact_id for phone numbers
        message: {
          media_message: {
            url: mediaUrl,
            // You could add thumbnail_url here if available
          },
          // Include text message part if provided
          ...(text && { text_message: { text } }),
        },
        channel_priority_order: ['MMS'], // Explicitly request MMS
        // Specify the sender ID via channel properties for MMS
        channel_properties: {
          MMS: {
            sender_number: sender,
          },
        },
      }
    
      // Basic Authentication: Base64 encode "ACCESS_KEY:ACCESS_SECRET"
      const authToken = Buffer.from(`${accessKey}:${accessSecret}`).toString('base64')
    
      logger.info({ recipient, sender, mediaUrl }, 'Attempting to send Sinch MMS')
    
      try {
        const response = await fetch(apiUrl, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            Authorization: `Basic ${authToken}`,
          },
          body: JSON.stringify(payload),
        })
    
        const responseBody = await response.json()
    
        if (!response.ok) {
          const errorInfo = responseBody as SinchErrorResponse
          logger.error(
            { status: response.status, error: errorInfo, payload },
            'Sinch API request failed'
          )
          throw new Error(
            `Sinch API Error (${response.status}): ${
              errorInfo?.error?.message || 'Unknown error'
            }`
          )
        }
    
        logger.info(
          { response: responseBody as SinchSuccessResponse },
          'Sinch MMS sent successfully'
        )
        return responseBody as SinchSuccessResponse
      } catch (error) {
        logger.error({ error, payload }, 'Failed to send Sinch MMS')
        // Re-throw the error to be caught by the service
        throw error instanceof Error ? error : new Error('An unknown error occurred during MMS sending.')
      }
    }

    Key Points:

    • Environment variables are checked.
    • Basic E.164 phone number format validation is added.
    • The API endpoint URL is constructed dynamically using the region and project ID.
    • The payload structure matches the Conversation API requirements for sending an MMS (media_message, contact_id, channel_properties.MMS.sender_number).
    • Basic Authentication header is correctly generated using the Access Key and Secret.
    • Error handling checks the response status and logs relevant information.
    • Success returns the parsed response (which should include message_id).
  2. Generate MMS Service and SDL: Use the Redwood CLI to generate the boilerplate for the service and GraphQL schema definition.

    bash
    yarn rw g service mms --no-crud --no-tests

    Note: Add --no-crud and --no-tests as you're defining a custom mutation, not standard CRUD operations. Skip automated tests for brevity in this guide (though highly recommended for production).

  3. Define the GraphQL Mutation (SDL): Open api/src/graphql/mms.sdl.ts and define the sendMms mutation.

    graphql
    // api/src/graphql/mms.sdl.ts
    export const schema = gql`
      type MmsSendResponse {
        success: Boolean!
        messageId: String
        error: String
      }
    
      type Mutation {
        """Sends an MMS message via Sinch"""
        sendMms(recipient: String!, text: String, mediaUrl: String!): MmsSendResponse! @requireAuth
        # Or use @skipAuth if you handle auth differently, but @requireAuth is safer
      }
    `

    Explanation:

    • Define a response type MmsSendResponse to standardize the mutation's return value.
    • The sendMms mutation takes recipient, optional text, and required mediaUrl as arguments.
    • @requireAuth directive ensures only authenticated users (if using Redwood Auth) can trigger this mutation. Adjust if your auth setup differs.
  4. Implement the MMS Service: Open api/src/services/mms/mms.ts and implement the sendMms function. This function calls the library function and handles logging to the database.

    typescript
    // api/src/services/mms/mms.ts
    import type { MutationResolvers } from 'types/graphql'
    
    import { db } from 'src/lib/db' // Prisma client
    import { logger } from 'src/lib/logger'
    import { sendSinchMms } from 'src/lib/sinch' // Our Sinch library function
    
    export const sendMms: MutationResolvers['sendMms'] = async ({
      recipient,
      text,
      mediaUrl,
    }) => {
      const sender = process.env.SINCH_MMS_NUMBER
      let sinchMessageId: string | null = null
      let status = 'FAILED' // Default status
      let errorDetails: string | null = null
    
      // Basic input validation (can be enhanced)
      if (!recipient || !mediaUrl) {
        errorDetails = 'Recipient and Media URL are required.'
        logger.error(errorDetails)
        // Log failure attempt immediately
        try {
          await db.messageLog.create({
            data: { recipient, sender: sender || 'UNKNOWN', messageText: text, mediaUrl, status, errorDetails },
          })
        } catch (dbError) {
          logger.error({ dbError }, 'Failed to log initial MMS failure.')
        }
        return { success: false, error: errorDetails }
      }
    
      if (!sender) {
         errorDetails = 'Server configuration error: Sender number not set.'
         logger.error(errorDetails)
          // Log failure attempt
        try {
          await db.messageLog.create({
            data: { recipient, sender: 'UNKNOWN', messageText: text, mediaUrl, status, errorDetails },
          })
        } catch (dbError) {
          logger.error({ dbError }, 'Failed to log sender configuration error.')
        }
         return { success: false, error: errorDetails }
      }
    
      try {
        logger.info( { recipient, text, mediaUrl }, 'Processing sendMms mutation')
    
        const result = await sendSinchMms({
          recipient,
          sender,
          text,
          mediaUrl,
        })
    
        sinchMessageId = result.message_id
        status = 'SENT' // Update status on success
    
        logger.info( { sinchMessageId }, 'MMS successfully sent via Sinch')
    
        // Log success
        await db.messageLog.create({
          data: {
            recipient,
            sender,
            messageText: text,
            mediaUrl,
            sinchMessageId,
            status,
          },
        })
    
        return { success: true, messageId: sinchMessageId }
    
      } catch (error) {
        logger.error({ error }, 'sendMms mutation failed')
        errorDetails = error instanceof Error ? error.message : 'An unknown error occurred.'
        status = 'FAILED' // Ensure status is FAILED
    
        // Log failure
        try {
           await db.messageLog.create({
             data: {
               recipient,
               sender, // Sender should be defined if we reached this point
               messageText: text,
               mediaUrl,
               sinchMessageId, // Will be null
               status,
               errorDetails,
             },
           })
        } catch (dbError) {
           logger.error({ dbError }, 'Failed to log MMS sending error.')
        }
    
        return { success: false, messageId: null, error: errorDetails }
      }
    }

    Explanation:

    • Imports necessary modules: GraphQL types, Prisma client (db), logger, and the sendSinchMms function.
    • Retrieves the sender number from environment variables.
    • Performs basic validation. Consider using libraries like Zod or Yup for enhancement.
    • Wraps the call to sendSinchMms in a try...catch block.
    • On success, logs the attempt with status 'SENT' and the sinchMessageId to the MessageLog table.
    • On failure, logs the attempt with status 'FAILED' and the error message.
    • Returns the standardized MmsSendResponse object.

4. Create the GraphQL MMS API

Verify your GraphQL schema and service are configured correctly, then test the MMS mutation using the RedwoodJS GraphQL Playground.

  1. Check the Service: Open api/src/services/mms/mms.ts and verify the sendMms function is correctly implemented.

  2. Check the SDL: Open api/src/graphql/mms.sdl.ts and verify the sendMms mutation is defined.

  3. Test the Mutation: Use the RedwoodJS GraphQL Playground (available at http://localhost:8910/graphql when the dev server is running) to test the sendMms mutation. Example query:

    graphql
    mutation {
      sendMms(recipient: "+15551234567", mediaUrl: "https://example.com/image.jpg") {
        success
        messageId
        error
      }
    }

5. Build the MMS Frontend Interface

Create a React page component with a form that lets users send MMS messages by triggering the GraphQL mutation.

  1. Generate a Page:

    bash
    yarn rw g page SendMmsPage /send-mms
  2. Implement the Page Component: Open web/src/pages/SendMmsPage/SendMmsPage.tsx and add a form to collect the MMS details.

    typescript
    // web/src/pages/SendMmsPage/SendMmsPage.tsx
    import { MetaTags, useMutation } from '@redwoodjs/web'
    import { toast, Toaster } from '@redwoodjs/web/toast'
    import {
      Form,
      TextField,
      TextAreaField, // Use TextArea for potentially longer text messages
      Submit,
      Label,
      FieldError,
      FormError, // Display general form errors
    } from '@redwoodjs/forms'
    
    // Define the GraphQL Mutation
    const SEND_MMS_MUTATION = gql`
      mutation SendMmsMutation(
        $recipient: String!
        $text: String
        $mediaUrl: String!
      ) {
        sendMms(recipient: $recipient, text: $text, mediaUrl: $mediaUrl) {
          success
          messageId
          error
        }
      }
    `
    
    const SendMmsPage = () => {
      const [sendMms, { loading, error }] = useMutation(SEND_MMS_MUTATION, {
        onCompleted: (data) => {
          if (data.sendMms.success) {
            toast.success(
              `MMS sent successfully! Message ID: ${data.sendMms.messageId}`
            )
            // Optionally reset the form here
          } else {
            toast.error(`Failed to send MMS: ${data.sendMms.error}`)
          }
        },
        onError: (error) => {
          // Handle network errors or GraphQL errors not caught by the resolver
          toast.error(`An error occurred: ${error.message}`)
        },
      })
    
      const onSubmit = (formData) => {
        // Basic client-side validation (can be more robust)
        if (!formData.recipient || !formData.mediaUrl) {
          toast.error('Recipient phone number and media URL are required.')
          return
        }
        // Ensure recipient starts with '+' for E.164 format hint
         if (!formData.recipient.startsWith('+')) {
          toast.error('Recipient phone number must be in E.164 format (e.g., +15551234567).');
          return;
        }
    
        sendMms({
          variables: {
            recipient: formData.recipient,
            text: formData.text,
            mediaUrl: formData.mediaUrl,
          },
        })
      }
    
      return (
        <>
          <MetaTags title="Send MMS" description="Send an MMS message via Sinch" />
          <Toaster toastOptions={{ className: 'rw-toast', duration: 6000 }} />
    
          <div className="rw-segment">
            <header className="rw-segment-header">
              <h2 className="rw-heading rw-heading-secondary">Send Sinch MMS</h2>
            </header>
            <div className="rw-segment-main">
              <div className="rw-form-wrapper">
                <Form onSubmit={onSubmit} error={error}>
                  {/* Display general form errors from the mutation hook */}
                   <FormError error={error} wrapperClassName="rw-form-error-wrapper" titleClassName="rw-form-error-title" listClassName="rw-form-error-list" />
    
                  <Label name="recipient" className="rw-label" errorClassName="rw-label rw-label-error">
                    Recipient Phone (E.164 format)
                  </Label>
                  <TextField
                    name="recipient"
                    placeholder="+15551234567"
                    className="rw-input"
                    errorClassName="rw-input rw-input-error"
                    validation={{ required: true, pattern: { value: /^\+\d+$/, message: 'Use E.164 format (e.g., +15551234567)'} }}
                  />
                  <FieldError name="recipient" className="rw-field-error" />
    
                  <Label name="text" className="rw-label" errorClassName="rw-label rw-label-error">
                    Message Text (Optional)
                  </Label>
                  <TextAreaField
                    name="text"
                    placeholder="Enter your message here..."
                    className="rw-input"
                    errorClassName="rw-input rw-input-error"
                    // No specific validation, as it's optional
                  />
                  <FieldError name="text" className="rw-field-error" />
    
                  <Label name="mediaUrl" className="rw-label" errorClassName="rw-label rw-label-error">
                    Media URL (Publicly Accessible Image)
                  </Label>
                  <TextField
                    name="mediaUrl"
                    placeholder="https://example.com/image.jpg"
                    className="rw-input"
                    errorClassName="rw-input rw-input-error"
                    validation={{
                      required: true,
                      pattern: { value: /^https?:\/\/.+/, message: 'Must be a valid URL' },
                     }}
                  />
                   <FieldError name="mediaUrl" className="rw-field-error" />
    
                  <div className="rw-button-group">
                    <Submit className="rw-button rw-button-blue" disabled={loading}>
                      {loading ? 'Sending...' : 'Send MMS'}
                    </Submit>
                  </div>
                </Form>
              </div>
            </div>
          </div>
        </>
      )
    }
    
    export default SendMmsPage

    Explanation:

    • Imports necessary components from RedwoodJS (MetaTags, useMutation, toast, Form, form fields).
    • Defines the SEND_MMS_MUTATION matching the SDL.
    • Uses the useMutation hook to get the sendMms function and loading/error states.
    • onCompleted handles successful or failed responses from the backend mutation, displaying toasts.
    • onError handles network-level or unexpected GraphQL errors.
    • The onSubmit handler performs basic client-side checks and calls the sendMms function with form data.
    • Standard RedwoodJS form components (TextField, TextAreaField, Submit, Label, FieldError, FormError) are used for the UI and basic validation.
    • The submit button is disabled while the mutation is in progress (loading).

6. Test Sending MMS Messages

Test your complete MMS integration by sending a multimedia message with text and an image to a real phone number.

  1. Start the Dev Server: If it's not already running:

    bash
    yarn rw dev
  2. Navigate to the Page: Open your browser and go to http://localhost:8910/send-mms.

  3. Fill the Form:

    • Recipient Phone: Enter a valid mobile number (that you can check) in E.164 format (e.g., +15559876543).
    • Message Text: (Optional) Enter some text.
    • Media URL: Provide a publicly accessible URL to a .jpg, .png, or .gif image. A simple test URL: https://www.gravatar.com/avatar/?d=mp (yields a generic person icon). Ensure the URL works directly in your browser.
  4. Submit the Form: Click "Send MMS".

  5. Check for Results:

    • Toast Notification: You should see a success or error toast message on the page.
    • Recipient's Phone: Check the device associated with the recipient number. You should receive an MMS containing the image and text (delivery times can vary).
    • Developer Console (API): Look at the terminal where yarn rw dev is running. You should see logs from api/src/lib/sinch.ts and api/src/services/mms/mms.ts indicating the attempt and success/failure.
    • Database (Optional): If you implemented logging, check the MessageLog table (you can use yarn rw prisma studio to open a GUI). You should see a record for the attempt.
    • Sinch Dashboard: Log in to the Sinch dashboard, navigate to your Conversation API App, and check the logs/analytics section. You should see evidence of the API call.

7. Implement Error Handling and Logging

Understand the error handling patterns and logging strategies for production MMS applications.

  • API Errors: The lib/sinch.ts function attempts to parse error messages from Sinch's API response. These are logged and propagated to the service, then to the frontend via the mutation response. Common errors include invalid credentials (401), malformed requests (400), or number provisioning issues.
  • Network Errors: node-fetch might throw errors for network issues (e.g., DNS resolution failure, timeouts). These are caught in lib/sinch.ts and the service.
  • Configuration Errors: The library function checks for missing environment variables. The service checks for the sender number.
  • Logging: Redwood's default logger is used on the API side. For production, configure more robust logging (e.g., sending logs to a dedicated service, adjusting log levels). Check the RedwoodJS Logging documentation.
  • Retry Mechanisms: This guide doesn't implement automatic retries. For production, consider adding a retry strategy (e.g., using a queue like Faktory or BullMQ with exponential backoff) for transient network errors or specific Sinch API errors (like rate limiting – 429).

8. Secure Your MMS Application

Implement security best practices to protect API credentials, validate user input, and prevent abuse.

  • API Keys: Never commit your .env file containing secrets to version control. Use environment variable management provided by your hosting platform for production.
  • Input Validation: The current validation is basic. Implement more robust validation on both the frontend and backend (using libraries like Zod or Yup within the service) to sanitize inputs and prevent injection attacks. Validate phone number formats strictly. Validate the mediaUrl to prevent Server-Side Request Forgery (SSRF) if the URL is user-provided and processed further on the backend (less critical here as Sinch fetches it, but good practice).
  • Authorization: The @requireAuth directive is used. Ensure your RedwoodJS authentication is properly configured to protect the mutation endpoint.
  • Rate Limiting: Implement rate limiting on the API endpoint (using middleware or tools like express-rate-limit adapted for Redwood services) to prevent abuse. Check Sinch API rate limits.

9. Troubleshoot Common MMS Issues

Resolve common problems with Sinch API authentication, phone number formatting, and media URL accessibility.

  • Credentials Error (401 Unauthorized): Double-check SINCH_ACCESS_KEY and SINCH_ACCESS_SECRET in your .env file. Ensure they are correctly copied and the .env file is loaded (restart dev server). Verify you are using the correct key pair for the specified SINCH_PROJECT_ID and SINCH_APP_ID.
  • Invalid Request (400 Bad Request): Check the API console logs for details from Sinch. Common causes:
    • Incorrect recipient or sender format (must be E.164).
    • Invalid or inaccessible mediaUrl. The URL must be publicly reachable by Sinch's servers. Private network URLs won't work.
    • Malformed JSON payload (check lib/sinch.ts).
    • Missing required fields (e.g., app_id, recipients, media_message.url).
  • Region Mismatch: Ensure SINCH_REGION_URL matches the region where your Conversation API App was created (us or eu). Using the wrong region URL will result in errors (likely 404 Not Found or authentication failures).
  • Number Not Provisioned: The SINCH_MMS_NUMBER must be correctly assigned to the Service Plan linked in the Conversation API App's MMS channel settings, and it must be enabled for MMS traffic by Sinch (this is usually the case for US/Canada numbers suitable for A2P MMS). Contact Sinch support if you suspect provisioning issues.
  • Media URL Issues: Sinch needs to fetch the media from the provided URL. Ensure it's public, doesn't require authentication, and returns the correct Content-Type header (e.g., image/jpeg, image/png).
  • node-fetch v3+ Issues: If you accidentally installed node-fetch v3 or later and encounter require is not defined errors in the API, either switch back to v2 (yarn workspace api remove node-fetch && yarn workspace api add node-fetch@2) or configure your environment for ESM compatibility if preferred.
  • Delivery Delays: MMS delivery can sometimes take longer than SMS. Check Sinch logs for submission status.

10. Deploy Your MMS Application

Prepare your RedwoodJS MMS application for production deployment with proper environment configuration and monitoring.

For production deployment, you'll need to:

  • Configure environment variables on your hosting platform (Vercel, Netlify, Render, Railway, or AWS)
  • Set up secure credential storage using your platform's secret management
  • Configure your production database (PostgreSQL recommended over SQLite)
  • Enable monitoring and logging for message tracking
  • Implement rate limiting to prevent abuse

For detailed RedwoodJS deployment instructions, see the RedwoodJS deployment documentation. For SMS alternatives or comparison, explore our Twilio Node.js guides or learn about 10DLC registration for US commercial messaging.

Frequently Asked Questions

How to send MMS with RedwoodJS and Sinch

Integrate Sinch's MMS capabilities into your RedwoodJS app by setting up a project, configuring Sinch credentials, creating backend services and GraphQL mutations, and developing a frontend to handle user input and media URLs. This allows sending rich media content alongside text messages using Sinch's infrastructure. Refer to the step-by-step guide for detailed instructions and code examples.

What is Sinch Conversation API used for

The Sinch Conversation API is a unified interface for sending various types of messages, including MMS, across different channels. This guide uses it to send multimedia messages (text and images) from a RedwoodJS web application, leveraging Sinch's communication platform. It simplifies the process of integrating messaging features into your applications.

Why use node-fetch version 2 with RedwoodJS

Node-fetch version 2 offers better compatibility with RedwoodJS's common CommonJS setup, making integration simpler. While RedwoodJS can support the ESM modules used by node-fetch v3+, version 2 provides a robust solution without the added complexity of ESM configurations.

How to set up Sinch API credentials in RedwoodJS

Securely store your Sinch credentials (Project ID, App ID, Access Key, Access Secret, MMS Number, and Region URL) as environment variables in a `.env` file in your RedwoodJS project root. Ensure you never commit this file to version control. Restart your RedwoodJS dev server for changes to take effect.

What is the system architecture for sending Sinch MMS

The user interacts with the RedwoodJS frontend, triggering a GraphQL mutation. This mutation is received by the RedwoodJS API side, which calls a library function to interact with the Sinch Conversation API. Optionally, the API logs transaction details via Prisma. Sinch processes the request and delivers the MMS.

When should I use @requireAuth directive

The `@requireAuth` directive is recommended within your SDL for security. It ensures only authenticated users can trigger your `sendMms` mutation. Modify or remove this directive only if you use custom authentication not managed by standard Redwood Auth.

What is the RedwoodJS API side responsible for in MMS sending

The API side receives GraphQL mutations, calls the Sinch library function to send messages, and optionally logs messages to the database via Prisma. It manages the interaction between the frontend and the Sinch API, handling authentication, data processing, and logging.

Can I log MMS messages sent with Sinch

Yes, the guide demonstrates optional logging to a database using Prisma. A `MessageLog` model is created to store details like recipient, sender, message content, Sinch message ID, and status. This logging capability aids in tracking message delivery and troubleshooting.

How to handle Sinch API errors in RedwoodJS

The provided `lib/sinch.ts` function parses error messages from the Sinch API and logs them. These errors are propagated up to the service and then to the frontend as part of the mutation response, facilitating error handling and display to the user. Consider adding retry mechanisms for specific errors.

What are some common Sinch MMS errors

Common errors include invalid credentials (401 Unauthorized), malformed requests (400 Bad Request), and number provisioning issues. Issues with the media URL, such as inaccessibility or incorrect format, can also cause errors. Always refer to the Sinch documentation for comprehensive error codes and solutions.

How to validate recipient phone number format

The guide provides basic E.164 format validation, but for production, consider using dedicated libraries like Zod or Yup within your service. Enforce strict validation on both frontend and backend to prevent unexpected behavior and security risks. Ensure the number starts with a '+'.

Why does Sinch need a publicly accessible media URL

Sinch's servers need to directly access the image URL to include it in the MMS message. Private or internal network URLs are inaccessible to Sinch and will result in delivery failures. Always use a publicly accessible image URL.

What are security best practices for sending Sinch MMS

Crucially, never commit your `.env` file to version control. Use your hosting provider's environment variable management in production. Implement robust input validation, use the `@requireAuth` directive or equivalent authorization, and consider rate limiting on the API endpoint to prevent abuse.

How to troubleshoot Sinch MMS delivery delays

MMS delivery can sometimes experience delays. Consult Sinch's logs and dashboard for message submission status and details. If delays persist, contact Sinch support for assistance. Consider adding retry mechanisms within your application for improved reliability.