This guide provides a step-by-step walkthrough for building a system where your Next.js application can trigger WhatsApp messages using AWS Lambda as the backend processor and the official Meta (WhatsApp) Business API for delivery. We'll cover everything from setting up your AWS and Meta accounts to deploying and testing the final solution.
This approach enables your application to send notifications, alerts, or other messages directly to users on WhatsApp, leveraging the scalability and security of AWS services. We use AWS Lambda
for serverless execution, AWS Secrets Manager
for securely storing API credentials, and the Meta Graph API
for interacting with WhatsApp.
Project Overview and Goals
Goal: To create a secure and scalable pipeline enabling a Next.js application to send WhatsApp messages via an API call.
Problem Solved: Provides a reliable way to integrate WhatsApp messaging into web applications without managing complex messaging infrastructure directly, suitable for notifications, alerts, or potentially customer service interactions (within Meta's policies).
Technologies:
- Next.js: React framework for the frontend and API route layer.
- Node.js: Runtime environment for
AWS Lambda
. - AWS Lambda: Serverless compute service to run the message sending logic.
- AWS Secrets Manager: Securely store the sensitive WhatsApp API access token.
- AWS IAM: Manage permissions for AWS services.
- Meta for Developers: Platform to configure the WhatsApp Business API integration.
- Meta Graph API: The official API for sending WhatsApp messages.
- Axios: Promise-based HTTP client for making requests from Lambda to the Meta API.
Architecture:
graph LR
A[User via Next.js App] --> B(Next.js API Route);
B -- Invoke Lambda --> C{AWS Lambda Function};
C -- Get Secret --> D[AWS Secrets Manager];
D -- WhatsApp Token --> C;
C -- Send Message Request --> E(Meta Graph API);
E -- Delivers Message --> F[(WhatsApp User)];
style B fill:#D6EAF8,stroke:#333,stroke-width:2px
style C fill:#FAD7A0,stroke:#333,stroke-width:2px
style D fill:#F5B7B1,stroke:#333,stroke-width:2px
style E fill:#ABEBC6,stroke:#333,stroke-width:2px
Prerequisites:
- An AWS account with necessary permissions to create IAM users/roles, Lambda functions, and
Secrets Manager
secrets. - A Meta (Facebook) developer account.
- Node.js and npm (or yarn) installed locally.
- AWS CLI installed and configured locally (optional but recommended for deployment).
- Git installed.
- A test phone number with WhatsApp installed to receive messages.
- Docker installed and running (only required if using AWS CDK for deployment; optional otherwise).
Final Outcome: A Next.js application with an API endpoint (e.g., /api/send-whatsapp
) that accepts a recipient phone number and message text, triggers an AWS Lambda
function, which securely retrieves credentials and sends the message via the Meta API
to the recipient's WhatsApp.
1. Setting up the Infrastructure and Credentials
This section covers the essential groundwork: configuring Meta for WhatsApp API access and setting up AWS resources.
1.1. Configure Meta for Developers and WhatsApp
You need to create a Meta App, enable WhatsApp, generate credentials, and add a test recipient.
-
Create a Meta App:
- Navigate to the Meta for Developers console.
- Click
My Apps
->Create App
. - Select
Other
->Next
. - Select
Business
as the app type ->Next
. - Provide an
App name
(e.g.,MyNextJsWhatsAppSender
), yourApp contact email
. - Optionally, link a
Meta Business Account
. If you don't have one, you might need to create it. Use this term consistently. - Click
Create app
and complete any security checks.
-
Add WhatsApp Product:
- From your app's dashboard, find the
Add products to your app
section. - Locate
WhatsApp
and clickSet up
. - Link it to your Meta Business Account if prompted. Click
Continue
.
- From your app's dashboard, find the
-
Note Phone Number ID and Add Test Recipient:
- You should land on the
WhatsApp
->API Setup
page. - Under
Send and receive messages
, Meta provides a temporary Test Phone Number. Note thePhone number ID
associated with this test number. You'll need it later. - In the
To
field underSend messages with the API
, add your personal phone number (including country code, e.g.,15551234567
) that has WhatsApp installed. ClickSend
. - You'll receive a verification code on WhatsApp. Enter it on the Meta page to confirm your number as a test recipient.
- Important: This test number has limitations (e.g., only sends to verified test recipients initially). For production, you need to register your own business phone number through the Meta platform.
- You should land on the
-
Create a System User and Generate Permanent Token:
- For reliable API access, generate a permanent access token using a system user.
- Navigate to your Meta Business Settings (ensure you're in the correct Meta Business Account linked to your app).
- Under
Users
->System users
, clickAdd
. - Enter a
System user name
(e.g.,whatsapp-api-user
), set theSystem user role
to Admin. ClickCreate system user
.- Note: While 'Admin' role is straightforward, verify if a more restricted custom role granting only the required permissions (
whatsapp_business_management
,whatsapp_business_messaging
) is sufficient for your use case, following the principle of least privilege.
- Note: While 'Admin' role is straightforward, verify if a more restricted custom role granting only the required permissions (
- Select the newly created user and click
Add assets
. - In the popup:
- Select
Apps
in the left column. - Choose the Meta App you created earlier (
MyNextJsWhatsAppSender
). - Enable
Manage App
(Full Control) permissions for this app. - Click
Save Changes
.
- Select
- Click
Generate new token
. - In the popup:
- Select your App.
- Set
Token expiration
to Never. - Under
Available permissions
, select:whatsapp_business_management
whatsapp_business_messaging
- Click
Generate token
.
- CRITICAL: Copy the generated access token immediately and store it somewhere safe temporarily. You cannot view it again after closing the dialog. This is your
WHATSAPP_ACCESS_TOKEN
.
1.2. Set up AWS IAM User (for Deployment/Local SDK use)
Create an IAM user with programmatic access to manage AWS resources if you plan to deploy or test locally using the AWS SDK. If deploying via a CI/CD pipeline with assumed roles, this specific user might not be needed for the pipeline itself.
- Go to the AWS IAM console.
- Navigate to
Users
->Create user
. - Enter a
User name
(e.g.,nextjs-whatsapp-deployer
). - Select
Provide user access to the AWS Management Console - *optional*
if desired. Crucially, ensure you enable Programmatic access if you intend to use AWS CLI/SDK locally with access keys. - Proceed to attach policies directly to grant the necessary permissions.
- Warning: The following policies provide broad access and are suitable only for initial setup or sandbox environments. For production, always follow the principle of least privilege by creating custom policies with only the necessary permissions.
- Attach necessary policies. For deploying Lambda and managing Secrets Manager, you might initially attach:
AWSLambda_FullAccess
(or more restricted permissions likeiam:PassRole
,lambda:CreateFunction
,lambda:UpdateFunctionCode
, etc.)SecretsManagerReadWrite
(or more restricted)IAMFullAccess
(needed to create the Lambda execution role, use with extreme caution and replace with minimal permissions likeiam:CreateRole
,iam:AttachRolePolicy
,iam:GetRole
for the specific role in production)
- Best Practice: Replace these broad policies with a custom policy granting only the minimum required permissions for deploying your specific resources (e.g., creating/updating the target Lambda function, creating/reading the specific secret, creating/passing the specific execution role).
- Click
Next
, add tags (optional), and clickCreate user
. - CRITICAL: On the success screen, copy the Access key ID and Secret access key. Store these securely. These are your
AWS_ACCESS_KEY_ID
andAWS_SECRET_ACCESS_KEY
for local configuration. Configure your AWS CLI with these credentials usingaws configure
.
1.3. Store WhatsApp Token in AWS Secrets Manager
Never hardcode sensitive tokens in your code or Lambda environment variables.
- Go to the AWS Secrets Manager console.
- Click
Store a new secret
. - Select
Other type of secret
. - Under
Secret key/value
:- In the first key field, enter
WHATSAPP_ACCESS_TOKEN
. - In the value field, paste the permanent WhatsApp Access Token you generated earlier.
- Click
Add row
. - In the second key field, enter
WHATSAPP_PHONE_NUMBER_ID
. - In the value field, paste the
Phone Number ID
you noted from the Meta API Setup page.
- In the first key field, enter
- Choose an encryption key (the default
aws/secretsmanager
is usually fine). ClickNext
. - Enter a
Secret name
(e.g.,whatsapp/api/credentials
). Remember this name. Add a description. ClickNext
. - Configure automatic rotation if desired (not applicable for permanent tokens unless Meta changes policy). Click
Next
. - Review and click
Store
. - Note the ARN of the newly created secret. You'll need it for the Lambda function's permissions and environment variables (e.g.,
arn:aws:secretsmanager:us-east-1:123456789012:secret:whatsapp/api/credentials-XXXXXX
).
1.4. Set up Next.js Project
Initialize a new Next.js project and install dependencies. We will use the AWS SDK for JavaScript v3 throughout for consistency.
-
Create Project:
npx create-next-app@latest my-whatsapp-app cd my-whatsapp-app
(Choose options like TypeScript, App Router, etc., as desired. This guide assumes JavaScript and Pages Router for simplicity, but concepts apply to App Router).
-
Install Dependencies (for Next.js API Route):
@aws-sdk/client-lambda
: AWS SDK v3 client for invoking Lambda functions from your Next.js API route.axios
: For making HTTP requests (if needed elsewhere in Next.js, not directly used in the API route example).dotenv
: To manage environment variables locally.
npm install @aws-sdk/client-lambda axios dotenv
Note: The AWS Lambda function itself will also need dependencies (
@aws-sdk/client-secrets-manager
,axios
). These must be included in the Lambda deployment package (see Section 2), not necessarily installed in the Next.js project'snode_modules
. -
Configure Environment Variables (Local): Create a file named
.env.local
in the root of your Next.js project. Do not commit this file to Git.# For Next.js API Route to invoke Lambda (use credentials from `aws configure`) AWS_REGION="us-east-1" # Replace with your AWS region LAMBDA_FUNCTION_NAME="SendWhatsAppMessageLambda" # Choose a name for your Lambda NEXT_PUBLIC_API_BASE_URL="/api" # Or your full backend URL if deployed separately # Note: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are typically picked up # automatically by the SDK if configured via `aws configure` or environment variables. # Avoid storing them directly in .env.local if possible. # Variables needed *inside* the Lambda function (will be set in Lambda config, not here) # SECRET_ARN="arn:aws:secretsmanager:us-east-1:123456789012:secret:whatsapp/api/credentials-XXXXXX"
AWS_REGION
: The AWS region where you created yourSecrets Manager
secret and will deploy your Lambda.LAMBDA_FUNCTION_NAME
: The name you will give your Lambda function in AWS.SECRET_ARN
(For Lambda): The ARN of the secret stored inSecrets Manager
. This will be configured directly in the Lambda environment settings, not in the Next.js.env.local
.
2. Implementing Core Functionality (AWS Lambda)
This Lambda function retrieves the WhatsApp token from Secrets Manager
and calls the Meta API
. It uses the AWS SDK v3.
-
Create Lambda Function:
- Go to the AWS Lambda console.
- Click
Create function
. - Select
Author from scratch
. - Function name:
SendWhatsAppMessageLambda
(or the name you chose in.env.local
). - Runtime:
Node.js 18.x
(or a later supported version that includes AWS SDK v3, or bundle it). - Architecture:
x86_64
(orarm64
). - Permissions:
- Click
Change default execution role
. - Select
Create a new role with basic Lambda permissions
. Name it something descriptive likeSendWhatsAppMessageLambdaRole
. - (We will add Secrets Manager permissions next).
- Click
- Click
Create function
.
-
Add Secrets Manager Permissions to Lambda Role:
- After the function is created, go to the
Configuration
->Permissions
tab. - Click on the
Execution role
name (e.g.,SendWhatsAppMessageLambdaRole
). This opens the IAM console. - Click
Add permissions
->Attach policies
. - Search for
SecretsManagerReadWrite
policy. Warning: This grants write access too. For better security, create a custom inline policy instead. - Better (Inline Policy): Click
Add permissions
->Create inline policy
.- Select Service:
Secrets Manager
. - Actions: Expand
Read
-> SelectGetSecretValue
. - Resources: Click
Add ARN
. Paste the ARN of the secret you created earlier (arn:aws:secretsmanager:REGION:ACCOUNT_ID:secret:whatsapp/api/credentials-XXXXXX
). ClickAdd ARNs
. - Click
Review policy
. Give it a name (e.g.,AllowReadWhatsAppSecret
). ClickCreate policy
.
- Select Service:
- Ensure the role also has basic CloudWatch Logs permissions (
AWSLambdaBasicExecutionRole
policy is usually attached by default and includes this:logs:CreateLogGroup
,logs:CreateLogStream
,logs:PutLogEvents
).
- After the function is created, go to the
-
Configure Lambda Environment Variables:
- In the Lambda function console, go to
Configuration
->Environment variables
. - Click
Edit
. - Click
Add environment variable
. - Key:
SECRET_ARN
, Value: Paste the ARN of your secret inSecrets Manager
. - Key:
AWS_NODEJS_CONNECTION_REUSE_ENABLED
, Value:1
(Good practice for performance). - Click
Save
.
- In the Lambda function console, go to
-
Write Lambda Function Code (
index.mjs
):- Go to the
Code
tab in your Lambda function. - Replace the contents of
index.mjs
with the following code. This uses Node.js 18.x features and AWS SDK v3. - Important: Since we are using external libraries (
@aws-sdk/client-secrets-manager
,axios
), they must be included in your Lambda deployment package (e.g., in anode_modules
folder within the zip file).
// filename: index.mjs (Lambda function code) import { SecretsManagerClient, GetSecretValueCommand } from ""@aws-sdk/client-secrets-manager""; import axios from 'axios'; // Make sure axios is included in your deployment package const secretsManagerClient = new SecretsManagerClient({}); const secretArn = process.env.SECRET_ARN; let cachedSecret; // Simple cache for the secret // Helper function to get secrets (with simple caching) async function getWhatsAppCredentials() { if (cachedSecret) { console.log(""Using cached secret""); return cachedSecret; } console.log(`Fetching secret from ARN: ${secretArn}`); if (!secretArn) { throw new Error(""SECRET_ARN environment variable not set.""); } try { const command = new GetSecretValueCommand({ SecretId: secretArn }); const data = await secretsManagerClient.send(command); if (!data.SecretString) { throw new Error(""SecretString not found in secret.""); } console.log(""Secret fetched successfully.""); cachedSecret = JSON.parse(data.SecretString); // Assuming secret stored as JSON key/value return cachedSecret; } catch (error) { console.error(""Error fetching secret from Secrets Manager:"", error); throw new Error(""Could not retrieve WhatsApp credentials.""); } } export const handler = async (event) => { console.log(""Received event:"", JSON.stringify(event, null, 2)); // 1. Extract recipient number and message from the invocation event // We expect the invoking service (Next.js API) to send this in the payload. let recipientPhoneNumber; let messageBody; // Check if the payload is coming directly or via API Gateway proxy integration const body = event.body ? JSON.parse(event.body) : event; recipientPhoneNumber = body.to; messageBody = body.message; if (!recipientPhoneNumber || !messageBody) { console.error(""Missing 'to' or 'message' in request body.""); return { statusCode: 400, body: JSON.stringify({ error: ""Missing 'to' (recipient phone number) or 'message' in request."" }), headers: { 'Content-Type': 'application/json' } }; } // Basic validation - only checks for digits. For production, use a robust library like libphonenumber-js for proper E.164 format validation. if (!/^\d+$/.test(recipientPhoneNumber)) { console.error(""Invalid phone number format.""); return { statusCode: 400, body: JSON.stringify({ error: ""Invalid phone number format. Should contain only digits (including country code without '+')."" }), headers: { 'Content-Type': 'application/json' } }; } try { // 2. Get WhatsApp Credentials from Secrets Manager const credentials = await getWhatsAppCredentials(); const accessToken = credentials.WHATSAPP_ACCESS_TOKEN; const phoneNumberId = credentials.WHATSAPP_PHONE_NUMBER_ID; if (!accessToken || !phoneNumberId) { throw new Error(""WHATSAPP_ACCESS_TOKEN or WHATSAPP_PHONE_NUMBER_ID missing from secret.""); } // 3. Construct Meta API Request // Note: Periodically check and update the Meta Graph API version (e.g., v19.0) according to Meta's versioning policy. const apiUrl = `https://graph.facebook.com/v19.0/${phoneNumberId}/messages`; const headers = { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', }; const data = { messaging_product: ""whatsapp"", to: recipientPhoneNumber, // Needs to be E.164 format ideally, check Meta docs type: ""text"", text: { body: messageBody, }, }; console.log(`Sending message to ${recipientPhoneNumber} via Meta API...`); // 4. Send Request using Axios const response = await axios.post(apiUrl, data, { headers: headers }); console.log('Meta API Response Status:', response.status); console.log('Meta API Response Data:', response.data); // 5. Return Success Response return { statusCode: 200, body: JSON.stringify({ success: true, messageId: response.data.messages[0]?.id || 'N/A' }), headers: { 'Content-Type': 'application/json' } }; } catch (error) { console.error('Error processing request:', error); let statusCode = 500; let errorMessage = ""Internal Server Error sending WhatsApp message.""; if (axios.isAxiosError(error) && error.response) { // Error from Meta API console.error('Meta API Error Status:', error.response.status); console.error('Meta API Error Data:', error.response.data); statusCode = error.response.status >= 500 ? 502 : 400; // Treat Meta server errors as 502 Bad Gateway, client errors as 400 errorMessage = error.response.data?.error?.message || ""Error interacting with WhatsApp API.""; } else if (error.message.includes(""credential"") || error.message.includes(""SECRET_ARN"")) { // Error fetching secrets statusCode = 500; errorMessage = ""Failed to retrieve necessary credentials.""; } return { statusCode: statusCode, body: JSON.stringify({ success: false, error: errorMessage }), headers: { 'Content-Type': 'application/json' } }; } };
- Go to the
-
Deploy Lambda Code:
- Manual Upload:
- Create a directory (e.g.,
lambda-package
). - Place your
index.mjs
file inside it. - Navigate into the directory:
cd lambda-package
. - Install production dependencies:
npm init -y && npm install @aws-sdk/client-secrets-manager axios --save-prod
. - Go back one level:
cd ..
. - Create a deployment package:
zip -r function.zip lambda-package
. - In the Lambda console (
Code
tab), clickUpload from
->.zip file
. Upload yourfunction.zip
.
- Create a directory (e.g.,
- Using AWS CLI (example):
# Assuming you followed the manual steps above to create function.zip aws lambda update-function-code \ --function-name SendWhatsAppMessageLambda \ --zip-file fileb://function.zip \ --region us-east-1 # Your region rm function.zip rm -rf lambda-package # Clean up
- Using Serverless Framework/SAM/CDK: These tools provide more robust deployment mechanisms that handle dependency bundling automatically (recommended for production).
- Manual Upload:
3. Building the API Layer (Next.js API Route)
This API route in your Next.js app will receive requests from the frontend and invoke the Lambda function using AWS SDK v3.
-
Create the API Route File: Create
pages/api/send-whatsapp.js
(orapp/api/send-whatsapp/route.ts
if using App Router).// filename: pages/api/send-whatsapp.js (for Pages Router) import { LambdaClient, InvokeCommand } from "@aws-sdk/client-lambda"; // Initialize Lambda client - credentials should be picked up from environment // (via `aws configure` locally or IAM role on Vercel/AWS Amplify) const lambdaClient = new LambdaClient({ region: process.env.AWS_REGION, }); const lambdaFunctionName = process.env.LAMBDA_FUNCTION_NAME; export default async function handler(req, res) { if (req.method !== 'POST') { res.setHeader('Allow', ['POST']); return res.status(405).json({ error: `Method ${req.method} Not Allowed` }); } if (!lambdaFunctionName) { console.error("LAMBDA_FUNCTION_NAME environment variable is not set."); return res.status(500).json({ success: false, error: "Server configuration error." }); } try { const { to, message } = req.body; // Basic Input Validation if (!to || !message) { return res.status(400).json({ success: false, error: "Missing 'to' (recipient phone number) or 'message' in request body." }); } // Add more robust validation as needed (e.g., phone number format, message length) // Prepare payload for Lambda invocation const invokeParams = { FunctionName: lambdaFunctionName, Payload: JSON.stringify({ // Lambda expects this structure now to: to, message: message }), InvocationType: 'RequestResponse', // Synchronous invocation LogType: 'Tail', // Include execution logs in the response (optional) }; console.log(`Invoking Lambda function: ${lambdaFunctionName}`); const command = new InvokeCommand(invokeParams); const lambdaResponse = await lambdaClient.send(command); console.log("Lambda invocation response status:", lambdaResponse.StatusCode); // Decode the payload from Uint8Array const payloadString = Buffer.from(lambdaResponse.Payload).toString('utf-8'); const payloadJson = JSON.parse(payloadString); // Check for Lambda function errors (different from invocation errors) if (lambdaResponse.FunctionError) { console.error("Lambda function returned an error:", payloadJson); // Attempt to return the error message from Lambda if available const errorMessage = payloadJson?.error || payloadJson?.errorMessage || "Lambda function execution failed."; return res.status(502).json({ success: false, error: `Lambda Error: ${errorMessage}` }); // 502 Bad Gateway } // Check the status code returned *within* the Lambda's response body if (payloadJson.statusCode && payloadJson.statusCode >= 400) { console.error(`Lambda returned status code ${payloadJson.statusCode}:`, payloadJson.body); // Try to parse the body if it's stringified JSON let lambdaErrorBody = {}; try { lambdaErrorBody = JSON.parse(payloadJson.body); } catch (parseError) { console.warn("Could not parse Lambda error body:", payloadJson.body); } const errorMessage = lambdaErrorBody?.error || "Error processing request in Lambda."; // Map Lambda's status code if possible, default to 500 or 400 const clientStatusCode = payloadJson.statusCode === 400 ? 400 : 500; return res.status(clientStatusCode).json({ success: false, error: errorMessage }); } console.log("Lambda response payload:", payloadJson); // Successfully invoked Lambda, and Lambda returned success // Assuming Lambda returns { statusCode: 200, body: '{ "success": true, ... }' } let lambdaBody = {}; try { lambdaBody = JSON.parse(payloadJson.body); } catch (parseError) { console.error("Could not parse successful Lambda response body:", payloadJson.body); return res.status(500).json({ success: false, error: "Invalid response format from backend service." }); } return res.status(200).json({ success: true, data: lambdaBody }); } catch (error) { console.error("Error invoking Lambda function:", error); // Handle SDK errors (e.g., permissions, function not found) return res.status(500).json({ success: false, error: "Failed to invoke backend service.", details: error.message }); } }
-
Testing the API Route: Once your Next.js app is running locally (
npm run dev
), you can test the API endpoint usingcurl
or a tool like Postman. ReplaceYOUR_TEST_PHONE_NUMBER
with the number you verified in Meta (including country code, no+
or spaces).curl -X POST http://localhost:3000/api/send-whatsapp \ -H "Content-Type: application/json" \ -d '{ "to": "YOUR_TEST_PHONE_NUMBER", "message": "Hello from Next.js and AWS Lambda!" }'
You should receive a JSON response indicating success or failure, and a message should appear on your test WhatsApp number. Check your Lambda's CloudWatch logs for detailed execution information.
Sections 4-10: Production Considerations (Summarized)
The previous sections cover the core implementation. For a production-ready system, consider these crucial aspects:
- 4. Integrating with Third-Party Services: Primarily covered via Meta setup and secure credential handling with
Secrets Manager
. Ensure you understand Meta's API versioning and update your Lambda code accordingly (e.g., thev19.0
in the URL). - 5. Error Handling, Logging, Retry Mechanisms:
- Enhanced Logging: Add more detailed, structured (JSON) logs in both Next.js and Lambda, including request IDs, relevant identifiers (like user ID if applicable), and specific error codes from downstream services (Meta API, AWS SDK).
- Consistent Errors: Define standard error response formats from your API route.
- Retries: Implement retries carefully. Retrying
Meta API
calls might lead to duplicate messages. Consider retries only for transient network errors or AWS service issues (likeSecrets Manager
throttling or Lambda invocation errors), possibly with exponential backoff. For persistent Lambda failures, consider using Lambda Destinations (like an SQS queue or another Lambda) to handle failed event payloads for later inspection or reprocessing.
- 6. Database Schema and Data Layer: Not directly part of sending, but a real app would likely store message status (requires webhooks), history, user opt-out preferences, etc., in a database (e.g., DynamoDB for serverless patterns, or a relational DB).
- 7. Security Features:
- API Authentication/Authorization: Protect your
/api/send-whatsapp
endpoint. Ensure only authenticated and authorized users/systems can trigger messages (e.g., using NextAuth.js, Clerk, or API keys validated server-side). - Input Validation: Rigorously validate phone numbers in the API route before invoking Lambda (use libraries like
libphonenumber-js
for proper E.164 validation). Sanitize message content to prevent injection attacks if user input forms part of the message. - Rate Limiting: Implement rate limiting on the Next.js API endpoint (e.g., using middleware like
express-rate-limit
withnext-connect
, Vercel's built-in features, or anAPI Gateway
if used) to prevent abuse, manage costs, and respectMeta API
limits. - IAM Least Privilege: Ensure the Lambda execution role and any deployment users/roles have only the minimum permissions required. Regularly audit permissions.
- API Authentication/Authorization: Protect your
- 8. Handling Special Cases:
- WhatsApp Message Templates: This is non-negotiable for production. For business-initiated conversations outside the 24-hour customer service window, or for sending notifications, you must use pre-approved Message Templates. Update the Lambda function to accept a template name and variables, constructing the appropriate JSON payload for the
Meta API
(includingnamespace
,name
,language
, andcomponents
with parameters). See Meta documentation on templates. Sending free-form text is highly restricted. - Phone Number Formatting: Consistently handle and validate E.164 format (
+
followed by country code and number). - Opt-Outs: Implement logic to respect user opt-out requests (required by Meta policy and privacy regulations). Store opt-out status (e.g., in your database) and check before sending any message.
- Internationalization: If sending messages globally, manage different languages using approved templates with language packs.
- WhatsApp Message Templates: This is non-negotiable for production. For business-initiated conversations outside the 24-hour customer service window, or for sending notifications, you must use pre-approved Message Templates. Update the Lambda function to accept a template name and variables, constructing the appropriate JSON payload for the
- 9. Performance Optimizations:
- Lambda: Enable provisioned concurrency only if consistent low latency is critical and cold starts are problematic. Optimize Lambda package size. Cache secrets within the Lambda execution context (as shown in the example).
- Next.js: Optimize API route performance. Consider edge functions if latency is paramount and dependencies allow.
- 10. Deployment and CI/CD:
- Use Infrastructure as Code (IaC) tools like AWS CDK, SAM, Serverless Framework, or Terraform to manage AWS resources reproducibly.
- Set up a CI/CD pipeline (e.g., GitHub Actions, GitLab CI, AWS CodePipeline) to automate testing, building, and deploying both the Next.js application and the Lambda function. Manage environment variables and secrets securely within the CI/CD process.