Sending MMS with RedwoodJS and MessageBird
This guide provides a step-by-step walkthrough for integrating MessageBird's MMS API into a RedwoodJS application. We'll build a backend service capable of sending MMS messages, including media attachments, track their basic status, and handle necessary configurations securely. This enables developers to add rich media messaging capabilities to their RedwoodJS projects, suitable for notifications, marketing, or user communication.
We will utilize RedwoodJS for its integrated structure (API and web sides), Prisma for database interactions, and the official MessageBird Node.js SDK. By the end, you'll have a functional GraphQL mutation to send MMS messages and a basic setup for tracking them.
Target Audience: Developers familiar with RedwoodJS and Node.js looking to implement programmatic MMS sending.
Prerequisites:
- Node.js (Check RedwoodJS docs for specific version compatibility)
- Yarn
- A RedwoodJS project set up. If you don't have one, create it:
yarn create redwood-app ./my-mms-app
- A MessageBird account.
- A MessageBird Access Key (Live or Test).
- An MMS-enabled virtual mobile number purchased from MessageBird (currently limited to US/Canada for sending).
System Architecture:
[User] -> [Redwood Web Frontend] -(GraphQL Mutation)-> [Redwood API Backend]
|
v
[MMS Service (api/src/services/mms)]
| - Initializes MessageBird Client
| - Calls MessageBird MMS API
| - Interacts with Database (Prisma)
v
[MessageBird API] -> [Carrier Network] -> [Recipient]
^
| (Status Webhook - Optional)
|
[Webhook Handler (api/src/functions/messagebirdWebhook)] <- [MessageBird API]
1. Project Setup and Configuration
First_ we need to install the MessageBird SDK and configure our RedwoodJS environment.
-
Navigate to your project directory:
cd my-mms-app
-
Install the MessageBird Node.js SDK: Install the SDK specifically in the
api
workspace.yarn workspace api add messagebird
-
Configure Environment Variables: RedwoodJS uses
.env
files for environment variables. Create or open the.env
file in the root of your project and add your MessageBird credentials and sender ID.# .env MESSAGEBIRD_ACCESS_KEY=YOUR_MESSAGEBIRD_API_ACCESS_KEY # Use an MMS-enabled number purchased from MessageBird (E.164 format) MESSAGEBIRD_ORIGINATOR=+1XXXXXXXXXX
MESSAGEBIRD_ACCESS_KEY
: Your API access key from the MessageBird Dashboard (Developers -> API access). Treat this like a password and keep it secret. Use a live key for actual sending or a test key for development without incurring charges or sending real messages.MESSAGEBIRD_ORIGINATOR
: The MMS-enabled phone number (in E.164 format, e.g.,+12025550187
) or potentially an approved Alphanumeric Sender ID (check MessageBird documentation and country restrictions – US/CA generally require a number for MMS). This number must be associated with your MessageBird account and enabled for MMS.
-
Ensure Environment Variables are Loaded: RedwoodJS automatically loads variables from
.env
intoprocess.env
. No further action is needed for them to be accessible in the API side.
2. Database Schema for Tracking Messages
While not strictly necessary just to send an MMS, storing message details is crucial for tracking, debugging, and displaying history. We'll use Prisma to define a simple model.
-
Define the Prisma Schema: Open
api/db/schema.prisma
and add the following model:// api/db/schema.prisma model MmsMessage { id String @id @default(cuid()) messageBirdId String? @unique // The ID returned by MessageBird API recipient String // Recipient phone number originator String // Sending number/ID subject String? body String? mediaUrls String[] // Store URLs as an array of strings status String @default(""pending"") // e.g., pending, sent, delivered, failed, received statusMessage String? // Store error messages or details createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }
messageBirdId
: Stores the unique ID returned by MessageBird upon successful sending, allowing us to correlate updates later. Marked as optional initially as it's only available after a successful API call.mediaUrls
: Prisma supports string arrays, suitable for storing the list of media URLs.status
: Tracks the message lifecycle. Initialized aspending
.
-
Apply Schema Changes (Database Migration): Run the following command to create and apply a new database migration:
yarn rw prisma migrate dev
Follow the prompts to name your migration (e.g.,
add_mms_message_model
).
3. Building the API Layer (GraphQL)
We'll create a GraphQL mutation to trigger the MMS sending process.
-
Generate GraphQL and Service Files: Use the Redwood generator to scaffold the necessary files for an
mms
resource.yarn rw g sdl mms --crud
This command creates:
api/src/graphql/mms.sdl.ts
: Defines the GraphQL schema types and operations.api/src/services/mms/mms.ts
: Contains the business logic (service functions).api/src/services/mms/mms.scenarios.ts
: For database seeding during tests.api/src/services/mms/mms.test.ts
: For writing unit tests.
-
Define the
sendMms
Mutation: Modifyapi/src/graphql/mms.sdl.ts
to define a specific mutation for sending MMS. We'll remove the generated CRUD operations for now and add oursendMms
mutation.# api/src/graphql/mms.sdl.ts export const schema = gql` type MmsMessage { id: String! messageBirdId: String recipient: String! originator: String! subject: String body: String mediaUrls: [String!]! status: String! statusMessage: String createdAt: DateTime! updatedAt: DateTime! } input SendMmsInput { recipient: String! subject: String body: String mediaUrls: [String!]! } type Mutation { sendMms(input: SendMmsInput!): MmsMessage! @requireAuth } `
- We define an
MmsMessage
type mirroring our Prisma model. SendMmsInput
specifies the required data: recipient number, optional subject/body, and an array of media URLs. At leastbody
ormediaUrls
must be provided in the actual MessageBird API call, which we'll enforce in the service.- The
sendMms
mutation takes this input and returns theMmsMessage
record created in our database. @requireAuth
: This directive ensures only authenticated users can call this mutation. Adjust or remove based on your application's auth requirements.
- We define an
4. Implementing Core Functionality (MMS Service)
Now, we implement the logic within the service function to interact with the MessageBird API.
-
Edit the MMS Service: Open
api/src/services/mms/mms.ts
and replace its contents with the following:// api/src/services/mms/mms.ts import type { MutationResolvers, SendMmsInput } from 'types/graphql' import { validate } from '@redwoodjs/api' import { logger } from 'src/lib/logger' import { db } from 'src/lib/db' // Import the specific function needed import { initClient } from 'messagebird' // Initialize the MessageBird client // Ensure MESSAGEBIRD_ACCESS_KEY is set in your .env file const messagebird = initClient(process.env.MESSAGEBIRD_ACCESS_KEY) interface MessageBirdMmsParams { originator: string recipients: string[] subject?: string body?: string mediaUrls?: string[] reference?: string // Optional: Client reference for status reports } export const sendMms: MutationResolvers['sendMms'] = async ({ input }: { input: SendMmsInput }) => { // 1. Validate Input validate(input.recipient, 'Recipient', { presence: true }) validate(input.mediaUrls, 'Media URLs', { presence: true }) // MMS requires media if (!input.body && (!input.mediaUrls || input.mediaUrls.length === 0)) { throw new Error('MMS must include either a body or at least one media URL.') } if (input.mediaUrls && input.mediaUrls.length > 10) { // MessageBird limit throw new Error('MMS cannot contain more than 10 media attachments.') } // Ensure originator is configured const originator = process.env.MESSAGEBIRD_ORIGINATOR if (!originator) { logger.error('MESSAGEBIRD_ORIGINATOR environment variable is not set.') throw new Error('MMS sending is not configured correctly.') } // 2. Prepare data for MessageBird API const mmsParams: MessageBirdMmsParams = { originator: originator, recipients: [input.recipient], // API expects an array subject: input.subject, body: input.body, mediaUrls: input.mediaUrls, // You could generate a unique reference here if using status webhooks // reference: `mms_${context.currentUser?.id}_${Date.now()}` } let messageBirdResponse = null let errorMessage = null let initialStatus = 'failed' // Default status // 3. Create initial record in DB (optional, but good for tracking attempts) // You might choose to create the record *after* a successful API call instead const dbRecord = await db.mmsMessage.create({ data: { recipient: input.recipient, originator: originator, subject: input.subject, body: input.body, mediaUrls: input.mediaUrls, status: 'pending', // Start as pending }, }) try { // 4. Call MessageBird API logger.info({ mmsParams }, 'Attempting to send MMS via MessageBird') messageBirdResponse = await new Promise((resolve, reject) => { messagebird.mms.create(mmsParams, (err, response) => { if (err) { logger.error({ err }, 'MessageBird API error') reject(err) // Reject the promise on error } else { logger.info({ response }, 'MessageBird API success') resolve(response) // Resolve the promise on success } }) }) // 5. Update DB record on Success initialStatus = 'sent' // Status from MessageBird perspective await db.mmsMessage.update({ where: { id: dbRecord.id }, data: { messageBirdId: messageBirdResponse.id, status: initialStatus, // Prisma's @updatedAt handles this automatically }, }) // Return the updated record return { ...dbRecord, messageBirdId: messageBirdResponse.id, status: initialStatus } } catch (error) { // 6. Handle Errors and Update DB record on Failure logger.error({ error, recipient: input.recipient }, 'Failed to send MMS') errorMessage = error.message || 'Unknown error during MMS sending.' // Extract specific errors if available (MessageBird SDK might structure errors differently) if (error.errors) { errorMessage = error.errors.map(e => `${e.description} (Code: ${e.code})`).join(', ') } await db.mmsMessage.update({ where: { id: dbRecord.id }, data: { status: 'failed', statusMessage: errorMessage, // Prisma's @updatedAt handles this automatically }, }) // Re-throw or handle error appropriately for the GraphQL response // Depending on requirements, you might want to return the failed DB record // or throw a GraphQL error. Throwing is often clearer. throw new Error(`Failed to send MMS: ${errorMessage}`) // Alternatively, return the failed record: // return { ...dbRecord, status: 'failed', statusMessage: errorMessage }; } } // Remove or comment out other generated functions like mmsMessages, mmsMessage, etc. // unless you plan to implement listing/viewing messages via GraphQL. // export const mmsMessages: QueryResolvers['mmsMessages'] = () => { ... } // export const mmsMessage: QueryResolvers['mmsMessage'] = ({ id }) => { ... } // ... etc
Explanation:
- Imports: We import necessary types, Redwood functions (
validate
,logger
,db
), andinitClient
frommessagebird
. - Client Initialization: The
messagebird
client is initialized outside the function using theMESSAGEBIRD_ACCESS_KEY
from environment variables. This reuses the client instance. - Input Validation: Basic validation checks for required fields (
recipient
,mediaUrls
) and the MessageBird limit on media attachments. Also checks that eitherbody
ormediaUrls
are present. - Originator Check: Ensures the
MESSAGEBIRD_ORIGINATOR
is configured. - Prepare Params: Constructs the
mmsParams
object matching the structure required bymessagebird.mms.create
. Note thatrecipients
must be an array. - Initial DB Record: We create a record in our
MmsMessage
table before calling the API with apending
status. This helps track attempts even if the API call fails immediately. - API Call:
- We wrap the
messagebird.mms.create
call in aPromise
because the SDK uses a callback pattern. This allows us to useasync/await
. - The callback handles
err
andresponse
from MessageBird. On error, the promise is rejected; on success, it's resolved with the response.
- We wrap the
- Success Handling: If the API call succeeds, we update the database record with the
messageBirdId
returned by the API and set the status tosent
. We return the updated record. - Error Handling: A
try...catch
block catches errors either from input validation or the API call promise rejection.- We log the error using Redwood's logger.
- We attempt to extract a meaningful error message from the caught
error
object (MessageBird errors often have a.errors
array). - The database record is updated with a
failed
status and the error message. - We re-throw the error to signal failure to the GraphQL layer. You could modify this to return the failed DB record instead if preferred.
- Imports: We import necessary types, Redwood functions (
5. Integrating with MessageBird (Service Details Revisited)
The core integration happens within the sendMms
service function:
- Authentication: Handled automatically by
initClient(process.env.MESSAGEBIRD_ACCESS_KEY)
. Ensure the key is correct and has the necessary permissions in your MessageBird account. - API Endpoint: The SDK directs requests to the correct MessageBird MMS API endpoint (
https://rest.messagebird.com/mms
). - Parameters: We map our
SendMmsInput
to the requiredmessagebird.mms.create
parameters:originator
: Fromprocess.env.MESSAGEBIRD_ORIGINATOR
.recipients
: Takesinput.recipient
and puts it in an array[input.recipient]
.subject
: Frominput.subject
.body
: Frominput.body
.mediaUrls
: Frominput.mediaUrls
. Ensure these are publicly accessible URLs. MessageBird needs to fetch them. They must also adhere to size (under 1MB each typically) and type constraints (see MessageBird MMS docs for supported types like JPEG, PNG, GIF, MP4, etc.).
- Secure Credentials: Using
.env
is the standard way to handle API keys securely in RedwoodJS. Never commit your.env
file to version control. Use.env.defaults
for non-sensitive defaults and add.env
to your.gitignore
file.
Obtaining MessageBird Credentials:
- Log in to your MessageBird Dashboard.
- Navigate to Developers in the left-hand menu.
- Click on API access.
- You can view your Live and Test API keys here. Click the eye icon to reveal and copy the desired key.
- To get an MMS-enabled number:
- Navigate to Numbers.
- Buy a new number, ensuring it's in the US or Canada and supports MMS capabilities.
- Copy the number in E.164 format (e.g.,
+1...
).
6. Error Handling, Logging, and Retries
Our service includes basic error handling and logging:
- Error Handling Strategy: We use
try...catch
to capture exceptions during the process. Errors are logged, and the corresponding database record is updated with a 'failed' status and an error message. The error is then re-thrown to the GraphQL layer. - Logging: Redwood's built-in
logger
(pino
) is used to log informational messages (API call attempts, success) and errors. Check your console output when runningyarn rw dev
or your production log streams. Logged objects ({ err }
,{ response }
) provide detailed context. MessageBird API error responses often contain anerrors
array with specificcode
,description
, andparameter
fields, which are invaluable for debugging. - Retry Mechanisms: This basic implementation does not include automatic retries. For production systems, consider adding retry logic for transient network errors or specific MessageBird error codes (e.g., temporary service unavailability). Libraries like
async-retry
can help implement strategies like exponential backoff. This typically involves catching specific error types/codes and scheduling a background job (using RedwoodJS background functions or external queues like Redis/BullMQ) to retry thesendMms
operation after a delay.
7. Security Considerations
- Authentication/Authorization: The
@requireAuth
directive on thesendMms
mutation ensures only logged-in users can trigger it. Implement role-based access control if needed (e.g., only admins can send certain types of MMS). - Input Validation: We perform basic validation using
@redwoodjs/api
'svalidate
function and custom checks (e.g., body/media presence, media count). You could add more specific validation for phone number formats (e.g., usinglibphonenumber-js
) or URL formats. - Input Sanitization: While less critical for phone numbers and pre-signed media URLs, always sanitize user-generated text inputs (
subject
,body
) if they are displayed elsewhere in your application to prevent XSS attacks. Libraries likedompurify
(if rendering HTML) or simple replacements can help. For this API-only interaction, the risk is lower. - API Key Security: Storing the key in
.env
and not committing it is paramount. Use secrets management solutions (like Doppler, Vercel environment variables, AWS Secrets Manager) in production environments. - Rate Limiting: MessageBird imposes rate limits on API requests. Implement rate limiting on your GraphQL mutation (e.g., using Redwood's directives or middleware with libraries like
graphql-rate-limit-directive
) to prevent abuse and hitting MessageBird limits. - Media URL Security: Ensure the
mediaUrls
provided point to trusted sources. If users upload media, use secure storage (like S3, GCS) and consider pre-signed URLs with limited expiry times passed to the mutation, rather than directly public URLs if possible. Validate file types and sizes during upload.
8. Testing
Testing ensures the service behaves as expected.
-
Unit Testing the Service: RedwoodJS generates a test file (
api/src/services/mms/mms.test.ts
). Modify it to test thesendMms
logic. We need to mock themessagebird
client and thedb
client.// api/src/services/mms/mms.test.ts import { sendMms } from './mms' import { db } from 'src/lib/db' import { logger } from 'src/lib/logger' // Mock the MessageBird SDK // Store the mock reference to reset calls between tests let mockMmsCreate jest.mock('messagebird', () => { mockMmsCreate = jest.fn() // Assign the mock function here return { initClient: jest.fn().mockReturnValue({ mms: { create: mockMmsCreate, }, }), } }) // Mock the logger to prevent console noise during tests jest.mock('src/lib/logger') describe('mms service', () => { // Mock database interactions const mockDbMmsMessage = { create: jest.fn(), update: jest.fn(), } beforeAll(() => { // Assign the mock implementation to db.mmsMessage Object.assign(db, { mmsMessage: mockDbMmsMessage }) // Set required environment variables for tests process.env.MESSAGEBIRD_ORIGINATOR = '+15550001111' process.env.MESSAGEBIRD_ACCESS_KEY = 'test_key' // Doesn't matter, initClient is mocked }) afterEach(() => { // Reset mocks after each test jest.clearAllMocks() mockMmsCreate.mockReset() // Specifically reset the create mock }) it('sends an MMS successfully', async () => { const input = { recipient: '+15551112222', subject: 'Test Subject', body: 'Test Body', mediaUrls: ['http://example.com/image.jpg'], } const mockDbRecord = { id: 'mock-db-id-1', recipient: input.recipient, originator: process.env.MESSAGEBIRD_ORIGINATOR, subject: input.subject, body: input.body, mediaUrls: input.mediaUrls, status: 'pending', // Initial status from create messageBirdId: null, createdAt: new Date(), updatedAt: new Date(), } const mockApiResponse = { id: 'mb-mms-id-123', // ... other fields MessageBird might return } // Mock DB create response mockDbMmsMessage.create.mockResolvedValue(mockDbRecord) // Mock MessageBird API success via callback mockMmsCreate.mockImplementation((params, callback) => { callback(null, mockApiResponse) }) const result = await sendMms({ input }) // Assertions expect(mockDbMmsMessage.create).toHaveBeenCalledTimes(1) expect(mockDbMmsMessage.create).toHaveBeenCalledWith({ data: expect.objectContaining({ recipient: input.recipient, status: 'pending', mediaUrls: input.mediaUrls, }) }) expect(mockMmsCreate).toHaveBeenCalledTimes(1) expect(mockMmsCreate).toHaveBeenCalledWith( expect.objectContaining({ recipients: [input.recipient], originator: process.env.MESSAGEBIRD_ORIGINATOR, body: input.body, mediaUrls: input.mediaUrls, }), expect.any(Function) // Check that a callback was provided ) expect(mockDbMmsMessage.update).toHaveBeenCalledTimes(1) expect(mockDbMmsMessage.update).toHaveBeenCalledWith({ where: { id: mockDbRecord.id }, data: expect.objectContaining({ messageBirdId: mockApiResponse.id, status: 'sent', }), }) expect(result).toEqual(expect.objectContaining({ id: mockDbRecord.id, messageBirdId: mockApiResponse.id, status: 'sent', recipient: input.recipient, })) expect(logger.error).not.toHaveBeenCalled() // Ensure no errors were logged }) it('handles MessageBird API error', async () => { const input = { recipient: '+15553334444', mediaUrls: ['http://example.com/image.png'], // Only media URL } const mockDbRecord = { id: 'mock-db-id-2', /* ... other fields */ } mockDbMmsMessage.create.mockResolvedValue(mockDbRecord) // DB create succeeds const mockApiError = { errors: [{ code: 21, description: 'Recipient is invalid', parameter: 'recipients' }], } // Mock MessageBird API failure via callback mockMmsCreate.mockImplementation((params, callback) => { callback(mockApiError, null) }) // Expect the function to throw an error await expect(sendMms({ input })).rejects.toThrow( /Failed to send MMS: Recipient is invalid \(Code: 21\)/ ) // Assertions expect(mockDbMmsMessage.create).toHaveBeenCalledTimes(1) expect(mockMmsCreate).toHaveBeenCalledTimes(1) expect(mockDbMmsMessage.update).toHaveBeenCalledTimes(1) expect(mockDbMmsMessage.update).toHaveBeenCalledWith({ where: { id: mockDbRecord.id }, data: expect.objectContaining({ status: 'failed', statusMessage: 'Recipient is invalid (Code: 21)', messageBirdId: undefined, // Ensure messageBirdId was not set on failure }), }) expect(logger.error).toHaveBeenCalled() // Ensure error was logged }) it('throws error if originator is not configured', async () => { const originalOriginator = process.env.MESSAGEBIRD_ORIGINATOR; delete process.env.MESSAGEBIRD_ORIGINATOR; // Temporarily remove env var const input = { recipient: '+15551112222', mediaUrls: ['url'] }; await expect(sendMms({ input })).rejects.toThrow( 'MMS sending is not configured correctly.' ); expect(mockDbMmsMessage.create).not.toHaveBeenCalled(); expect(mockMmsCreate).not.toHaveBeenCalled(); process.env.MESSAGEBIRD_ORIGINATOR = originalOriginator; // Restore env var }) it('throws error if body and mediaUrls are missing', async () => { const input = { recipient: '+15551112222', mediaUrls: [] }; // Missing body, empty mediaUrls await expect(sendMms({ input })).rejects.toThrow( 'MMS must include either a body or at least one media URL.' ); expect(mockDbMmsMessage.create).not.toHaveBeenCalled(); expect(mockMmsCreate).not.toHaveBeenCalled(); }) })
Run tests using
yarn rw test api
. -
Manual Verification (Development):
-
Start the development server:
yarn rw dev
. -
Open the GraphQL Playground, usually at
http://localhost:8911/graphql
. -
Ensure your
.env
file has valid Test or Live credentials and a valid MMS-enabled Originator Number. -
Execute the
sendMms
mutation:mutation SendTestMms { sendMms(input: { recipient: ""+1RECIPIENTNUMBER"", # Use a real number you can check subject: ""Redwood Test MMS"", body: ""Hello from RedwoodJS!"", # Generic body text mediaUrls: [""https://developers.messagebird.com/img/logos/mb-400.jpg""] # Publicly accessible image URL }) { id messageBirdId recipient status statusMessage mediaUrls subject body } }
-
Checklist:
- Did the mutation complete without errors in the Playground?
- Check the API server console (
yarn rw dev
output) for logs. Are there errors? - Check your database (e.g., using
yarn rw prisma studio
) - was anMmsMessage
record created? Does it have amessageBirdId
andsent
status (if successful) orfailed
status and an error message? - If using a Live key and number, did the recipient phone receive the MMS message with the subject, body, and image? (This might take a few seconds to minutes).
- If using a Test key, the message won't actually be delivered, but the API call should succeed or fail according to MessageBird's test environment rules, and the DB record should reflect this.
-
9. Handling Status Updates (Webhook - Optional but Recommended)
To get delivery statuses (delivered
, failed
, etc.) back from MessageBird, you need to set up a webhook.
-
Create a Webhook Handler Function: Use the Redwood generator to create an HTTP function.
yarn rw g function messagebirdWebhook --typescript
-
Implement the Handler: Edit
api/src/functions/messagebirdWebhook.ts
. MessageBird sends status updates via GET requests to your configured URL.// api/src/functions/messagebirdWebhook.ts import type { APIGatewayEvent, Context } from 'aws-lambda' import { logger } from 'src/lib/logger' import { db } from 'src/lib/db' /** * Handles incoming status report webhooks from MessageBird for MMS/SMS. * MessageBird sends status updates as GET requests. * It might also be used for receiving MO (Mobile Originated/Incoming) messages via POST. */ export const handler = async (event: APIGatewayEvent, _context: Context) => { logger.info({ event }, 'Received request on messagebirdWebhook') // --- Security Check (Basic) --- // Ideally, verify a signature if MessageBird provides one for webhooks. // If not, consider IP whitelisting MessageBird IPs as a basic measure. // Add more robust checks as needed. if (event.httpMethod === 'GET') { // Handle Status Report (GET request) const params = event.queryStringParameters logger.info({ params }, 'Processing MessageBird GET Status Report') if (!params || !params.id || !params.status || !params.recipient || !params.statusDatetime) { logger.warn('Webhook GET request missing required parameters.') return { statusCode: 400, body: 'Missing parameters' } } const messageBirdId = params.id // This is the MessageBird message ID const status = params.status // e.g., 'delivered', 'delivery_failed', 'sent', 'buffered' const recipient = params.recipient // Recipient number const statusDatetime = params.statusDatetime // Timestamp of status update try { // Using updateMany for simplicity, assuming messageBirdId is reliably unique as per schema. // If multiple records somehow shared a messageBirdId (schema violation), this would update all. // Consider findUnique + update for stricter single-record handling if needed. const updatedRecord = await db.mmsMessage.updateMany({ where: { messageBirdId: messageBirdId, // Optional: Add recipient check for extra safety // recipient: recipient }, data: { status: status, statusMessage: `Status updated via webhook at ${statusDatetime}`, updatedAt: new Date(statusDatetime), // Use MessageBird's timestamp }, }) if (updatedRecord.count === 0) { logger.warn({ messageBirdId }, 'No matching MmsMessage found in DB for status update.') // Decide how to handle: 404 or 200? MessageBird retries on non-200. // Returning 200 prevents retries for messages we don't track. return { statusCode: 200, body: 'OK (No matching record)' } } logger.info({ messageBirdId, status }, 'Successfully updated MmsMessage status from webhook.') return { statusCode: 200, body: 'OK' } } catch (error) { logger.error({ error, messageBirdId }, 'Error updating MmsMessage status from webhook.') // Return 500 to signal an internal error, MessageBird might retry. return { statusCode: 500, body: 'Internal Server Error' } } } else if (event.httpMethod === 'POST') { // Handle Incoming Message (MO - Mobile Originated) (POST request) // This part is for receiving MMS sent TO your MessageBird number. // You would parse the POST body (likely JSON) according to MessageBird docs // and potentially create a new MmsMessage record with status 'received'. logger.info('Received POST request (potentially incoming message) - not implemented yet.') // Implement logic here if needed return { statusCode: 200, body: 'OK (POST received)' } } else { logger.warn(`Unsupported HTTP method: ${event.httpMethod}`) return { statusCode: 405, body: 'Method Not Allowed' } } }