code examples

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

Send MMS with AWS SNS and Next.js: A Developer Guide

A step-by-step guide for building a Next.js application to send MMS messages using AWS Pinpoint SMS and Voice v2 API, AWS SDK v3, and S3 for media storage.

This guide provides a step-by-step walkthrough for building a feature within a Next.js application to send Multimedia Messaging Service (MMS) messages using AWS services. We'll leverage the AWS SDK for JavaScript v3, specifically interacting with the Amazon Pinpoint SMS and Voice v2 API (sms-voice namespace). While the AWS console might still reference SNS for SMS history, this newer Pinpoint API is used for sending MMS messages programmatically. Amazon S3 will be used for media storage.

Project Overview and Goals

Goal: To create a simple Next.js application with a backend API endpoint capable of sending MMS messages containing text and an image hosted on Amazon S3 to a specified phone number.

Problem Solved: This provides a foundational implementation for applications needing to send rich media messages programmatically – useful for notifications, alerts, marketing campaigns, or user interactions involving images or other media.

Technologies:

  • Next.js: A React framework providing server-side rendering, static site generation, and simplified API route creation. Chosen for its developer experience and ease of building full-stack applications.
  • AWS SDK for JavaScript v3: The modern AWS SDK, used to interact with AWS services programmatically. We'll use clients for Amazon Pinpoint SMS and Voice v2 (@aws-sdk/client-pinpoint-sms-voice-v2). Chosen for its modularity and async/await support.
  • Amazon Pinpoint SMS and Voice v2 API: The specific AWS API endpoint (SendMediaMessage) required for sending MMS messages.
  • Amazon S3: Used to host the media files (e.g., images, videos) that will be included in the MMS messages. Chosen for its scalability, durability, and integration with other AWS services.
  • Node.js: The runtime environment for Next.js and the AWS SDK.

Architecture:

text
+-----------------+      +------------------------+      +--------------------------------+      +---------------------+      +--------------------+
|   User Browser  |----->|  Next.js Frontend UI   |----->|   Next.js API Route (/api/mms) |----->| AWS SDK (Pinpoint V2) |----->| AWS Pinpoint Service |
| (React Component)|      |   (pages/index.js)     |      |  (pages/api/send-mms.js)       |      | (SendMediaMessage)    |      | (Sends MMS)        |
+-----------------+      +------------------------+      +--------------------------------+      +---------------------+      +----------+---------+
                                                                      |                                                            |
                                                                      |                                                            |  Reads Media
                                                                      | Uses Credentials & Config                              +---v---+-------+
                                                                      +------------------------------------------------------->|  Amazon S3  |
                                                                                                                               | (Media File)|
                                                                                                                               +-------------+

Prerequisites:

  • An AWS account with permissions to manage IAM, S3, and Pinpoint/SNS resources.
  • Node.js (v18 or later recommended) and npm/yarn installed.
  • AWS CLI installed and configured (useful for setup and testing).
  • A basic understanding of Next.js, React, and asynchronous JavaScript.
  • An MMS-capable phone number obtained through AWS (e.g., a 10DLC number, Toll-Free Number in supported regions like US/Canada).

Note: You will set up the S3 bucket and upload the media file in Section 2 as part of this guide.

Outcome: By the end of this guide, you will have a functional Next.js application with a simple UI allowing users to input a destination phone number, a message body, and a media URL (hosted on S3), which triggers an API call to send an MMS message via AWS.

1. Setting up the Project

