code examples
code examples
How to Send Bulk SMS with Infobip API in Next.js: Complete Guide
Learn how to build a production-ready bulk SMS messaging system using Infobip's SMS API and Next.js. Step-by-step tutorial with code examples, error handling, and deployment guide.
How to Send Bulk and Broadcast SMS Messages with Infobip API in Next.js
This guide provides a complete walkthrough for building a system capable of sending bulk or broadcast SMS messages using Infobip's API within a Next.js application. We will cover everything from initial project setup to deployment, monitoring, and best practices for a production-ready implementation.
By the end of this tutorial, you will have a Next.js application with a secure API endpoint that can accept a list of recipients and a message, and then reliably dispatch these messages via the Infobip SMS API.
Project Goals:
- Create a Next.js API route to handle bulk SMS sending requests.
- Integrate securely with the Infobip SMS API.
- Implement robust error handling and logging.
- Provide a foundation for scaling and adding features like scheduling and delivery report tracking.
- Offer guidance on deployment and testing.
Technology Stack:
- Next.js: A React framework providing server-side rendering, API routes, and a streamlined developer experience. We use its API routes feature for backend logic.
- Infobip SMS API: A powerful API for sending SMS messages globally. We'll use the
sms/2/text/advancedendpoint for its flexibility in handling multiple destinations. - Node.js: The runtime environment for Next.js.
- Axios: A promise-based HTTP client for making requests to the Infobip API.
- (Optional) Prisma: For database interactions if storing recipient lists or message logs.
- (Optional) Vercel: For easy deployment of Next.js applications.
System Architecture:
+-------------------+ +-----------------------+ +----------------+
| Client/Frontend | ---> | Next.js API Route | ---> | Infobip SMS API|
| (e.g., Admin UI) | | (/api/send-bulk-sms) | | |
+-------------------+ +-----------------------+ +----------------+
| - Validate Request |
| - Format Payload |
| - Call Infobip API |
| - Handle Response |
| - Log Results/Errors |
+-----------------------+
| (Optional: Logging)
v
+-----------------------+
| Logging Service |
+-----------------------+
| (Optional: DB Interaction)
v
+-----------------------+
| Database |
| (Recipients, Logs) |
+-----------------------+
| (Optional: Delivery Reports)
v
+-----------------------+
| Next.js API Route | <--- | Infobip Webhook|
| (/api/infobip-webhook)| | (notifyUrl) |
+-----------------------+ +----------------+Note: The webhook flow (bottom right) for receiving delivery reports via notifyUrl is optional but highly recommended for achieving full observability of message delivery statuses.
Prerequisites:
- Node.js (LTS version recommended) and npm/yarn installed.
- An active Infobip account with API access enabled.
- Basic understanding of Next.js, React, and asynchronous JavaScript.
- Familiarity with REST APIs.
- (Optional) A code editor like VS Code.
- (Optional) Git for version control.
Phone Number Format Requirements:
- All phone numbers must comply with ITU-T E.164 international format
- Maximum length: 15 digits (including country code)
- Structure: Country Code (1–3 digits) + Subscriber Number (max 12 digits)
- Must include a leading + sign when displayed (e.g.,
+14155552671) - No spaces, hyphens, or parentheses in API requests
- Country codes: see ITU-T E.164 country code list
1. Setting up the Project
Let's initialize a new Next.js project and set up the basic structure and environment.
1.1 Initialize Next.js Project
Open your terminal and run the following command:
npx create-next-app@latest infobip-sms-senderFollow the prompts. We recommend selecting:
- TypeScript: No (for simplicity in this guide, but adaptable)
- ESLint: Yes
- Tailwind CSS: No (not needed for the backend focus)
src/directory: Yes (good practice for organization)- App Router: Yes (recommended for new projects)
- Customize default import alias: No
Navigate into your new project directory:
cd infobip-sms-sender1.2 Install Dependencies
We need axios to make HTTP requests to the Infobip API.
npm install axios
# or
# yarn add axios1.3 Environment Variables
Create a file named .env.local in the root of your project. This file will store sensitive credentials and configuration. Never commit this file to version control.
#.env.local
# Infobip Credentials - Obtain from your Infobip account dashboard
INFOBIP_API_KEY=YOUR_INFOBIP_API_KEY
INFOBIP_BASE_URL=YOUR_INFOBIP_BASE_URL
# Default Sender ID (Optional - can be overridden per request)
INFOBIP_DEFAULT_SENDER_ID=InfoSMSINFOBIP_API_KEY: Your secret API key from Infobip.INFOBIP_BASE_URL: Your unique base URL provided by Infobip (e.g.,xxxxx.api.infobip.com).INFOBIP_DEFAULT_SENDER_ID: A default sender ID (alphanumeric or numeric) to use if not specified in the request. Check Infobip documentation and local regulations for sender ID requirements.
Ensure your .gitignore file includes .env.local:
# .gitignore - should already contain this if using create-next-app defaults
.env*.local1.4 Project Structure (App Router)
Your relevant structure within the src/app/ directory will look something like this:
src/
└── app/
├── api/
│ └── send-bulk-sms/
│ └── route.js # Our main API endpoint logic
├── layout.js
└── page.js
.env.local
package.json
# ... other files2. Implementing Core Functionality (API Route)
We'll create the API route that receives the list of recipients and the message content, then interacts with the Infobip API.
Create the file src/app/api/send-bulk-sms/route.js:
// src/app/api/send-bulk-sms/route.js
import { NextResponse } from 'next/server';
import axios from 'axios';
// --- Configuration ---
const INFOBIP_API_KEY = process.env.INFOBIP_API_KEY;
const INFOBIP_BASE_URL = process.env.INFOBIP_BASE_URL;
const DEFAULT_SENDER_ID = process.env.INFOBIP_DEFAULT_SENDER_ID || 'InfoSMS';
// Basic input validation helper
function validateInput(data) {
if (!data || typeof data !== 'object') {
return 'Invalid request body: Expected an object.';
}
const { recipients, text, senderId } = data;
if (!Array.isArray(recipients) || recipients.length === 0) {
return 'Invalid input: ""recipients"" must be a non-empty array of phone numbers (strings).';
}
if (!recipients.every(num => typeof num === 'string' && num.trim().length > 0)) {
return 'Invalid input: All items in ""recipients"" array must be non-empty strings.';
}
if (typeof text !== 'string' || text.trim().length === 0) {
return 'Invalid input: ""text"" must be a non-empty string.';
}
if (senderId && typeof senderId !== 'string') {
return 'Invalid input: ""senderId"" must be a string if provided.';
}
// Add more specific validation (e.g., phone number format) if needed
return null; // Validation passed
}
// --- API Handler ---
export async function POST(request) {
console.log('Received request to /api/send-bulk-sms');
// --- Check Credentials ---
if (!INFOBIP_API_KEY || !INFOBIP_BASE_URL) {
console.error('Server configuration error: Infobip API Key or Base URL is missing.');
return NextResponse.json(
{ error: 'Server configuration error. Please contact the administrator.' },
{ status: 500 }
);
}
let requestBody;
try {
requestBody = await request.json();
} catch (error) {
console.error('Failed to parse request body:', error);
return NextResponse.json({ error: 'Invalid JSON in request body.' }, { status: 400 });
}
// --- Validate Input ---
const validationError = validateInput(requestBody);
if (validationError) {
console.warn('Input validation failed:', validationError);
return NextResponse.json({ error: validationError }, { status: 400 });
}
const { recipients, text, senderId } = requestBody;
const effectiveSenderId = senderId || DEFAULT_SENDER_ID;
// --- Prepare Infobip Payload ---
// This payload targets the Infobip endpoint: /sms/2/text/advanced
// Map recipients array to Infobip's expected `destinations` format
const destinations = recipients.map(phoneNumber => ({ to: phoneNumber }));
const infobipPayload = {
messages: [
{
from: effectiveSenderId,
destinations: destinations,
text: text,
// Optional: Add other Infobip parameters here if needed
// notifyUrl: 'YOUR_DELIVERY_REPORT_WEBHOOK_URL',
// notifyContentType: 'application/json',
// callbackData: 'Your custom tracking data',
},
],
// Optional: Define a bulkId for tracking this batch
// bulkId: `my-campaign-${Date.now()}`
};
// --- Call Infobip API ---
const apiUrl = `https://${INFOBIP_BASE_URL}/sms/2/text/advanced`;
const headers = {
'Authorization': `App ${INFOBIP_API_KEY}`, // Use 'App' prefix for newer API keys
// 'Authorization': `ApiKey ${INFOBIP_API_KEY}`, // Use 'ApiKey' for older keys if 'App' doesn't work
'Content-Type': 'application/json',
'Accept': 'application/json',
};
try {
console.log(`Sending ${recipients.length} messages via Infobip...`);
const response = await axios.post(apiUrl, infobipPayload, { headers });
console.log('Infobip API Response Status:', response.status);
console.log('Infobip API Response Body:', response.data);
// Check for potential partial success/errors within the response body
if (response.data && response.data.messages) {
// Group ID 5 often indicates 'REJECTED'. Check Infobip docs for definitive status codes.
const successfulSends = response.data.messages.filter(
msg => msg.status && msg.status.groupId !== 5
);
const failedSends = response.data.messages.filter(
msg => msg.status && msg.status.groupId === 5
);
console.log(`Successfully submitted ${successfulSends.length} messages to Infobip.`);
if (failedSends.length > 0) {
console.warn(`Failed to submit ${failedSends.length} messages to Infobip. Check response body for details.`);
// Potentially return partial success info
}
}
return NextResponse.json(response.data, { status: response.status });
} catch (error) {
console.error('Error calling Infobip API:');
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.error('Status:', error.response.status);
console.error('Headers:', error.response.headers);
console.error('Data:', error.response.data);
// Return the error details from Infobip if available
return NextResponse.json(
{
error: 'Failed to send SMS via Infobip.',
infobipError: error.response.data
},
{ status: error.response.status }
);
} else if (error.request) {
// The request was made but no response was received
console.error('No response received:', error.request);
return NextResponse.json(
{ error: 'No response from Infobip API. Check network or Infobip status.' },
{ status: 504 } // Gateway Timeout
);
} else {
// Something happened in setting up the request that triggered an Error
console.error('Axios setup error:', error.message);
return NextResponse.json(
{ error: 'Internal server error setting up request.' },
{ status: 500 }
);
}
}
}Explanation:
- Imports: Import
NextResponsefor API responses andaxios. - Configuration: Load environment variables. Provide a default sender ID.
validateInputHelper: Performs basic checks on the incoming request body structure and types. This should be expanded based on specific needs (e.g., stricter phone number validation using libraries likelibphonenumber-js).POSTHandler: This is the main function executed when aPOSTrequest hits/api/send-bulk-sms.- Credential Check: Ensures API key and Base URL are configured on the server.
- Parse Request Body: Safely parses the incoming JSON payload.
- Input Validation: Calls the
validateInputhelper. Returns a 400 Bad Request error if validation fails. - Prepare Infobip Payload:
- Explicitly notes the target endpoint (
/sms/2/text/advanced). - Maps the simple
recipientsarray from the request to Infobip's requireddestinationsarray of objects ({ to: phoneNumber }). - Constructs the main
messagesarray payload according to the Infobip Send SMS API documentation. - Includes comments showing where to add optional parameters like
notifyUrl(for delivery reports) orbulkId.
- Explicitly notes the target endpoint (
- Call Infobip API:
- Constructs the full API endpoint URL.
- Sets the required
Authorizationheader. Note: Infobip usesApp <API_KEY>for newer keys andApiKey <API_KEY>for older ones. Check your Infobip dashboard or try both if one fails. Also includesContent-TypeandAcceptheaders. - Uses
axios.postto send the request.
- Handle Response:
- Logs the status and body of the Infobip response.
- Includes logic to check individual message statuses, noting that Group ID 5 often indicates rejection but advising to consult Infobip documentation for definitive codes.
- Returns the Infobip response data to the client on success (usually a 2xx status).
- Error Handling:
- Uses a
try...catchblock to capture errors during the API call. - Differentiates between errors with a response from Infobip, request errors where no response was received, and setup errors.
- Logs detailed error information to the server console.
- Returns appropriate JSON error responses and HTTP status codes to the client.
- Uses a
3. Building a Complete API Layer
The previous section already created our core API endpoint. This section details its usage and testing.
API Endpoint: POST /api/send-bulk-sms
Request Body (JSON):
{
""recipients"": [""+14155552671"", ""+442071234567"", ""+33123456789""],
""text"": ""Hello from our Next.js App! Special offer inside."",
""senderId"": ""MyAppName""
}recipients: (Required) An array of strings, each representing a phone number in international E.164 format (e.g.,+14155552671).text: (Required) The content of the SMS message as a string.senderId: (Optional) The alphanumeric or numeric sender ID to display to the recipient. Defaults toINFOBIP_DEFAULT_SENDER_IDfrom.env.localif not provided.
Success Response (Example - Status 200 OK):
{
""bulkId"": ""some-unique-bulk-id-from-infobip"",
""messages"": [
{
""to"": ""+14155552671"",
""status"": {
""groupId"": 1,
""groupName"": ""PENDING"",
""id"": 7,
""name"": ""PENDING_ENROUTE"",
""description"": ""Message sent to next instance""
},
""messageId"": ""unique-message-id-1""
},
{
""to"": ""+442071234567"",
""status"": {
""groupId"": 1,
""groupName"": ""PENDING"",
""id"": 7,
""name"": ""PENDING_ENROUTE"",
""description"": ""Message sent to next instance""
},
""messageId"": ""unique-message-id-2""
},
{
""to"": ""+33123456789"",
""status"": {
""groupId"": 5,
""groupName"": ""REJECTED"",
""id"": 1,
""name"": ""REJECTED_DESTINATION"",
""description"": ""Invalid destination address.""
},
""messageId"": ""unique-message-id-3""
}
]
}bulkId: An identifier assigned by Infobip (or your custom one if provided) for this batch send request. Useful for fetching logs or reports later.messages: An array containing the status for each individual message sent within the bulk request.to: The recipient's phone number.status: An object indicating the initial status of the message submission (e.g.,PENDING,REJECTED). See Infobip Status Codes for details.messageId: A unique identifier for this specific message, crucial for tracking delivery status via webhooks (notifyUrl).
Error Response Examples:
- 400 Bad Request (Input Validation):
json
{ ""error"": ""Invalid input: \""recipients\"" must be a non-empty array of phone numbers (strings)."" } - 401 Unauthorized (Invalid API Key):
json
{ ""error"": ""Failed to send SMS via Infobip."", ""infobipError"": { ""requestError"": { ""serviceException"": { ""messageId"": ""UNAUTHORIZED"", ""text"": ""Invalid login details"" } } } } - 500 Internal Server Error (Missing Config):
json
{ ""error"": ""Server configuration error. Please contact the administrator."" }
Testing with cURL:
Replace placeholders (YOUR_NEXTJS_URL, phone numbers, text, YOUR_SENDER_ID) with your actual values. Run your Next.js app locally (npm run dev or yarn dev).
curl -X POST http://localhost:3000/api/send-bulk-sms \
-H ""Content-Type: application/json"" \
-d '{
""recipients"": [""+14155550001"", ""+14155550002""],
""text"": ""Test message from cURL!"",
""senderId"": ""TestSend""
}'Testing with Postman:
- Create a new request.
- Set the method to
POST. - Enter the URL:
http://localhost:3000/api/send-bulk-sms(or your deployed URL). - Go to the ""Body"" tab, select ""raw"", and choose ""JSON"" from the dropdown.
- Paste the request body JSON (example above).
- Click ""Send"".
- Examine the response body and status code.
4. Integrating with Infobip (Credentials)
This section details how to obtain the necessary credentials from Infobip.
4.1 Obtain API Key and Base URL
- Log in to your Infobip Account.
- Navigate to API Keys: In the left-hand navigation menu, find the ""Developers"" or ""Developer Tools"" section, then click on ""API Keys"". (The exact location might change slightly).
- Generate API Key:
- Click the ""Create API Key"" or ""New API Key"" button.
- Give the key a descriptive name (e.g., ""NextJS Bulk Sender"").
- Assign appropriate roles/permissions. For sending SMS, ensure the relevant SMS API permissions are granted. Start with broad permissions if unsure, then refine later for security.
- Note the available key types (Public API Key is usually what you need).
- Copy the generated API Key immediately and store it securely. You often cannot view it again after closing the creation dialog. Store this in your
.env.localasINFOBIP_API_KEY.
- Find your Base URL:
- The Base URL is specific to your account and region.
- It's often displayed prominently on the Infobip developer portal homepage after logging in, or within the API documentation examples specific to your account. It will look like
xxxxx.api.infobip.com. - Copy this URL (without
https://) and store it in your.env.localasINFOBIP_BASE_URL.
4.2 Secure Storage
.env.local: As configured in Step 1, this file keeps credentials out of your codebase and version control.- Production Environment Variables: When deploying, use your hosting provider's mechanism for setting environment variables (e.g., Vercel Environment Variables, Docker environment variables, system environment variables). Do not hardcode credentials in your deployed code.
4.3 Infobip Dashboard Configuration (Optional but Recommended)
- Sender IDs: You may need to register alphanumeric sender IDs with Infobip or local regulators depending on the destination country. Check Infobip's documentation or support for country-specific rules.
- Delivery Reports (
notifyUrl): To get real-time updates on message delivery status (delivered, failed, expired), you need to:- Create another API route in your Next.js app (e.g.,
/api/infobip-webhook) capable of receiving POST requests from Infobip. - Provide the public URL of this endpoint in the
notifyUrlfield when sending messages via the Infobip API (see the commented-out line inroute.js). - Ensure this webhook endpoint is secured (e.g., using a secret query parameter or checking the source IP if possible).
- Specify
notifyContentType: 'application/json'to receive reports in JSON format.
- Create another API route in your Next.js app (e.g.,
5. Error Handling, Logging, and Retry Mechanisms
Our API route already includes basic error handling and logging. Let's enhance it.
5.1 Consistent Error Handling
The current try...catch block in route.js provides a good foundation:
- It distinguishes between validation errors, Infobip API errors, network errors, and setup errors.
- It logs detailed errors server-side.
- It returns structured JSON errors to the client with appropriate HTTP status codes.
5.2 Enhanced Logging
For production, consider using a dedicated logging library or service instead of just console.log/console.error.
- Libraries:
pino,winstonoffer structured logging, different log levels (debug, info, warn, error), and log rotation. - Services: Vercel Logs, Datadog, Sentry, Logtail provide centralized log aggregation, searching, and alerting.
Example (Conceptual - using a hypothetical logger):
// Replace console.log/error with logger methods
// import logger from './logger'; // Assuming a logger setup
// ... inside POST handler ...
if (!INFOBIP_API_KEY || !INFOBIP_BASE_URL) {
logger.error('Server configuration error: Infobip credentials missing.');
// ... return response
}
// ... inside catch block ...
if (error.response) {
logger.error({
message: 'Infobip API Error',
status: error.response.status,
data: error.response.data,
url: apiUrl,
payload: infobipPayload // Be cautious logging sensitive payload data
});
// ... return response
} else if (error.request) {
logger.error({ message: 'Infobip API No Response', error: error.message, url: apiUrl });
// ... return response
} else {
logger.error({ message: 'API Request Setup Error', error: error.message });
// ... return response
}5.3 Retry Mechanisms
Retrying failed bulk sends automatically can be complex and potentially costly.
- When to Retry: Only retry your API call to Infobip on transient errors (e.g., network timeouts
504, temporary Infobip server issues503). Do not retry on client errors (4xx) like invalid input or authentication failure. - Strategy: Implement exponential backoff (wait longer between retries). Limit the number of retries for API calls.
- Infobip Internal Retries: Note that Infobip itself may perform internal retries for certain delivery failures (e.g., temporary carrier issues) trying to deliver the message to the handset, which is separate from retrying your initial API call to submit the message.
- Complexity: For bulk messages, if the initial POST to Infobip fails entirely (e.g., network error before getting a
bulkId), retrying the whole batch might be feasible. If the initial POST succeeds but some messages are later reported asFAILEDvianotifyUrl, retrying individual failed messages requires more state management (trackingmessageIds and their final statuses). - Recommendation: For many bulk use cases, it's simpler to log failures thoroughly and handle retries manually or through a separate process, rather than building complex retry logic directly into the initial sending API route. Focus on reliable logging and monitoring first. If using the
notifyUrl, log the delivery reports to understand final delivery success/failure.
5.4 Testing Error Scenarios
- Invalid Input: Send requests with missing fields, incorrect types, or empty arrays to
/api/send-bulk-sms. - Invalid Credentials: Temporarily change
INFOBIP_API_KEYin.env.localto an invalid value and send a request. Expect a 401 error. - Invalid Numbers: Include deliberately malformed or non-existent phone numbers in the
recipientsarray. Check the Infobip response forREJECTEDstatuses. - Network Issues (Mocking): Use testing libraries (like
mswor Jest mocks) to simulate network errors or specific Infobip error responses (5xx) during integration tests.
6. Creating a Database Schema (Optional Enhancement)
While not strictly required for sending, a database is crucial for managing recipients, tracking sends, and storing delivery statuses in a real-world application. We'll outline a basic schema using Prisma.
6.1 Setup Prisma
npm install prisma --save-dev
npm install @prisma/client
npx prisma init --datasource-provider postgresql # Or your preferred DBThis creates a prisma directory with a schema.prisma file and updates .env (or .env.local) with a DATABASE_URL. Configure your database connection string there.
6.2 Define Schema (prisma/schema.prisma)
// prisma/schema.prisma
generator client {
provider = ""prisma-client-js""
}
datasource db {
provider = ""postgresql"" // Or your chosen provider
url = env(""DATABASE_URL"")
}
model Recipient {
id String @id @default(cuid())
phoneNumber String @unique // E.164 format
firstName String?
lastName String?
// Add other relevant fields (tags, groups, etc.)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
MessageLogs MessageLog[]
}
model BulkSend {
id String @id @default(cuid())
infobipBulkId String? @unique // Store Infobip's bulkId if available
messageText String
senderId String
requestedAt DateTime @default(now())
status String @default(""PENDING"") // e.g., PENDING, PROCESSING, COMPLETED, FAILED
MessageLogs MessageLog[]
}
model MessageLog {
id String @id @default(cuid())
infobipMessageId String? @unique // Store Infobip's messageId
bulkSendId String
recipientId String // Foreign key to Recipient table
status String // Initial status from API response (e.g., PENDING_ENROUTE, REJECTED_DESTINATION)
deliveryStatus String? // Final status from webhook (e.g., DELIVERED_TO_HANDSET, FAILED_UNKNOWN)
statusMessage String? // Description from Infobip status
sentAt DateTime @default(now())
updatedAt DateTime @updatedAt
webhookPayload Json? // Store the full delivery report payload
bulkSend BulkSend @relation(fields: [bulkSendId], references: [id])
recipient Recipient @relation(fields: [recipientId], references: [id])
@@index([bulkSendId])
@@index([recipientId])
}6.3 Apply Schema
Run the migration command to create the tables in your database:
npx prisma migrate dev --name init
# Or for production/non-dev:
# npx prisma db push6.4 Integrate with API Route (Conceptual)
Modify src/app/api/send-bulk-sms/route.js to interact with Prisma:
// src/app/api/send-bulk-sms/route.js
// ... other imports
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// ... inside POST handler, after validation ...
let bulkSendRecord;
try {
// 1. Create a record for the bulk send attempt
bulkSendRecord = await prisma.bulkSend.create({
data: {
messageText: text,
senderId: effectiveSenderId,
status: 'PROCESSING', // Mark as processing initially
},
});
// --- Prepare Infobip Payload ---
// Option A: If recipients in request are DB IDs (e.g., [""id1"", ""id2""])
// const recipientIds = requestBody.recipients; // Assuming request sends IDs
// const dbRecipients = await prisma.recipient.findMany({ where: { id: { in: recipientIds } } });
// const destinations = dbRecipients.map(r => ({ to: r.phoneNumber }));
// Option B: If recipients in request are phone numbers (as in current example)
const phoneNumbers = requestBody.recipients;
const destinations = phoneNumbers.map(phoneNumber => ({ to: phoneNumber }));
const infobipPayload = { /* ... as before ... */ };
// Use our DB ID as bulkId for easier linking. Check Infobip docs for any bulkId constraints
// (e.g., length, format), although a CUID like Prisma's default is likely acceptable.
infobipPayload.bulkId = bulkSendRecord.id;
// --- Call Infobip API ---
const apiUrl = `https://${INFOBIP_BASE_URL}/sms/2/text/advanced`;
const headers = { /* ... as before ... */ };
const response = await axios.post(apiUrl, infobipPayload, { headers });
// 2. Log individual message statuses
if (response.data && response.data.messages) {
const messageLogsData = [];
for (const msg of response.data.messages) {
// PROBLEM: Mapping 'msg.to' (phone number) back to your internal 'recipientId'
// This requires fetching recipients based on phone numbers, which can be slow or complex.
// const recipient = await prisma.recipient.findUnique({ where: { phoneNumber: msg.to } }); // Example lookup
// const recipientId = recipient ? recipient.id : 'UNKNOWN'; // Handle if number not in DB
// SIMPLER APPROACH: Store phone number directly in the log if mapping is too complex initially.
// You might need a separate 'recipientPhoneNumber' field in MessageLog schema for this.
messageLogsData.push({
bulkSendId: bulkSendRecord.id,
// recipientId: recipientId, // Use the mapped ID if available
// recipientPhoneNumber: msg.to, // Or store the number directly
infobipMessageId: msg.messageId,
status: msg.status.name,
statusMessage: msg.status.description,
sentAt: new Date(),
});
}
// Consider logging directly without Prisma if mapping complexity is high,
// or perform lookups asynchronously. For simplicity here, we prepare data.
console.log('TODO: Save message logs to DB. Data prepared:', messageLogsData);
// Example DB write (handle potential errors):
// await prisma.messageLog.createMany({ data: messageLogsData, skipDuplicates: true });
}
// 3. Update bulk send status (e.g., to COMPLETED or PARTIAL_FAILURE)
await prisma.bulkSend.update({
where: { id: bulkSendRecord.id },
data: {
infobipBulkId: response.data.bulkId, // Store Infobip's ID
status: 'COMPLETED', // Or determine based on message statuses
},
});
return NextResponse.json(response.data, { status: response.status });
} catch (error) {
// 4. Update bulk send status on failure
if (bulkSendRecord) {
await prisma.bulkSend.update({
where: { id: bulkSendRecord.id },
data: { status: 'FAILED' },
});
}
// ... rest of error handling ...
} finally {
await prisma.$disconnect(); // Disconnect Prisma client
}This integration is illustrative. A production implementation requires careful handling of recipient mapping (phone number to ID), transaction management, and performance optimization for database lookups.
Frequently Asked Questions (FAQ)
How do I send bulk SMS messages with Infobip in Next.js?
Create a Next.js API route using the App Router pattern, configure Infobip API credentials in environment variables, and use the /sms/2/text/advanced endpoint to send messages to multiple recipients. The implementation requires axios for HTTP requests and proper error handling for production use.
What is the correct phone number format for Infobip SMS API?
Phone numbers must follow the ITU-T E.164 international format: a plus sign (+) followed by the country code (1-3 digits) and subscriber number, with a maximum total length of 15 digits. Example: +14155552671 for a US number.
Which Infobip authorization header should I use?
Use Authorization: App YOUR_API_KEY for newer Infobip API keys. If you have an older key and receive authentication errors, try Authorization: ApiKey YOUR_API_KEY instead. Check your Infobip dashboard to determine your key type.
How do I track SMS delivery status with Infobip?
Implement a webhook endpoint in your Next.js application and specify it in the notifyUrl parameter when sending messages. Infobip will POST delivery reports to this endpoint with status updates (delivered, failed, etc.) for each message.
What does Infobip status Group ID 5 mean?
Group ID 5 indicates a REJECTED status, meaning the message was not accepted for delivery. Common reasons include invalid destination numbers, blocked sender IDs, or insufficient account credits. Check the detailed status description in the API response for specific rejection reasons.
How many recipients can I include in a single Infobip API call?
While Infobip's /sms/2/text/advanced endpoint supports multiple destinations in one request, practical limits depend on your account configuration and rate limits. For very large batches (10,000+ recipients), implement batching logic to split requests into smaller chunks.
Should I retry failed SMS sends automatically?
Only retry on transient errors (network timeouts 504, server errors 503). Do not retry on client errors (4xx) like invalid credentials or malformed requests. Implement exponential backoff and limit retry attempts to 3-5 times maximum.
How do I secure my Infobip API credentials in Next.js?
Store credentials in .env.local for local development (never commit this file). For production, use your hosting platform's environment variable management (e.g., Vercel Environment Variables). Never hardcode API keys in source code or client-side JavaScript.
What's the difference between bulk SMS and broadcast SMS?
Both terms typically refer to sending the same message to multiple recipients. "Bulk SMS" emphasizes volume, while "broadcast SMS" emphasizes wide distribution. The Infobip implementation is identical for both use cases using the /sms/2/text/advanced endpoint.
How do I validate E.164 phone numbers in Node.js?
Use the libphonenumber-js library for robust phone number validation. Install with npm install libphonenumber-js and validate numbers before sending to Infobip to reduce rejected messages and improve delivery rates.
Additional Resources
Frequently Asked Questions
How to send bulk SMS with Next.js and Infobip?
Create a Next.js API route (/api/send-bulk-sms) that handles POST requests containing recipient phone numbers and message text. This route interacts with the Infobip SMS API using Axios to dispatch messages. Refer to the provided code example for a complete implementation guide.
What is the Infobip sms/2/text/advanced endpoint?
The `/sms/2/text/advanced` endpoint in the Infobip API is used for sending bulk SMS messages. It provides flexibility for managing multiple recipients, customizing sender IDs, and tracking delivery reports. This endpoint is central to the provided Next.js integration.
Why does Infobip need a Base URL?
The Infobip Base URL (e.g., xxxxx.api.infobip.com) is unique to your account and region. It directs your requests to the correct Infobip API server, ensuring secure communication. You must configure this URL in your Next.js project's environment variables.
When should I use the notifyUrl parameter with Infobip?
Use the `notifyUrl` parameter when you need real-time delivery reports for your SMS messages. Set its value to a dedicated webhook endpoint in your Next.js app. Infobip will send POST requests to this URL with delivery status updates for each message.
Can I use Prisma for managing SMS recipients in Next.js?
Yes, Prisma is recommended for storing and managing recipient lists, as well as logging sent messages and delivery statuses. The schema provided in the guide includes examples for Recipient, BulkSend, and MessageLog tables. Configure Prisma with PostgreSQL or your preferred database.
How to set up Infobip API key in Next.js?
Obtain your API Key from your Infobip account dashboard. Store this key securely in a `.env.local` file in your Next.js project root. Then, access it in your code using `process.env.INFOBIP_API_KEY`.
What is the role of Axios in bulk SMS sending?
Axios, a promise-based HTTP client, simplifies making API requests to Infobip from your Next.js application. It handles sending the POST request to the Infobip endpoint with necessary headers and data.
How to handle Infobip API errors in Next.js?
Implement a `try...catch` block around your Axios requests to the Infobip API. Log detailed error information (status, headers, response body) to the server. Return appropriate JSON error messages and HTTP status codes to the client.
How to structure the recipients data for Infobip API?
Infobip's `/sms/2/text/advanced` endpoint expects an array of objects, each with a `to` property containing a phone number in E.164 format. The provided code example demonstrates how to format the request body for sending bulk SMS.
How to set up a webhook for Infobip delivery reports?
Create a new API route in your Next.js application (e.g., /api/infobip-webhook). This route will receive POST requests from Infobip. Provide the public URL of this route as the `notifyUrl` in your Infobip API request payload.
What are the prerequisites for using Infobip with Next.js?
You'll need Node.js and npm/yarn, an active Infobip account with API access, basic Next.js/React knowledge, and familiarity with REST APIs. A code editor and Git are recommended but not strictly required.
What technology stack is used for bulk SMS sending?
The project leverages Next.js for its API routes, Infobip SMS API for message delivery, Node.js as the runtime environment, and Axios for HTTP requests. Optionally, Prisma can be used for database interactions and Vercel for deployment.
When should I retry failed Infobip API requests?
Retry API requests only on transient errors like network timeouts (504) or temporary Infobip server issues (503). Do not retry on client errors (4xx) such as invalid input or authentication failures. Implement exponential backoff and limit retry attempts.
Why should I log Infobip API responses?
Logging responses allows you to track successful and failed message sends, identify potential issues with recipient numbers or API connectivity, and monitor overall system performance. Use structured logging and consider a dedicated logging service for production.