This comprehensive guide walks you through building a Next.js application that sends SMS messages using the MessageBird API and logs message data to Supabase. You'll create a production-ready API route with proper error handling, validation, and database integration—perfect for building SMS notifications, two-factor authentication, or transactional messaging systems.
Next.js: React framework with built-in API routes, perfect for full-stack applications without separate backend infrastructure
Supabase: Open-source Firebase alternative providing PostgreSQL database with real-time capabilities and generous free tier
By the end of this tutorial, you'll have a functional Next.js API endpoint at /api/send-sms that accepts SMS requests, sends them via MessageBird, and logs all transactions to a Supabase database for tracking and analytics.
Key Technologies:
Node.js 18+: JavaScript runtime (Next.js requires Node.js 18.17 or later per Next.js documentation)
Next.js 14+: React framework with API Routes for backend functionality
MessageBird SMS API: RESTful API for sending SMS messages worldwide via the messagebird Node.js SDK
[Client (Browser/cURL)]
|
| HTTP POST Request (/api/send-sms)
v
[Next.js API Route]
|
|-- Validates input
|-- Initializes MessageBird SDK
v
[MessageBird API] ----> [Sends SMS to recipient]
|
|-- On success
v
[Supabase Client] ----> [Logs message to PostgreSQL database]
|
v
[Returns response to client]
MESSAGEBIRD_API_KEY: Your MessageBird access key from the API Access page. Test keys have the test_ prefix.
MESSAGEBIRD_ORIGINATOR: The sender ID (phone number in E.164 format like +14155550100 or alphanumeric string up to 11 characters). Note: Alphanumeric senders aren't supported in all countries including the United States.
NEXT_PUBLIC_SUPABASE_ANON_KEY: Your Supabase anonymous public key (safe for client-side use with RLS policies)
Security Note: The NEXT_PUBLIC_ prefix makes variables accessible in the browser. Only use this for public keys. Keep MESSAGEBIRD_API_KEY without the prefix to ensure it stays server-side only.
Update .gitignore: Next.js automatically includes .env.local in .gitignore, but verify it contains:
Before writing code, create a database table to store SMS message logs.
Creating the SMS Logs Table
Navigate to SQL Editor: In your Supabase Dashboard, go to the SQL Editor section.
Create Table with SQL: Run this SQL to create an sms_logs table with proper indexes:
SQL
-- Create SMS logs tableCREATE TABLE IF NOTEXISTS public.sms_logs (
id UUID PRIMARY KEYDEFAULT gen_random_uuid(),
recipient TEXT NOT NULL,
message TEXT NOT NULL,
originator TEXT NOT NULL,
message_id TEXT,
status TEXT NOT NULLDEFAULT'sent',
created_at TIMESTAMPWITHTIME ZONE DEFAULT NOW(),
updated_at TIMESTAMPWITHTIME ZONE DEFAULT NOW()
);
-- Add index for faster queries by recipient and dateCREATE INDEX idx_sms_logs_recipient ON public.sms_logs(recipient);
CREATE INDEX idx_sms_logs_created_at ON public.sms_logs(created_at DESC);
-- Enable Row Level SecurityALTER TABLE public.sms_logs ENABLE ROW LEVEL SECURITY;
-- Create policy to allow service role to insert (for API route)CREATE POLICY "Enable insert for service role" ON public.sms_logs
FORINSERTWITHCHECK (true);
-- Create policy to allow authenticated users to read their own logsCREATE POLICY "Enable read for all users" ON public.sms_logs
FORSELECTUSING (true);
Schema Explanation:
id: UUID primary key, automatically generated
recipient: Phone number that received the SMS (E.164 format)
message: Content of the SMS sent
originator: Sender ID used (your MessageBird number)
message_id: MessageBird's unique message identifier for tracking
status: Message status (sent, failed, etc.)
created_at/updated_at: Timestamps for record tracking
Verify Table Creation: Go to the Table Editor in your Supabase Dashboard and confirm the sms_logs table exists with the correct columns.
Security Note: Row Level Security (RLS) is enabled to control access. The policies allow API routes (using service role key) to insert and all users to read. Adjust policies based on your security requirements.
3. Creating the Next.js API Route for SMS Sending
Next.js API Routes provide serverless functions for backend logic. Any file in pages/api/ becomes an API endpoint.
Create the API Route File
Create API Directory Structure: If not already present, create the pages/api folder:
// pages/api/send-sms.tsimporttype { NextApiRequest, NextApiResponse } from'next';
import messagebird from'messagebird';
import { createClient } from'@supabase/supabase-js';
// Type definitions for better type safetytypeResponseData = {
success: boolean;
message: string;
messageId?: string;
error?: string;
};
typeSMSRequestBody = {
to: string;
text: string;
};
// Initialize MessageBird client// Per MessageBird documentation: https://developers.messagebird.com/tutorials/send-sms-nodeconst messagebirdClient = messagebird(process.env.MESSAGEBIRD_API_KEYasstring);
// Initialize Supabase client// Per Supabase documentation: https://supabase.com/docs/guides/getting-started/quickstarts/nextjsconst supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URLasstring,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEYasstring
);
exportdefaultasyncfunctionhandler(req: NextApiRequest,
res: NextApiResponse<ResponseData>
) {
// Only accept POST requestsif (req.method !== 'POST') {
return res.status(405).json({
success: false,
message: 'Method not allowed. Use POST.',
});
}
const { to, text } = req.bodyasSMSRequestBody;
// Input validationif (!to || !text) {
return res.status(400).json({
success: false,
message: 'Missing required fields: "to" (recipient) or "text" (message content).',
});
}
// Validate E.164 phone number format// E.164 format: + followed by 1-15 digitsconst e164Regex = /^\+[1-9]\d{1,14}$/;
if (!e164Regex.test(to)) {
return res.status(400).json({
success: false,
message: 'Invalid phone number format. Use E.164 format (e.g., +14155550100).',
});
}
// Validate message length (SMS limit is 160 characters per segment)if (text.length === 0) {
return res.status(400).json({
success: false,
message: 'Message text cannot be empty.',
});
}
try {
// Send SMS via MessageBird// API Reference: https://developers.messagebird.com/api/const messagebirdResponse = awaitnewPromise<any>((resolve, reject) => {
messagebirdClient.messages.create(
{
originator: process.env.MESSAGEBIRD_ORIGINATORasstring,
recipients: [to],
body: text,
},
(err, response) => {
if (err) {
reject(err);
} else {
resolve(response);
}
}
);
});
// Log to Supabase databaseconst { data: logData, error: logError } = await supabase
.from('sms_logs')
.insert([
{
recipient: to,
message: text,
originator: process.env.MESSAGEBIRD_ORIGINATOR,
message_id: messagebirdResponse.id,
status: 'sent',
},
])
.select()
.single();
if (logError) {
console.error('Supabase logging error:', logError);
// Don't fail the request if logging fails, but log the error
}
return res.status(200).json({
success: true,
message: 'SMS sent successfully',
messageId: messagebirdResponse.id,
});
} catch (error: any) {
console.error('Error sending SMS:', error);
// Log failed attempt to Supabaseawait supabase.from('sms_logs').insert([
{
recipient: to,
message: text,
originator: process.env.MESSAGEBIRD_ORIGINATOR,
status: 'failed',
},
]);
// Handle MessageBird-specific errors// Error codes reference: https://developers.messagebird.com/api/const errorMessage =
error.errors && error.errors[0]
? `MessageBird Error: ${error.errors[0].description}`
: 'Failed to send SMS';
return res.status(500).json({
success: false,
message: errorMessage,
error: error.message,
});
}
}
Code Walkthrough:
Client Initialization: Initialize the MessageBird client once at module level per SDK best practices. The Supabase client uses environment variables from .env.local.
Method Check: Next.js API routes handle all HTTP methods. We restrict this endpoint to POST only.
Input Validation: Validates required fields and E.164 phone format. E.164 is the international standard format required by MessageBird.
SMS Sending: Uses messages.create() method from MessageBird SDK with callback-style API wrapped in Promise for async/await compatibility.
Database Logging: Inserts success/failure record to Supabase. Non-blocking error handling ensures SMS delivery isn't blocked by logging failures.
Error Handling: Catches MessageBird API errors and returns structured error responses with proper HTTP status codes (400 for validation, 500 for server errors).
4. Testing Your SMS Integration
Start the Development Server
Run Next.js Development Server:
Bash
npm run dev
Next.js displays output confirming the server is running:
Code
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
Verify Environment Variables: Check the terminal for any errors about missing environment variables. If you see errors, verify your .env.local file.
Test with cURL
Send Test SMS: Open a new terminal and run:
Bash
curl -X POST http://localhost:3000/api/send-sms \
-H "Content-Type: application/json" \
-d '{
"to": "+1XXXXXXXXXX",
"text": "Hello from MessageBird and Next.js!"
}'
Replace +1XXXXXXXXXX with a valid phone number in E.164 format. For MessageBird test accounts, verify the destination number first in your dashboard.
Expected Success Response:
JSON
{"success":true,"message":"SMS sent successfully","messageId":"abc123def456"}
The RLS policies created earlier protect your database. For production:
SQL
-- Restrict reads to authenticated users onlyDROP POLICY IF EXISTS "Enable read for all users" ON public.sms_logs;
CREATE POLICY "Enable read for authenticated users" ON public.sms_logs
FORSELECTUSING (auth.role() ='authenticated');
-- Restrict inserts to service role only (API routes)-- Service role key should be used server-side only
Store the service role key separately for API routes:
CREATE TABLE sms_templates (
id UUID PRIMARY KEYDEFAULT gen_random_uuid(),
name TEXT NOT NULL,
body TEXT NOT NULL,
variables JSONB,
created_at TIMESTAMPWITHTIME ZONE DEFAULT NOW()
);
Verification Checklist
Next.js project created with npx create-next-app
MessageBird and Supabase packages installed
.env.local configured with all required keys
Supabase sms_logs table created with RLS policies
API route created at pages/api/send-sms.ts
Development server runs without errors (npm run dev)
Test SMS sent successfully via cURL
SMS received on test phone number
Message logged in Supabase sms_logs table
Error handling tested (invalid phone format, missing fields)
MessageBird dashboard shows sent message
Production environment variables configured in deployment platform
Conclusion
You now have a production-ready Next.js application that sends SMS messages via MessageBird and logs transactions to Supabase. This foundation supports building notification systems, two-factor authentication, marketing campaigns, and customer communication platforms.
Use the Vonage Messages API with the Vonage Node.js SDK. Set up an Express route to handle requests, then use the SDK to send SMS messages via the API. This allows you to integrate SMS functionality directly into your Node.js applications.
The Vonage Messages API is a service that allows you to send and receive messages through different channels like SMS, WhatsApp, and more. This tutorial focuses on using it to send SMS notifications from your Node.js app.
Dotenv loads environment variables from a .env file. This keeps sensitive credentials like your Vonage API key and secret out of your source code, which improves security and prevents accidental exposure.
While the provided Node.js code uses the Messages API directly, it's beneficial to set the default SMS API in your Vonage account settings to the Messages API for consistency, especially if you plan to implement features involving webhook functionality, as webhook formats differ between the legacy SMS API and the Messages API.
No, trial accounts have restrictions. You can only send SMS messages to numbers you've verified in your Vonage Dashboard under 'Getting Started' > 'Add test numbers'. For unrestricted sending, you'll need to upgrade to a paid account.
Use npm install express @vonage/server-sdk dotenv. This command installs Express for the web framework, the Vonage Server SDK to interface with the Vonage API, and dotenv to securely manage environment variables.
The .gitignore file specifies files and directories that Git should ignore when tracking changes. It’s crucial to add node_modules and .env to your .gitignore to prevent accidentally committing dependencies and your sensitive Vonage API credentials.
First, install it with npm, then initialize it with your API key and secret, which you obtain from the Vonage API Dashboard. These credentials are loaded into your project from the '.env' file via dotenv.
E.164 is an international standard for phone number formatting. It includes a '+' sign followed by the country code and the national subscriber number without any spaces or special characters. For example, a US number would be +14155550100.
Create a POST route handler in your Express app (e.g., app.post('/send-sms', ...)). This route will receive the recipient's phone number and the message text from a request. Use this data to send the SMS using the Vonage SDK.
A 400 Bad Request error indicates an issue with the client's request, typically due to missing required fields or invalid formatting. Ensure the request body contains 'to' and 'text' fields, and that the 'to' number follows E.164 format.
After starting your server, use a command like curl -X POST -H "Content-Type: application/json" -d '{"to": "+1XXXXXXXXXX", "text": "Test message"}' http://localhost:3000/send-sms. Replace +1XXXXXXXXXX with a valid E.164 formatted phone number and 'Test message' with your desired SMS content.
This usually occurs with trial Vonage accounts. Ensure the recipient's phone number is added to your allowed list of test numbers in the Vonage Dashboard. If you need to send to any number, upgrade to a paid account.
Use a try-catch block around your API calls to handle errors. Log these errors for debugging and return appropriate error responses to the client. For production, consider using a logging framework like Winston or Pino.
This comprehensive guide walks you through building a Next.js application that sends SMS messages using the MessageBird API and logs message data to Supabase. You'll create a production-ready API route with proper error handling, validation, and database integration—perfect for building SMS notifications, two-factor authentication, or transactional messaging systems.
Why This Stack?
By the end of this tutorial, you'll have a functional Next.js API endpoint at
/api/send-smsthat accepts SMS requests, sends them via MessageBird, and logs all transactions to a Supabase database for tracking and analytics.Key Technologies:
System Architecture:
Prerequisites:
1. Setting Up Your Next.js Project with MessageBird and Supabase
Next.js provides an optimized setup command that scaffolds a complete project structure.
Create Next.js Project: Open your terminal and run the official Next.js creation command:
When prompted, select these options:
Navigate to Project Directory:
cd messagebird-sms-appInstall Required Dependencies: Add MessageBird SDK and Supabase client:
messagebird: Official MessageBird Node.js SDK for SMS API integration@supabase/supabase-js: Supabase JavaScript client for database operationsCreate Environment Variables File: Next.js uses
.env.localfor environment variables that you should never commit:touch .env.localConfigure
.env.local: Add your API credentials (replace placeholders with actual values from your dashboards):Variable Explanations:
MESSAGEBIRD_API_KEY: Your MessageBird access key from the API Access page. Test keys have thetest_prefix.MESSAGEBIRD_ORIGINATOR: The sender ID (phone number in E.164 format like+14155550100or alphanumeric string up to 11 characters). Note: Alphanumeric senders aren't supported in all countries including the United States.NEXT_PUBLIC_SUPABASE_URL: Your Supabase project URL from Project Settings > APINEXT_PUBLIC_SUPABASE_ANON_KEY: Your Supabase anonymous public key (safe for client-side use with RLS policies)Security Note: The
NEXT_PUBLIC_prefix makes variables accessible in the browser. Only use this for public keys. KeepMESSAGEBIRD_API_KEYwithout the prefix to ensure it stays server-side only.Update
.gitignore: Next.js automatically includes.env.localin.gitignore, but verify it contains:2. Setting Up Supabase Database
Before writing code, create a database table to store SMS message logs.
Creating the SMS Logs Table
Navigate to SQL Editor: In your Supabase Dashboard, go to the SQL Editor section.
Create Table with SQL: Run this SQL to create an
sms_logstable with proper indexes:-- Create SMS logs table CREATE TABLE IF NOT EXISTS public.sms_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), recipient TEXT NOT NULL, message TEXT NOT NULL, originator TEXT NOT NULL, message_id TEXT, status TEXT NOT NULL DEFAULT 'sent', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- Add index for faster queries by recipient and date CREATE INDEX idx_sms_logs_recipient ON public.sms_logs(recipient); CREATE INDEX idx_sms_logs_created_at ON public.sms_logs(created_at DESC); -- Enable Row Level Security ALTER TABLE public.sms_logs ENABLE ROW LEVEL SECURITY; -- Create policy to allow service role to insert (for API route) CREATE POLICY "Enable insert for service role" ON public.sms_logs FOR INSERT WITH CHECK (true); -- Create policy to allow authenticated users to read their own logs CREATE POLICY "Enable read for all users" ON public.sms_logs FOR SELECT USING (true);Schema Explanation:
id: UUID primary key, automatically generatedrecipient: Phone number that received the SMS (E.164 format)message: Content of the SMS sentoriginator: Sender ID used (your MessageBird number)message_id: MessageBird's unique message identifier for trackingstatus: Message status (sent, failed, etc.)created_at/updated_at: Timestamps for record trackingVerify Table Creation: Go to the Table Editor in your Supabase Dashboard and confirm the
sms_logstable exists with the correct columns.Security Note: Row Level Security (RLS) is enabled to control access. The policies allow API routes (using service role key) to insert and all users to read. Adjust policies based on your security requirements.
3. Creating the Next.js API Route for SMS Sending
Next.js API Routes provide serverless functions for backend logic. Any file in
pages/api/becomes an API endpoint.Create the API Route File
Create API Directory Structure: If not already present, create the pages/api folder:
mkdir -p pages/apiCreate
send-sms.tsRoute Handler: Createpages/api/send-sms.ts:// pages/api/send-sms.ts import type { NextApiRequest, NextApiResponse } from 'next'; import messagebird from 'messagebird'; import { createClient } from '@supabase/supabase-js'; // Type definitions for better type safety type ResponseData = { success: boolean; message: string; messageId?: string; error?: string; }; type SMSRequestBody = { to: string; text: string; }; // Initialize MessageBird client // Per MessageBird documentation: https://developers.messagebird.com/tutorials/send-sms-node const messagebirdClient = messagebird(process.env.MESSAGEBIRD_API_KEY as string); // Initialize Supabase client // Per Supabase documentation: https://supabase.com/docs/guides/getting-started/quickstarts/nextjs const supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL as string, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY as string ); export default async function handler( req: NextApiRequest, res: NextApiResponse<ResponseData> ) { // Only accept POST requests if (req.method !== 'POST') { return res.status(405).json({ success: false, message: 'Method not allowed. Use POST.', }); } const { to, text } = req.body as SMSRequestBody; // Input validation if (!to || !text) { return res.status(400).json({ success: false, message: 'Missing required fields: "to" (recipient) or "text" (message content).', }); } // Validate E.164 phone number format // E.164 format: + followed by 1-15 digits const e164Regex = /^\+[1-9]\d{1,14}$/; if (!e164Regex.test(to)) { return res.status(400).json({ success: false, message: 'Invalid phone number format. Use E.164 format (e.g., +14155550100).', }); } // Validate message length (SMS limit is 160 characters per segment) if (text.length === 0) { return res.status(400).json({ success: false, message: 'Message text cannot be empty.', }); } try { // Send SMS via MessageBird // API Reference: https://developers.messagebird.com/api/ const messagebirdResponse = await new Promise<any>((resolve, reject) => { messagebirdClient.messages.create( { originator: process.env.MESSAGEBIRD_ORIGINATOR as string, recipients: [to], body: text, }, (err, response) => { if (err) { reject(err); } else { resolve(response); } } ); }); // Log to Supabase database const { data: logData, error: logError } = await supabase .from('sms_logs') .insert([ { recipient: to, message: text, originator: process.env.MESSAGEBIRD_ORIGINATOR, message_id: messagebirdResponse.id, status: 'sent', }, ]) .select() .single(); if (logError) { console.error('Supabase logging error:', logError); // Don't fail the request if logging fails, but log the error } return res.status(200).json({ success: true, message: 'SMS sent successfully', messageId: messagebirdResponse.id, }); } catch (error: any) { console.error('Error sending SMS:', error); // Log failed attempt to Supabase await supabase.from('sms_logs').insert([ { recipient: to, message: text, originator: process.env.MESSAGEBIRD_ORIGINATOR, status: 'failed', }, ]); // Handle MessageBird-specific errors // Error codes reference: https://developers.messagebird.com/api/ const errorMessage = error.errors && error.errors[0] ? `MessageBird Error: ${error.errors[0].description}` : 'Failed to send SMS'; return res.status(500).json({ success: false, message: errorMessage, error: error.message, }); } }Code Walkthrough:
Client Initialization: Initialize the MessageBird client once at module level per SDK best practices. The Supabase client uses environment variables from
.env.local.Method Check: Next.js API routes handle all HTTP methods. We restrict this endpoint to POST only.
Input Validation: Validates required fields and E.164 phone format. E.164 is the international standard format required by MessageBird.
SMS Sending: Uses
messages.create()method from MessageBird SDK with callback-style API wrapped in Promise for async/await compatibility.Database Logging: Inserts success/failure record to Supabase. Non-blocking error handling ensures SMS delivery isn't blocked by logging failures.
Error Handling: Catches MessageBird API errors and returns structured error responses with proper HTTP status codes (400 for validation, 500 for server errors).
4. Testing Your SMS Integration
Start the Development Server
Run Next.js Development Server:
Next.js displays output confirming the server is running:
Verify Environment Variables: Check the terminal for any errors about missing environment variables. If you see errors, verify your
.env.localfile.Test with cURL
Send Test SMS: Open a new terminal and run:
curl -X POST http://localhost:3000/api/send-sms \ -H "Content-Type: application/json" \ -d '{ "to": "+1XXXXXXXXXX", "text": "Hello from MessageBird and Next.js!" }'Replace
+1XXXXXXXXXXwith a valid phone number in E.164 format. For MessageBird test accounts, verify the destination number first in your dashboard.Expected Success Response:
{ "success": true, "message": "SMS sent successfully", "messageId": "abc123def456" }Validation Error Example (missing field):
curl -X POST http://localhost:3000/api/send-sms \ -H "Content-Type: application/json" \ -d '{"to": "+14155550100"}'Response:
{ "success": false, "message": "Missing required fields: \"to\" (recipient) or \"text\" (message content)." }Format Validation Error (invalid phone format):
curl -X POST http://localhost:3000/api/send-sms \ -H "Content-Type: application/json" \ -d '{ "to": "5551234", "text": "Test" }'Response:
{ "success": false, "message": "Invalid phone number format. Use E.164 format (e.g., +14155550100)." }Verify in Dashboards
MessageBird Dashboard: Navigate to Developers > Message Logs to see sent messages with delivery status.
Supabase Dashboard:
sms_logstablerecipient,message,message_id, andstatusvaluescreated_attimestamps match your test timing5. Error Handling and Common Issues
MessageBird-Specific Error Codes
MessageBird returns structured error responses with error codes:
2MESSAGEBIRD_API_KEYin.env.local910202599Supabase Connection Errors
Common Supabase errors and solutions:
NEXT_PUBLIC_SUPABASE_URLandNEXT_PUBLIC_SUPABASE_ANON_KEYare correctsms_logstable exists in public schemaNext.js API Route Debugging
Environment Variables Not Loading:
.env.localis in project root directory (same level aspackage.json)npm run dev) after changing.env.localKEY=valuenotKEY="value"NEXT_PUBLIC_) are only available in API routesModule Not Found Errors:
TypeScript Errors:
6. Security Best Practices for Production SMS Applications
Environment Variable Security
.env.localto version control. Next.js automatically ignores it in.gitignore.NEXT_PUBLIC_prefix forMESSAGEBIRD_API_KEY)API Route Protection
For production, add authentication to prevent unauthorized SMS sending:
// pages/api/send-sms.ts import { getSession } from 'next-auth/react'; // Example with NextAuth.js export default async function handler(req: NextApiRequest, res: NextApiResponse) { // Check authentication const session = await getSession({ req }); if (!session) { return res.status(401).json({ success: false, message: 'Unauthorized. Authentication required.', }); } // … rest of handler code }Rate Limiting
Implement rate limiting using Next.js middleware to prevent abuse:
// middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; // Simple in-memory rate limiter (use Redis for production) const rateLimitMap = new Map<string, number[]>(); export function middleware(request: NextRequest) { if (request.nextUrl.pathname.startsWith('/api/send-sms')) { const ip = request.ip ?? 'unknown'; const now = Date.now(); const windowMs = 60 * 1000; // 1 minute const maxRequests = 10; const requests = rateLimitMap.get(ip) || []; const recentRequests = requests.filter((time) => now - time < windowMs); if (recentRequests.length >= maxRequests) { return NextResponse.json( { success: false, message: 'Too many requests. Try again later.' }, { status: 429 } ); } recentRequests.push(now); rateLimitMap.set(ip, recentRequests); } return NextResponse.next(); }Production Rate Limiting: For distributed deployments (Vercel, AWS), use Upstash Rate Limiting with Redis or Vercel Edge Middleware rate limiting.
Supabase Row Level Security
The RLS policies created earlier protect your database. For production:
-- Restrict reads to authenticated users only DROP POLICY IF EXISTS "Enable read for all users" ON public.sms_logs; CREATE POLICY "Enable read for authenticated users" ON public.sms_logs FOR SELECT USING (auth.role() = 'authenticated'); -- Restrict inserts to service role only (API routes) -- Service role key should be used server-side onlyStore the service role key separately for API routes:
7. Deployment Considerations
Deploying to Vercel
Vercel (creators of Next.js) provides optimal Next.js hosting:
Install Vercel CLI:
Deploy:
Configure Environment Variables:
.env.local:MESSAGEBIRD_API_KEYMESSAGEBIRD_ORIGINATORNEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYtest_prefix) for productionRedeploy: After adding environment variables, redeploy:
Serverless Function Considerations
Next.js API routes on Vercel run as serverless functions with limitations:
Alternative Deployment Platforms
Netlify:
Add environment variables in Netlify Dashboard > Site settings > Environment variables.
AWS Amplify:
Follow Next.js deployment guide and configure environment variables in Amplify Console.
Self-Hosted (VPS/EC2):
Use process managers like PM2 and ensure environment variables are set in system environment or
.env.production.local.8. Next Steps and Enhancements
Receive SMS Messages (Webhooks)
Implement inbound SMS handling using MessageBird webhooks:
Create Webhook Endpoint (
pages/api/sms-webhook.ts):import type { NextApiRequest, NextApiResponse } from 'next'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method !== 'POST') { return res.status(405).json({ error: 'Method not allowed' }); } const { originator, body, createdDatetime } = req.body; // Process inbound message console.log(`Received SMS from ${originator}: ${body}`); // Store in Supabase, trigger workflows, etc. return res.status(200).json({ success: true }); }Configure in MessageBird: Go to Dashboard > Developers > Webhooks, set webhook URL to
https://your-domain.com/api/sms-webhookDelivery Status Tracking
Track SMS delivery status by implementing a webhook for delivery reports:
// pages/api/delivery-status.ts import { createClient } from '@supabase/supabase-js'; const supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! ); export default async function handler(req: NextApiRequest, res: NextApiResponse) { const { id, status } = req.body; // MessageBird sends message ID and status await supabase .from('sms_logs') .update({ status }) .eq('message_id', id); return res.status(200).json({ success: true }); }Configure delivery report webhook URL in MessageBird Dashboard.
Build a Dashboard UI
Create a simple dashboard to view SMS logs:
// pages/sms-dashboard.tsx import { useEffect, useState } from 'react'; import { createClient } from '@supabase/supabase-js'; const supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! ); export default function SMSDashboard() { const [logs, setLogs] = useState([]); useEffect(() => { async function fetchLogs() { const { data } = await supabase .from('sms_logs') .select('*') .order('created_at', { ascending: false }) .limit(50); setLogs(data || []); } fetchLogs(); }, []); return ( <div> <h1>SMS Logs</h1> <table> <thead> <tr> <th>Recipient</th> <th>Message</th> <th>Status</th> <th>Sent At</th> </tr> </thead> <tbody> {logs.map((log: any) => ( <tr key={log.id}> <td>{log.recipient}</td> <td>{log.message}</td> <td>{log.status}</td> <td>{new Date(log.created_at).toLocaleString()}</td> </tr> ))} </tbody> </table> </div> ); }SMS Templates
Store reusable templates in Supabase:
CREATE TABLE sms_templates ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name TEXT NOT NULL, body TEXT NOT NULL, variables JSONB, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() );Verification Checklist
npx create-next-app.env.localconfigured with all required keyssms_logstable created with RLS policiespages/api/send-sms.tsnpm run dev)sms_logstableConclusion
You now have a production-ready Next.js application that sends SMS messages via MessageBird and logs transactions to Supabase. This foundation supports building notification systems, two-factor authentication, marketing campaigns, and customer communication platforms.
Key Resources:
For questions or issues, consult the official documentation or contact MessageBird Support or Supabase Support.