Let's initialize our Next.js project and install the necessary dependencies.

  1. Create Next.js App: Open your terminal and run:

    bash
    npx create-next-app@latest nextjs-aws-mms --typescript
    # or
    # yarn create next-app nextjs-aws-mms --typescript

    Follow the prompts (App Router recommended: Yes, Tailwind CSS: Optional, src/ directory: Yes, Customize defaults: No).

  2. Navigate to Project Directory:

    bash
    cd nextjs-aws-mms
  3. Install AWS SDK v3 Clients: We need the client for the Pinpoint SMS and Voice v2 API.

    bash
    npm install @aws-sdk/client-pinpoint-sms-voice-v2
    # or
    # yarn add @aws-sdk/client-pinpoint-sms-voice-v2
  4. Set Up Environment Variables: Create a file named .env.local in the root of your project. Never commit this file to version control if it contains sensitive credentials.

    env
    # AWS Credentials (Best Practice: Use IAM Roles for EC2/ECS/Lambda or temporary credentials)
    # For local development, these can be used but are less secure.
    AWS_ACCESS_KEY_ID=YOUR_AWS_ACCESS_KEY_ID
    AWS_SECRET_ACCESS_KEY=YOUR_AWS_SECRET_ACCESS_KEY
    
    # AWS Configuration
    AWS_REGION=us-east-1 # IMPORTANT: Use the SAME region as your S3 bucket and Origination Number
    
    # Application Specific
    # Get this from your AWS Pinpoint/SNS console after acquiring a number
    SNS_ORIGINATION_NUMBER=+1xxxxxxxxxx # Your MMS-capable AWS phone number in E.164 format
    S3_BUCKET_NAME=your-mms-media-bucket-name # The name of your S3 bucket
    • Obtaining AWS Credentials:
      • Navigate to the AWS IAM console.
      • Create an IAM user with Programmatic access.
      • Attach necessary permissions (see Step 5).
      • Record the generated Access Key ID and Secret Access Key.
      • Security Note: For production deployments on AWS (EC2, Lambda, etc.), use IAM Roles instead of storing long-lived credentials. The SDK automatically uses roles if available. For local development, these environment variables are common, but ensure .env.local is in your .gitignore.
    • AWS Region: Choose the AWS region where you will create your S3 bucket and where your origination phone number is registered. Consistency is crucial.
    • Origination Number: Acquire an MMS-capable number through the AWS console (often under Pinpoint or SNS sections depending on your setup). Ensure it's active and MMS is enabled.
    • S3 Bucket Name: The globally unique name of the S3 bucket you'll create in the next section.
  5. Configure IAM Permissions: The IAM user (or role) whose credentials your Next.js app uses needs permissions to:

    • Send MMS messages via Pinpoint SMS/Voice v2.
    • Read the media file from your S3 bucket.

    Create an IAM policy (e.g., NextJsMmsAppPolicy) with the following JSON structure, replacing placeholders:

    json
    {
        ""Version"": ""2012-10-17"",
        ""Statement"": [
            {
                ""Sid"": ""AllowMmsSend"",
                ""Effect"": ""Allow"",
                ""Action"": ""sms-voice:SendMediaMessage"",
                ""Resource"": ""*""
            },
            {
                ""Sid"": ""AllowS3Read"",
                ""Effect"": ""Allow"",
                ""Action"": ""s3:GetObject"",
                ""Resource"": ""arn:aws:s3:::YOUR_MMS_MEDIA_BUCKET_NAME/*""
            }
        ]
    }
    • Replace YOUR_MMS_MEDIA_BUCKET_NAME with your actual bucket name.
    • Attach this policy to the IAM user you created in step 4 (or to the IAM role your deployment environment uses).
    • Why these permissions? sms-voice:SendMediaMessage allows the application to use the specific API call for MMS. s3:GetObject enables the Pinpoint service (acting on behalf of your call) to retrieve the media file from your bucket when constructing the MMS.

2. AWS Resource Setup (S3 Bucket and Media)

MMS messages require media files to be hosted somewhere accessible. AWS S3 is the standard choice.

  1. Create S3 Bucket:

    • Navigate to the AWS S3 console.
    • Click ""Create bucket"".
    • Enter a globally unique bucket name (this will be your S3_BUCKET_NAME in .env.local).
    • Select the same AWS Region as specified in your .env.local and where your Origination Number resides. This is critical for MMS sending.
    • Keep default settings for Block Public Access (usually enabled – we will use IAM permissions for access, not public URLs).
    • Click ""Create bucket"".
  2. Upload Media File:

    • Navigate into your newly created bucket in the S3 console.
    • Click ""Upload"".
    • Add your media file (e.g., cat-image.jpg). Ensure it meets AWS MMS requirements (check Supported file types and sizes). Common formats like JPEG, PNG, GIF (under limits) are usually fine.
    • Click ""Upload"".
  3. Get Media File S3 URI:

    • Once uploaded, select the media file in the S3 console.
    • Find the ""S3 URI"" (it looks like s3://your-mms-media-bucket-name/cat-image.jpg). You will need this URI later for the API call. Note: This is not the public Object URL.

3. Implementing the Backend API Route

We'll create a Next.js API route to handle the MMS sending logic.

  1. Create API Route File: Create the file src/app/api/send-mms/route.ts (if using App Router) or src/pages/api/send-mms.ts (if using Pages Router). This example uses the App Router structure.

  2. Implement the API Handler: Paste the following code into src/app/api/send-mms/route.ts:

    typescript
    // src/app/api/send-mms/route.ts
    import { NextRequest, NextResponse } from 'next/server';
    import {
        PinpointSMSVoiceV2Client,
        SendMediaMessageCommand,
        SendMediaMessageCommandInput,
    } from '@aws-sdk/client-pinpoint-sms-voice-v2';
    
    // Ensure environment variables are loaded (especially outside Vercel)
    // require('dotenv').config({ path: '.env.local' }); // Uncomment if needed locally and not using Next.js auto-loading
    
    // Retrieve environment variables (checks happen within the handler)
    const { AWS_REGION, SNS_ORIGINATION_NUMBER } = process.env;
    
    // Create the Pinpoint SMS Voice v2 Client
    // Credentials will be automatically sourced from environment variables
    // (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) or IAM role if deployed on AWS infrastructure.
    const pinpointClient = new PinpointSMSVoiceV2Client({
        region: AWS_REGION, // SDK will use AWS_REGION env var if not explicitly passed here
    });
    
    export async function POST(request: NextRequest) {
        console.log('Received request to /api/send-mms');
    
        // Validate required environment variables *per request*
        if (!AWS_REGION || !SNS_ORIGINATION_NUMBER) {
             console.error('Server configuration error: Missing AWS_REGION or SNS_ORIGINATION_NUMBER');
             return NextResponse.json(
                { error: 'Server configuration error: Missing AWS environment variables.' },
                { status: 500 }
            );
        }
    
        let requestBody;
        try {
            requestBody = await request.json();
            console.log('Request body:', requestBody);
        } catch (error) {
            console.error('Error parsing request body:', error);
            return NextResponse.json({ error: 'Invalid request body' }, { status: 400 });
        }
    
        const { destinationPhoneNumber, messageBody, mediaUrl } = requestBody;
    
        // Basic Input Validation
        if (!destinationPhoneNumber || !mediaUrl) {
            return NextResponse.json(
                { error: 'Missing required fields: destinationPhoneNumber and mediaUrl are required.' },
                { status: 400 }
            );
        }
    
        // More robust validation could be added here (e.g., E.164 format check for phone number, S3 URI format check)
        if (!/^s3:\/\/[^\/]+\/.+/.test(mediaUrl)) {
             return NextResponse.json(
                { error: 'Invalid mediaUrl format. Must be an S3 URI (e.g., s3://bucket-name/key-name.jpg).' },
                { status: 400 }
            );
        }
         if (!/^\+\d{11,15}$/.test(destinationPhoneNumber)) {
             return NextResponse.json(
                { error: 'Invalid destinationPhoneNumber format. Must be E.164 format (e.g., +12223334444).' },
                { status: 400 }
            );
        }
    
        const params: SendMediaMessageCommandInput = {
            DestinationPhoneNumber: destinationPhoneNumber, // Provided in request
            OriginationIdentity: SNS_ORIGINATION_NUMBER,   // From environment variable
            MessageBody: messageBody || '',               // Optional message body from request
            MediaUrls: [mediaUrl],                         // S3 URI from request (must be an array)
            // ConfigurationSetName: 'YourOptionalConfigSetName' // If using configuration sets
        };
    
        console.log('Attempting to send MMS with params:', params);
    
        try {
            const command = new SendMediaMessageCommand(params);
            const response = await pinpointClient.send(command);
    
            console.log('MMS sent successfully:', response);
            // The response contains the MessageId
            return NextResponse.json({
                success: true,
                messageId: response.MessageId,
                message: 'MMS sent successfully',
            }, { status: 200 });
    
        } catch (error: any) {
            console.error('Failed to send MMS:', error);
    
            // Provide more specific feedback if possible
            let errorMessage = 'Failed to send MMS.';
            let statusCode = 500;
    
            if (error.name === 'ValidationException') {
                errorMessage = `Invalid request parameter: ${error.message}`;
                statusCode = 400;
            } else if (error.name === 'AccessDeniedException') {
                 errorMessage = `Permission denied. Check IAM permissions for sms-voice:SendMediaMessage and s3:GetObject. ${error.message}`;
                 statusCode = 403;
            } else if (error.name === 'ResourceNotFoundException') {
                errorMessage = `Resource not found (e.g., S3 object or Origination Number): ${error.message}`;
                statusCode = 404;
            } else if (error.name === 'ThrottlingException') {
                errorMessage = `Request throttled by AWS: ${error.message}`;
                statusCode = 429;
            } // Add more specific error handling as needed
    
            return NextResponse.json({ error: errorMessage, details: error.message }, { status: statusCode });
        }
    }

    Code Explanation:

    • Imports NextRequest, NextResponse and necessary AWS SDK v3 components.
    • Retrieves AWS configuration and the origination number from environment variables.
    • Instantiates the PinpointSMSVoiceV2Client. Credentials are automatically handled by the SDK based on environment variables or IAM roles.
    • Defines an asynchronous POST handler function as required by Next.js App Router API routes.
    • Checks for required environment variables (AWS_REGION, SNS_ORIGINATION_NUMBER) within the handler to ensure configuration is present for each request.
    • Parses the incoming JSON request body (destinationPhoneNumber, messageBody, mediaUrl).
    • Performs basic input validation (presence checks, S3 URI format, E.164 format).
    • Constructs the SendMediaMessageCommandInput object using data from the request and environment variables. Note that MediaUrls must be an array of S3 URIs.
    • Creates a SendMediaMessageCommand with the parameters.
    • Calls pinpointClient.send(command) within a try...catch block to execute the API call.
    • On success, logs the response and returns a JSON object with success: true and the messageId.
    • On failure, logs the error and returns a JSON error response with an appropriate status code and message, attempting to provide specific feedback based on the AWS error type.

4. Implementing the Frontend UI

Now, let's create a simple React component to interact with our API endpoint.

  1. Create Component File: Create src/components/MmsSender.tsx.

  2. Implement the Component: Paste the following code:

    typescript
    // src/components/MmsSender.tsx
    'use client'; // Required for components with interactivity (useState, onClick) in App Router
    
    import React, { useState } from 'react';
    
    export default function MmsSender() {
        const [destinationPhoneNumber, setDestinationPhoneNumber] = useState('');
        const [messageBody, setMessageBody] = useState('');
        const [mediaUrl, setMediaUrl] = useState(''); // Should be an S3 URI entered by the user
        const [statusMessage, setStatusMessage] = useState('');
        const [isLoading, setIsLoading] = useState(false);
    
        const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
            event.preventDefault();
            setIsLoading(true);
            setStatusMessage('Sending MMS...');
    
             // Basic validation reminder
             if (!destinationPhoneNumber || !mediaUrl) { // Check if mediaUrl is provided
                setStatusMessage('Error: Destination Phone Number and Media URL (S3 URI) are required.');
                setIsLoading(false);
                return;
            }
             // E.164 format validation reminder
            if (!/^\+\d{11,15}$/.test(destinationPhoneNumber)) {
                setStatusMessage('Error: Invalid phone number format. Use E.164 (e.g., +12223334444).');
                setIsLoading(false);
                return;
            }
            // S3 URI format validation reminder (basic)
             if (!/^s3:\/\/[^\/]+\/.+/.test(mediaUrl)) {
                setStatusMessage('Error: Invalid Media URL format. Must be an S3 URI (e.g., s3://bucket-name/key-name.jpg).');
                setIsLoading(false);
                return;
            }
    
            try {
                const response = await fetch('/api/send-mms', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({
                        destinationPhoneNumber,
                        messageBody,
                        mediaUrl: mediaUrl, // Use the S3 URI from state
                    }),
                });
    
                const result = await response.json();
    
                if (response.ok && result.success) {
                    setStatusMessage(`MMS sent successfully! Message ID: ${result.messageId}`);
                    // Clear form on success
                    setDestinationPhoneNumber('');
                    setMessageBody('');
                    setMediaUrl('');
                } else {
                    setStatusMessage(`Error: ${result.error || 'Failed to send MMS.'} ${result.details ? `(${result.details})` : ''}`);
                }
            } catch (error) {
                console.error('Network or unexpected error:', error);
                setStatusMessage('Error: Failed to connect to the API.');
            } finally {
                setIsLoading(false);
            }
        };
    
        return (
            <div className=""max-w-md mx-auto mt-10 p-6 border rounded-lg shadow-lg"">
                <h2 className=""text-2xl font-semibold mb-4 text-center"">Send MMS via AWS</h2>
                <form onSubmit={handleSubmit}>
                    <div className=""mb-4"">
                        <label htmlFor=""destinationPhoneNumber"" className=""block text-sm font-medium text-gray-700 mb-1"">
                            Destination Phone Number (E.164):
                        </label>
                        <input
                            type=""tel""
                            id=""destinationPhoneNumber""
                            value={destinationPhoneNumber}
                            onChange={(e) => setDestinationPhoneNumber(e.target.value)}
                            placeholder=""+12223334444""
                            required
                            className=""w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500""
                        />
                    </div>
                    <div className=""mb-4"">
                        <label htmlFor=""messageBody"" className=""block text-sm font-medium text-gray-700 mb-1"">
                            Message Body (Optional):
                        </label>
                        <textarea
                            id=""messageBody""
                            value={messageBody}
                            onChange={(e) => setMessageBody(e.target.value)}
                            rows={3}
                            className=""w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500""
                        />
                    </div>
                    <div className=""mb-4"">
                        <label htmlFor=""mediaUrl"" className=""block text-sm font-medium text-gray-700 mb-1"">
                            Media S3 URI:
                        </label>
                        <input
                            type=""text""
                            id=""mediaUrl""
                            value={mediaUrl}
                            onChange={(e) => setMediaUrl(e.target.value)}
                            placeholder=""s3://your-bucket/image.jpg""
                            required
                            className=""w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500""
                        />
                        <p className=""text-xs text-gray-500 mt-1"">Must be the S3 URI of the media file (e.g., s3://bucket-name/key).</p>
                    </div>
                    <button
                        type=""submit""
                        disabled={isLoading}
                        className=""w-full py-2 px-4 bg-indigo-600 text-white font-semibold rounded-md shadow hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50""
                    >
                        {isLoading ? 'Sending...' : 'Send MMS'}
                    </button>
                </form>
                {statusMessage && (
                    <p className={`mt-4 text-sm ${statusMessage.startsWith('Error:') ? 'text-red-600' : 'text-green-600'}`}>
                        {statusMessage}
                    </p>
                )}
            </div>
        );
    }
    • Code Explanation: Uses useState to manage form inputs and status messages. The handleSubmit function prevents default form submission, sets loading state, performs basic frontend validation checks (including for the S3 URI format), sends a POST request to /api/send-mms using fetch, includes the form data (using the user-provided mediaUrl) in the JSON body, and updates the status message based on the API response.
  3. Use the Component: Replace the content of src/app/page.tsx (or src/pages/index.tsx) with:

    typescript
    // src/app/page.tsx
    import MmsSender from '@/components/MmsSender'; // Adjust path if needed
    
    export default function HomePage() {
      return (
        <main className=""flex min-h-screen flex-col items-center justify-center p-4"">
          <MmsSender />
        </main>
      );
    }

5. Error Handling and Logging

Our API route includes basic error handling and logging:

  • Input Validation: Checks for required fields and basic format validity (S3 URI, E.164) on the backend. This prevents unnecessary calls to AWS.
  • AWS SDK Errors: The try...catch block around pinpointClient.send() catches errors from the AWS API.
  • Specific Error Handling: We attempt to identify common errors (ValidationException, AccessDeniedException, ResourceNotFoundException, ThrottlingException) and return more informative messages and appropriate HTTP status codes (400, 403, 404, 429).
  • Generic Errors: A fallback 500 Internal Server Error is returned for unexpected issues.
  • Logging: console.log and console.error are used to log request details, success messages, and errors on the server side (visible in your terminal during local development or in your deployment provider's logs).

Improvements:

  • Structured Logging: Use a library like pino or winston for structured JSON logging, which is easier to parse and analyze in production logging systems (e.g., CloudWatch Logs, Datadog).
  • Error Tracking Services: Integrate services like Sentry or Bugsnag to capture and aggregate errors automatically.
  • Retry Mechanisms: While the AWS SDK has built-in retries for transient network issues, you might implement application-level retries with exponential backoff (using libraries like async-retry) for specific scenarios (e.g., temporary throttling if you anticipate bursts), although this adds complexity.

6. Security Considerations

  • IAM Least Privilege: Ensure the IAM user/role used by the application has only the permissions required (sms-voice:SendMediaMessage, s3:GetObject on the specific bucket). Avoid using AdministratorAccess.
  • Input Sanitization/Validation: The backend API route must rigorously validate all inputs (destinationPhoneNumber, messageBody, mediaUrl) to prevent injection attacks or malformed requests. Check formats (E.164, S3 URI), lengths, and potentially disallowed characters.
  • Rate Limiting: Implement rate limiting on the /api/send-mms endpoint to prevent abuse and control costs. Libraries like rate-limiter-flexible or platform features (Vercel, Cloudflare) can be used.
  • Authentication/Authorization: This guide assumes an open endpoint. In a real application, protect this API route. Ensure only authenticated and authorized users/systems can trigger MMS sending. Implement session management, JWT, or API keys depending on your architecture.
  • Secrets Management: Never commit secrets (.env.local) to Git. Use environment variables provided by your hosting platform (Vercel, Netlify, AWS Systems Manager Parameter Store, Secrets Manager) for production deployments.
  • S3 Bucket Policy: Double-check that your S3 bucket is not publicly accessible unless intended. Rely on IAM permissions for the application to access media.

7. Testing

  1. Local Development:

    • Run npm run dev or yarn dev.
    • Open http://localhost:3000 in your browser.
    • Fill in a verified destination phone number (if your AWS account is still in the SMS/MMS sandbox) or any valid number (if out of the sandbox). Use the E.164 format (e.g., +15551234567).
    • Enter an optional message.
    • Enter the correct S3 URI for your uploaded media file (e.g., s3://your-mms-media-bucket-name/cat-image.jpg). This is required.
    • Click ""Send MMS"".
    • Check the status message on the UI.
    • Check your terminal running the Next.js app for logs (console.log/console.error output).
    • Check the destination phone for the MMS message.
  2. API Testing (Curl): You can test the API endpoint directly using curl:

    bash
    curl -X POST http://localhost:3000/api/send-mms \
    -H ""Content-Type: application/json"" \
    -d '{
      ""destinationPhoneNumber"": ""+1YOUR_TEST_PHONE_NUMBER"",
      ""messageBody"": ""Test message from curl!"",
      ""mediaUrl"": ""s3://YOUR_BUCKET_NAME/YOUR_MEDIA_KEY.jpg""
    }'

    Replace placeholders with your actual test number, bucket, and media key. Check the JSON response in your terminal.

  3. Unit/Integration Tests (Advanced):

    • Frontend: Use Jest and React Testing Library to test the MmsSender component's behavior (rendering, state changes, form submission). Mock the fetch call.
    • Backend: Use Jest to test the API route handler (POST function). Mock the @aws-sdk/client-pinpoint-sms-voice-v2 client using jest.mock to simulate successful responses and various error conditions from AWS without making actual API calls. This verifies your logic, validation, and error handling.

8. Deployment

Deploying a Next.js application is typically straightforward.

  1. Choose a Platform: Vercel (creator of Next.js), Netlify, AWS Amplify, or containerizing with Docker and deploying to AWS ECS/EKS/App Runner are common choices.

  2. Environment Variables: This is the most critical step for deployment.

    • Go to your chosen hosting provider's dashboard (e.g., Vercel Project Settings > Environment Variables).
    • Add the following environment variables with their production values:
      • AWS_ACCESS_KEY_ID
      • AWS_SECRET_ACCESS_KEY (Use IAM roles if deploying to AWS infrastructure like EC2/Lambda for better security)
      • AWS_REGION
      • SNS_ORIGINATION_NUMBER
      • S3_BUCKET_NAME
    • Ensure these variables are available to the ""Serverless Functions"" or backend environment.
  3. Connect Git Repository: Link your GitHub/GitLab/Bitbucket repository to your hosting provider for automatic deployments on push.

  4. Build & Deploy: The platform will typically build your Next.js application and deploy the static assets and API routes automatically.

  5. Testing Production: Test the deployed application thoroughly, ensuring the environment variables are correctly configured and the application can communicate with AWS services. Check logs on your deployment platform.

9. Troubleshooting and Caveats

  • AccessDeniedException:
    • Cause: IAM user/role lacks required permissions (sms-voice:SendMediaMessage or s3:GetObject).
    • Solution: Verify the IAM policy attached to the user/role used by your application. Ensure the S3 resource ARN in the policy exactly matches your bucket (arn:aws:s3:::your-bucket-name/*).
  • ResourceNotFoundException:
    • Cause: The specified OriginationIdentity (phone number) doesn't exist or isn't configured correctly in the specified AWS_REGION, OR the MediaUrls S3 object doesn't exist or the bucket name is wrong.
    • Solution: Double-check the SNS_ORIGINATION_NUMBER value and the AWS_REGION. Verify the S3 URI (mediaUrl) is correct, the bucket exists in the same region, and the object key is accurate.
  • ValidationException:
    • Cause: Incorrectly formatted parameters (e.g., DestinationPhoneNumber not in E.164, MediaUrls not an array or invalid S3 URI format, message too long, invalid file type/size).
    • Solution: Review the parameters being sent in the SendMediaMessageCommandInput. Check phone number format, ensure MediaUrls is [s3Uri], and verify media file compliance with AWS limits. Check API logs for details.
  • Region Mismatch (CRITICAL):
    • Cause: The S3 bucket and the OriginationIdentity are in different AWS regions.
    • Solution: Ensure the S3 bucket, the origination number, and the AWS_REGION configured in your application/SDK client are all the SAME.
  • AWS SNS Sandbox Limits:
    • Cause: New AWS accounts are often in the SMS/MMS sandbox, limiting sending to verified phone numbers only.
    • Solution: Verify destination numbers in the AWS SNS console for testing, or request to move your account out of the sandbox via AWS Support for production use.
  • MMS Capability:
    • Cause: The OriginationIdentity (phone number) you acquired does not support MMS.
    • Solution: Verify the number's capabilities in the AWS console (Pinpoint/SNS). Acquire a number specifically enabled for MMS.

Frequently Asked Questions

How to send MMS with Next.js and AWS?

You can send MMS messages by creating a Next.js API route that interacts with the AWS Pinpoint SMS and Voice V2 API. This API, along with the AWS SDK for JavaScript v3, allows you to send messages containing text and media hosted on Amazon S3.

What is the Amazon Pinpoint SMS and Voice v2 API?

The Amazon Pinpoint SMS and Voice V2 API is the AWS service used for sending MMS messages programmatically. While the AWS console might still reference SNS for message history, the Pinpoint API is what's used for sending MMS from your application.

Why use Amazon S3 for MMS media?

Amazon S3 is used to store the media files (images, videos) included in MMS messages. It's chosen for its scalability, durability, and seamless integration with other AWS services, making it ideal for handling media in your MMS workflow.

When should I use the sms-voice namespace in AWS SDK?

The `sms-voice` namespace in the AWS SDK for JavaScript v3 is specifically for interacting with the Amazon Pinpoint SMS and Voice V2 API. Use this namespace when you need to send MMS messages or other SMS/voice communications programmatically.

Can I use a different React framework with AWS for MMS?

While this guide uses Next.js for its ease of API route creation and full-stack capabilities, you can adapt the core logic to work with other React frameworks. You'll still need the AWS SDK for JavaScript v3 and interact with the Pinpoint API.

How to set up AWS credentials for sending MMS?

For local development, you can set `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` in a `.env.local` file. However, for production, it is best to use IAM roles for enhanced security. The AWS SDK will automatically use roles if available in your environment. Never commit credentials to version control.

What AWS permissions are needed for sending MMS?

Your IAM user or role needs the `sms-voice:SendMediaMessage` permission to send MMS via Pinpoint and `s3:GetObject` to allow the service to retrieve media from your designated S3 bucket.

How to structure the API request for sending an MMS?

Send a POST request to your API endpoint with a JSON body containing `destinationPhoneNumber` (in E.164 format), an optional `messageBody`, and `mediaUrl` which must be an S3 URI pointing to your media file.

What is the correct format for the mediaUrl in the API request?

The `mediaUrl` must be an S3 URI, for example, `s3://your-bucket-name/image.jpg`. It must follow this specific format so the Pinpoint API can retrieve the file.

Why does the AWS region matter for MMS?

Your S3 bucket, Origination Number, and the AWS region specified in your application/SDK client *must* all be the same. Mismatched regions will cause errors when trying to send MMS messages.

How to troubleshoot AccessDeniedException for sending MMS?

This error indicates missing IAM permissions. Verify your IAM user/role has `sms-voice:SendMediaMessage` and `s3:GetObject` permissions, ensuring the S3 resource ARN in the policy accurately specifies your bucket.

What if I get a ResourceNotFoundException when sending MMS?

This could mean your origination phone number (SNS_ORIGINATION_NUMBER) is incorrect, doesn't exist in the specified AWS region, or the media file specified by the S3 URI (mediaUrl) is not found or the bucket is incorrect.

How to fix ValidationException errors when sending MMS messages?

Check that the `destinationPhoneNumber` is in E.164 format, the `mediaUrl` is a valid S3 URI formatted as an array (`[s3Uri]`), and verify the media file meets AWS size and type limits. Ensure the origination number is also correct.

What are AWS SMS sandbox limitations for sending MMS?

New AWS accounts are often in a sandbox, restricting sending to verified phone numbers. Verify the destination numbers in the AWS SNS console or request to be moved out of the sandbox via AWS Support for production use.