Developer Guide: Sending Sinch MMS with RedwoodJS and Node.js
This guide provides a step-by-step walkthrough for integrating Sinch's Multimedia Messaging Service (MMS) capabilities into your RedwoodJS application. You will build a feature that allows users to send MMS messages containing text and an image URL via the Sinch Conversation API.
Project Overview and Goals
What We're Building:
We will add functionality to a RedwoodJS application enabling it to send MMS messages through the Sinch Conversation API. This involves:
- Setting up a RedwoodJS project.
- Configuring Sinch API credentials securely.
- Creating a backend service and GraphQL mutation to handle MMS sending requests.
- Developing a frontend component to capture recipient details, message text, and media URL.
- Implementing a reusable library function to interact with the Sinch Conversation API.
- Adding basic logging and error handling.
Problem Solved:
This guide addresses the need to programmatically send rich media content (images) alongside text messages to users directly from a web application, leveraging Sinch's robust 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. We use v2 for broader compatibility within common RedwoodJS setups.
System Architecture:
+-----------------+ +-------------------+ +-----------------+ +-------------+
| User Browser | ---> | RedwoodJS Web Side| ---> | RedwoodJS API Side| ---> | Sinch API |
| (React Frontend)| | (GraphQL Mutation)| | (Service + Lib) | | (Conv. API) |
+-----------------+ +-------------------+ +-----------------+ +-------------+
^ | | |
| | | Log v
| | v +----------+
+---------------------------+------------------ (Prisma) ----------->| Database |
+----------+
- The user interacts with the React frontend (RedwoodJS Web Side).
- Submitting the MMS form triggers a GraphQL mutation.
- The RedwoodJS API Side receives the mutation.
- The API service calls a library function (
lib/sinch.ts
). - The library function makes an HTTPS request to the Sinch Conversation API endpoint.
- (Optional) The API service logs the transaction details to the database via Prisma.
- Sinch processes the request and delivers the MMS message.
Prerequisites:
- Node.js: Version 18.x or later.
- Yarn: Version 1.x (Classic). RedwoodJS uses Yarn by default.
- RedwoodJS CLI: Install globally:
npm install -g @redwoodjs/cli
- 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:
By the end of this tutorial, you will have a functional RedwoodJS application capable of sending an MMS message (text + image from URL) to a specified phone number using the Sinch Conversation API. You'll also have a basic logging mechanism in place.
1. Setting up the RedwoodJS Project
Let's start by creating a new RedwoodJS project and installing necessary dependencies.
-
Create RedwoodJS App: Open your terminal and run the following command. We'll use TypeScript for this guide.
yarn create redwood-app --typescript ./redwood-sinch-mms cd redwood-sinch-mms
-
Install Dependencies: We need
node-fetch
to make requests from our API service to the Sinch API. Navigate to theapi
workspace and installnode-fetch
version 2.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. While modern RedwoodJS can support ESM with adjustments, using Version 2 provides robustfetch
functionality with simpler integration for this guide. -
Database Setup (Optional Logging): We'll create a simple Prisma model to log outgoing MMS messages. Open
api/db/schema.prisma
:- Remove the example
UserExample
model. - Add the
MessageLog
model:
// 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? }
- Remove the example
-
Apply Database Migration: Create and apply the migration to your database (this will create a SQLite file by default).
yarn rw prisma migrate dev --name initial-setup-mms
This command generates SQL migration files and applies them, creating the
MessageLog
table. -
Generate SDL and Service for Logging (Optional): If you want GraphQL access to the logs (not strictly required for sending), you can scaffold it:
yarn rw g sdl MessageLog # No service needed just for logging within other services initially
2. Configuring Sinch Conversation API
Before writing code, you need to set up your Sinch account and obtain the necessary API credentials and an MMS-enabled number.
- Log in to Sinch Dashboard: Access your Sinch Customer Dashboard.
- Navigate to Conversation API: In the left-hand menu, find ""Products"" -> ""Conversation API"".
- 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"".
- 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.
- 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. Setting Up Environment Variables
Store your Sinch credentials securely using environment variables. Open the .env
file in the root of your RedwoodJS project and add the following lines, replacing the placeholder values with your actual credentials:
# .env
# Database URL (already present)
DATABASE_URL="file:./dev.db"
# Sinch Conversation API Credentials
# Found in your Sinch Dashboard -> Conversation API -> Apps -> Your App
SINCH_PROJECT_ID="YOUR_PROJECT_ID"
SINCH_APP_ID="YOUR_APP_ID"
# Generated under App -> Authentication
SINCH_ACCESS_KEY="YOUR_ACCESS_KEY"
SINCH_ACCESS_SECRET="YOUR_ACCESS_SECRET"
# Your MMS-enabled number from the Sinch SMS Service Plan linked to the Conv API App
SINCH_MMS_NUMBER="+1xxxxxxxxxx"
# Base URL for the region where your Conversation API App was created (e.g., us or eu)
# Use https://us.conversation.api.sinch.com or https://eu.conversation.api.sinch.com
SINCH_REGION_URL="https://us.conversation.api.sinch.com"
Important: Restart your RedwoodJS development server after modifying the .env
file for the changes to take effect:
yarn rw dev
4. Implementing Core Functionality (Backend)
We'll create a reusable library function to handle the Sinch API communication and then a RedwoodJS service and SDL to expose this functionality via GraphQL.
-
Create the Sinch Library Function: Create a new file
api/src/lib/sinch.ts
. This function will encapsulate the logic for sending the MMS message via the Conversation API.// 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
).
-
Generate MMS Service and SDL: Use the Redwood CLI to generate the boilerplate for our service and GraphQL schema definition.
yarn rw g service mms --no-crud --no-tests
Note: We add
--no-crud
and--no-tests
as we're defining a custom mutation, not standard CRUD operations, and will skip writing automated tests for brevity in this guide (though highly recommended for production). -
Define the GraphQL Mutation (SDL): Open
api/src/graphql/mms.sdl.ts
and define thesendMms
mutation.// 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:
- We define a response type
MmsSendResponse
to standardize the mutation's return value. - The
sendMms
mutation takesrecipient
, optionaltext
, and requiredmediaUrl
as arguments. @requireAuth
directive ensures only authenticated users (if using Redwood Auth) can trigger this mutation. Adjust if your auth setup differs.
- We define a response type
-
Implement the MMS Service: Open
api/src/services/mms/mms.ts
and implement thesendMms
function. This function will call our library function and handle logging to the database.// 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:
- It imports necessary modules: GraphQL types, Prisma client (
db
), logger, and oursendSinchMms
function. - It retrieves the
sender
number from environment variables. - It performs basic validation. Suggests using libraries like Zod/Yup for enhancement.
- It wraps the call to
sendSinchMms
in atry...catch
block. - On success, it logs the attempt with status 'SENT' and the
sinchMessageId
to theMessageLog
table. - On failure, it logs the attempt with status 'FAILED' and the error message.
- It returns the standardized
MmsSendResponse
object.
- It imports necessary modules: GraphQL types, Prisma client (
5. Building the Frontend Component
Now, let's create a simple page on the web side to trigger our sendMms
mutation.
-
Generate a Page:
yarn rw g page SendMmsPage /send-mms
-
Implement the Page Component: Open
web/src/pages/SendMmsPage/SendMmsPage.tsx
and add a form to collect the MMS details.// 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 thesendMms
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 thesendMms
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
).
- Imports necessary components from RedwoodJS (
6. Verification and Testing
Now, let's test the implementation.
-
Start the Dev Server: If it's not already running:
yarn rw dev
-
Navigate to the Page: Open your browser and go to
http://localhost:8910/send-mms
. -
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.
- Recipient Phone: Enter a valid mobile number (that you can check) in E.164 format (e.g.,
-
Submit the Form: Click ""Send MMS"".
-
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 fromapi/src/lib/sinch.ts
andapi/src/services/mms/mms.ts
indicating the attempt and success/failure. - Database (Optional): If you implemented logging, check the
MessageLog
table (you can useyarn 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. Error Handling and Logging
- 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 inlib/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, consider configuring 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. Security Considerations
- 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. Troubleshooting and Caveats
- Credentials Error (401 Unauthorized): Double-check
SINCH_ACCESS_KEY
andSINCH_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 specifiedSINCH_PROJECT_ID
andSINCH_APP_ID
. - Invalid Request (400 Bad Request): Check the API console logs for details from Sinch. Common causes:
- Incorrect
recipient
orsender
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
).
- Incorrect
- Region Mismatch: Ensure
SINCH_REGION_URL
matches the region where your Conversation API App was created (us
oreu
). 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 installednode-fetch
v3 or later and encounterrequire 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. Deployment and CI
(Content for this section was missing in the original input)