This guide details how to build a Next.js application that can send SMS messages using the Infobip API and their official Node.js SDK. We'll create a simple API endpoint within our Next.js app that securely handles sending SMS messages via Infobip.
This approach enables you to integrate SMS notifications, alerts, marketing campaigns, or two-factor authentication (2FA) directly into your Next.js web applications.
Goal: Create a Next.js API route (/api/send-sms
) that accepts a phone number and message text, then uses the Infobip Node.js SDK to send the SMS.
Problem Solved: Provides a straightforward way to leverage Infobip's robust SMS infrastructure within a modern web framework like Next.js, abstracting away direct HTTP calls and managing authentication securely.
Technologies Used:
- Next.js: A popular React framework for building server-rendered or statically generated web applications, including API routes that run server-side Node.js code.
- Node.js: The JavaScript runtime environment used by Next.js for its backend features.
- Infobip: A global cloud communications platform providing APIs for various channels, including SMS.
@infobip-api/sdk
: The official Infobip Node.js SDK for interacting with their APIs.- Environment Variables: For securely storing sensitive API credentials.
System Architecture:
graph LR
A[User's Browser] -- Makes POST request --> B(Next.js App / Frontend);
B -- Calls API route --> C(Next.js API Route `/api/send-sms`);
C -- Uses Infobip SDK --> D(Infobip API);
D -- Sends SMS --> E(User's Phone);
D -- Sends Response --> C;
C -- Sends Response --> B;
B -- Displays Result --> A;
Prerequisites:
- A free or paid Infobip account.
- Node.js (v14 or later recommended).
npm
oryarn
package manager.- Basic understanding of JavaScript, Node.js, Next.js, and REST APIs.
Expected Outcome: A functional Next.js application with an API endpoint capable of sending SMS messages via Infobip, ready for integration into larger projects.
1. Setting up the Project
Let's start by creating a new Next.js project and installing the necessary dependencies.
-
Create a New Next.js App: Open your terminal and run the following command. Replace
infobip-nextjs-guide
with your preferred project name.npx create-next-app@latest infobip-nextjs-guide
Follow the prompts (you can accept the defaults).
-
Navigate to Project Directory:
cd infobip-nextjs-guide
-
Install Infobip Node.js SDK: We'll use the official SDK for interacting with the Infobip API.
npm install @infobip-api/sdk
or if using yarn:
yarn add @infobip-api/sdk
-
Set Up Environment Variables: Sensitive credentials like API keys should never be hardcoded. We'll use environment variables.
- Create a file named
.env.local
in the root of your project directory. - Add the following lines to
.env.local
, replacing the placeholder values with your actual Infobip credentials:
# .env.local # Your Infobip API Key (obtain from Infobip portal) INFOBIP_API_KEY=YOUR_INFOBIP_API_KEY # Your Infobip Base URL (obtain from Infobip portal, e.g., xyz.api.infobip.com) INFOBIP_BASE_URL=YOUR_INFOBIP_BASE_URL # Optional: Default Sender ID (must be registered/approved by Infobip) # INFOBIP_SENDER_ID=InfoSMS
-
Obtaining Credentials:
- Log in to your Infobip Portal.
- Navigate to the Developers section or API Keys management area (exact navigation may vary slightly).
- Generate a new API Key if you don't have one. Copy this key immediately and store it securely (in your
.env.local
file). - Your Base URL is usually displayed alongside your API key or on the API documentation landing page within the portal after you log in. It's specific to your account.
- The Sender ID (
from
field in the API) often needs to be registered with Infobip, especially for alphanumeric senders, depending on country regulations. You might start with a default numeric sender provided or approved by Infobip during signup.
-
Security: Ensure
.env.local
is added to your.gitignore
file (this is default increate-next-app
) to prevent accidentally committing secrets.
- Create a file named
-
Project Structure: Your basic structure will look like this (simplified):
infobip-nextjs-guide/ ├── pages/ │ ├── api/ # API routes live here │ │ └── send-sms.js # Our SMS sending endpoint │ └── index.js # Example frontend page (optional) ├── public/ ├── styles/ ├── .env.local # Infobip credentials (DO NOT COMMIT) ├── .gitignore ├── package.json └── README.md
We place our server-side logic within the
pages/api/
directory, leveraging Next.js's API routes feature.
2. Implementing the Core SMS Sending Functionality
Now, let's create the API route that will handle the SMS sending logic.
-
Create the API Route File: Create a new file:
pages/api/send-sms.js
-
Implement the API Handler: Add the following code to
pages/api/send-sms.js
.// pages/api/send-sms.js import { Infobip, AuthType } from '@infobip-api/sdk'; // Instantiate the Infobip client // Ensure environment variables are loaded correctly // Note: Base URL might need 'https://' prefix if not included in the env var const infobipClient = new Infobip({ baseUrl: process.env.INFOBIP_BASE_URL, apiKey: process.env.INFOBIP_API_KEY, authType: AuthType.ApiKey, }); export default async function handler(req, res) { // 1. Only allow POST requests if (req.method !== 'POST') { res.setHeader('Allow', ['POST']); return res.status(405).json({ message: `Method ${req.method} Not Allowed` }); } // 2. Basic Input Validation const { to, text, from } = req.body; // `from` is optional in request body if default is set if (!to || typeof to !== 'string') { return res.status(400).json({ message: 'Missing or invalid \'to\' field (phone number).' }); } if (!text || typeof text !== 'string') { return res.status(400).json({ message: 'Missing or invalid \'text\' field (message content).' }); } // Basic E.164 format check. // WARNING: This regex is very basic. It allows numbers without '+' and doesn't // fully validate E.164 structure or length limits precisely. // Consider using a dedicated library like 'libphonenumber-js' for robust validation. // See Section 3 for more details. const phoneRegex = /^\+?[1-9]\d{1,14}$/; if (!phoneRegex.test(to)) { return res.status(400).json({ message: 'Invalid \'to\' phone number format. Should resemble E.164 (e.g., +14155552671).' }); } // Use default sender from env var if not provided in request, otherwise use request body 'from' const senderId = from || process.env.INFOBIP_SENDER_ID || 'InfoSMS'; // Fallback sender // 3. Construct the SMS Payload for Infobip SDK const smsPayload = { messages: [ { destinations: [{ to: to }], from: senderId, text: text, // Add other options like flash, validityPeriod, notifyUrl etc. if needed // See Infobip docs: https://www.infobip.com/docs/api/channels/sms/send-sms-message }, ], // bulkId: 'YOUR_CUSTOM_BULK_ID', // Optional: For tracking batches // tracking: { track: 'SMS', type: 'MY_CAMPAIGN' }, // Optional: For analytics }; try { // 4. Send the SMS using the Infobip SDK console.log('Sending SMS payload:', JSON.stringify(smsPayload, null, 2)); // --- Integration point for Retry Logic (See Section 5) --- // Replace the direct call below with the retry function if implemented: // const infobipResponse = await sendSmsWithRetry(smsPayload); // --- Original Direct Call --- const infobipResponse = await infobipClient.channels.sms.send(smsPayload); console.log('Infobip API Response:', JSON.stringify(infobipResponse.data, null, 2)); // 5. Respond with Success // Extract relevant info like messageId and status const firstMessageResult = infobipResponse.data.messages?.[0]; return res.status(200).json({ success: true, message: 'SMS sent successfully.', infobipResponse: { bulkId: infobipResponse.data.bulkId, messageId: firstMessageResult?.messageId, status: firstMessageResult?.status?.name, // e.g., PENDING_ACCEPTED statusGroup: firstMessageResult?.status?.groupName, // e.g., PENDING }, }); } catch (error) { // 6. Handle Errors from Infobip SDK or Network console.error('Error sending SMS via Infobip:', error); // Extract more detailed error info if available from Infobip's response structure // Assumption: Error structure follows Axios pattern (error.response.data) // and Infobip's specific error format (requestError.serviceException). // This might be brittle if SDK/API changes error formats. // Consider adding checks: if (error.response && error.response.data && ...) const errorResponse = error.response?.data || {}; const serviceException = errorResponse.requestError?.serviceException; let statusCode = 500; let errorMessage = 'Failed to send SMS.'; let errorDetails = serviceException?.text || error.message || 'Unknown error'; if (error.response?.status) { statusCode = error.response.status; // Use HTTP status from Infobip if available if (statusCode === 401) errorMessage = 'Infobip authentication failed. Check API Key and Base URL.'; if (statusCode === 400) errorMessage = 'Bad request. Check payload structure or parameters.'; } // Specific Infobip error message parsing if (serviceException?.messageId === 'UNAUTHORIZED') { statusCode = 401; errorMessage = `Infobip Authentication Error: ${serviceException.text}`; errorDetails = serviceException; } else if (serviceException) { // statusCode = 400; // Often indicates bad input if not auth error - use status from response if available errorMessage = `Infobip Service Error: ${serviceException.text}`; errorDetails = serviceException; } return res.status(statusCode).json({ success: false, message: errorMessage, errorDetails: errorDetails, // Provide details for debugging }); } }
Code Explanation:
- Method Check: Ensures only
POST
requests are accepted. - Input Validation: Performs basic checks on
to
andtext
. Includes a very basic E.164 format check. Robust validation is recommended for production. - Payload Construction: Creates the
smsPayload
for the SDK, usingfrom
from the request or falling back to environment variables/defaults. - Sending SMS: Calls
infobipClient.channels.sms.send(smsPayload)
within atry...catch
block. Includes logging. - Success Response: Returns
200 OK
with success message and key details from the Infobip response. - Error Handling: Catches errors, logs details, attempts to parse specific Infobip error information, and returns an appropriate HTTP error status code (4xx/5xx) with a descriptive message.
3. Building the API Layer (Validation & Testing)
Our Next.js API route (/api/send-sms
) is the API layer. We've added basic validation. Let's discuss improvements and testing.
Request Validation (Improvements):
- Schema Validation: Use a library like
zod
orjoi
for robust request body schema validation (e.g., check types, required fields, lengths). - Phone Number Validation: The current regex (
/^\+?[1-9]\d{1,14}$/
) is basic. Infobip generally expects E.164 format (+
followed by country code and number, no spaces/dashes). For production, use a dedicated library likelibphonenumber-js
to parse and validate numbers more accurately based on country rules. - Message Length: Validate message length against SMS character limits (160 for GSM-7, 70 for UCS-2) to provide immediate feedback, although Infobip handles segmentation.
Authentication/Authorization:
- Protect this endpoint. Options include session/token verification, API keys for service-to-service calls, or IP whitelisting.
Testing the Endpoint:
Run your Next.js dev server (npm run dev
or yarn dev
). Use tools like curl
or Postman.
Using curl
:
curl -X POST http://localhost:3000/api/send-sms \
-H "Content-Type: application/json" \
-d '{
"to": "+14155552671",
"text": "Hello from Next.js and Infobip! Test @ [Current Time]"
}'
- Replace
+14155552671
: Use your registered number for Infobip free trials, or any valid number for paid accounts. - Optional
from
field: To specify a sender ID different from the default/environment variable, add it to the JSON payload:{"to": "...", "text": "...", "from": "YourSenderID"}
. Ensure this sender ID is approved/valid in your Infobip account.
Expected Success Response (JSON):
{
"success": true,
"message": "SMS sent successfully.",
"infobipResponse": {
"bulkId": "some-bulk-id-from-infobip",
"messageId": "some-message-id-from-infobip",
"status": "PENDING_ACCEPTED",
"statusGroup": "PENDING"
}
}
Expected Error Response (JSON - Example: Invalid API Key):
{
"success": false,
"message": "Infobip Authentication Error: Invalid login details",
"errorDetails": {
"messageId": "UNAUTHORIZED",
"text": "Invalid login details"
}
}
4. Integrating with Infobip (SDK Configuration Details)
We instantiated and used the Infobip SDK in Section 2. Here's a recap of the key configuration aspects:
-
Instantiation:
const infobipClient = new Infobip({ baseUrl: process.env.INFOBIP_BASE_URL, apiKey: process.env.INFOBIP_API_KEY, authType: AuthType.ApiKey, // Specify API Key authentication });
This creates the client, reading credentials securely from environment variables.
AuthType.ApiKey
ensures the SDK uses the correctAuthorization: App YOUR_API_KEY
header format. -
API Call Structure:
const infobipResponse = await infobipClient.channels.sms.send(smsPayload);
The SDK simplifies the interaction. It handles:
- Constructing the correct HTTP request (e.g.,
POST /sms/2/text/advanced
). - Setting necessary headers (
Authorization
,Content-Type
,Accept
). - Sending the JSON payload.
- Parsing the response or throwing an error.
- Constructing the correct HTTP request (e.g.,
Handling API Keys Securely:
- Environment Variables: Using
.env.local
for local development and platform environment variables (Vercel, Netlify, AWS, etc.) for deployment is the standard. - Secrets Management Systems: For enhanced security in production, especially with cloud providers, use services like AWS Secrets Manager, Google Secret Manager, Azure Key Vault, or HashiCorp Vault. These inject secrets securely at runtime.
- Never commit secrets to Git. Ensure
.env.local
or other secret files are in.gitignore
.
Fallback Mechanisms:
- For high-availability needs, consider a secondary SMS provider or channel (like email) if Infobip encounters issues. This requires:
- Advanced error handling to differentiate between Infobip errors and network problems.
- Integrating another provider's API/SDK.
- Logic to trigger the fallback based on specific error conditions.
- This adds significant complexity.
5. Error Handling, Logging, and Retries
Our API route includes foundational error handling. Let's refine it.
Consistent Strategy:
- Use
try...catch
for external calls. - Log errors effectively (see below).
- Return meaningful HTTP status codes (4xx client, 5xx server/dependency).
- Provide clear
success: false
andmessage
/errorDetails
in responses. - Be mindful of the error structure assumption (
error.response.data.requestError.serviceException
). While common for Infobip API errors via the SDK, consider adding checks for the existence oferror.response
,error.response.data
, etc., to handle network errors or unexpected structures more gracefully.
Logging:
- Development:
console.log
/console.error
is acceptable. - Production: Use a structured logging library (e.g.,
pino
,winston
) outputting JSON. This integrates well with log aggregation systems (Datadog, Logz.io, ELK, CloudWatch Logs). - Log Content: Include timestamp, severity, request ID (if possible), error message, stack trace, relevant Infobip error details (status code, message ID, text), and potentially sanitized request data.
Retry Mechanisms:
- Implement retries for transient issues (network errors, temporary Infobip 5xx errors).
- Strategy: Exponential backoff with jitter (increase delay between retries, add randomness).
- When to Retry: On 5xx errors or network errors (where
error.response
might be undefined). Do not retry 4xx errors (bad input, auth failure). - Implementation: Use libraries like
async-retry
or implement manually.
Conceptual Retry Logic (Manual):
async function sendSmsWithRetry(payload, maxRetries = 3, currentAttempt = 1) {
try {
// Use the globally configured client
return await infobipClient.channels.sms.send(payload);
} catch (error) {
const statusCode = error.response?.status;
// Retry on 5xx or network errors (no status code from response)
if (currentAttempt < maxRetries && (!statusCode || statusCode >= 500)) {
const delay = Math.pow(2, currentAttempt - 1) * 1000 + Math.random() * 1000; // Exponential backoff + jitter
console.warn(`Attempt ${currentAttempt} failed for SMS sending. Retrying in ${delay.toFixed(0)}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
return sendSmsWithRetry(payload, maxRetries, currentAttempt + 1);
} else {
// Non-retryable error or max retries reached
console.error(`SMS sending failed after ${currentAttempt} attempts or due to non-retryable error.`);
throw error; // Re-throw the error to be caught by the main handler
}
}
}
// --- How to integrate into the main handler (Section 2) ---
// Inside the `try` block of the `handler` function, replace:
// const infobipResponse = await infobipClient.channels.sms.send(smsPayload);
// WITH:
// const infobipResponse = await sendSmsWithRetry(smsPayload);
// The `catch` block in the main `handler` will then catch the final error after retries fail.
6. Database Schema and Data Layer (Optional Extension)
While not required for basic sending, storing SMS data is common:
- Track Status: Save
messageId
,bulkId
. Use Infobip Delivery Report webhooks to update status (DELIVERED
,FAILED
, etc.). - Audit Log: Record sent messages (recipient, content, timestamp, status).
- Contact/Campaign Management: Link messages to users or campaigns.
Example Schema (Conceptual - using Prisma):
// schema.prisma
datasource db {
provider = ""postgresql"" // or mysql, sqlite
url = env(""DATABASE_URL"")
}
generator client {
provider = ""prisma-client-js""
}
model SmsMessage {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
infobipMessageId String? @unique // Store the ID from Infobip
infobipBulkId String? // Store the Bulk ID if applicable
recipient String @index // The 'to' phone number (index for lookups)
sender String // The 'from' sender ID
messageText String @db.Text // The message content
status String @default(""PENDING"") @index // e.g., PENDING, SENT, DELIVERED, FAILED, REJECTED
statusDetails String? // Store detailed error message if failed
sentAt DateTime? // Timestamp when the API call was made
// Optional relations
// userId String?
// user User? @relation(fields: [userId], references: [id])
// campaignId String?
// campaign Campaign? @relation(fields: [campaignId], references: [id])
}
// Define User, Campaign models as needed
Implementing this requires setting up Prisma (or another ORM), defining the schema, migrating the database (npx prisma migrate dev
), and adding database interactions to your API route or a separate service layer.
7. Adding Security Features
Protect your API endpoint and application:
- Input Validation and Sanitization: Use robust schema validation (
zod
). Sanitize user input if displayed elsewhere (prevent XSS). - Protect Against Common Vulnerabilities:
- Authentication/Authorization: Secure the endpoint (Section 3).
- Rate Limiting: Prevent abuse and cost overruns. Use libraries like
rate-limiter-flexible
or platform features (Vercel, Cloudflare, API Gateway).- Example (
rate-limiter-flexible
conceptual):// Install: npm install rate-limiter-flexible import { RateLimiterMemory } from 'rate-limiter-flexible'; const limiterOptions = { points: 10, duration: 60 }; // Max 10 requests per IP per minute const rateLimiter = new RateLimiterMemory(limiterOptions); // In handler, before main logic: try { // Use req.socket.remoteAddress or a more reliable identifier like user ID if authenticated const clientIdentifier = req.socket.remoteAddress || 'unknown_ip'; await rateLimiter.consume(clientIdentifier); } catch (rejRes) { // Log rate limit exceeded event console.warn(`Rate limit exceeded for identifier: ${clientIdentifier}`); return res.status(429).json({ message: 'Too Many Requests' }); } // ... rest of handler logic ...
- Example (
- Secrets Management: Securely handle API keys (Section 4).
- Dependency Vulnerabilities: Regularly update dependencies (
npm audit fix
,yarn audit
) and use scanning tools (Snyk).
- Security Headers: Configure standard security headers (
X-Content-Type-Options
,Strict-Transport-Security
,Content-Security-Policy
) innext.config.js
for your frontend pages.
8. Handling Special Cases
Consider these SMS nuances:
- Phone Number Formatting: Enforce E.164 format (
+CountryCodeNumber
) strictly. Use libraries likelibphonenumber-js
for parsing/validation. - Message Length & Encoding:
- GSM-7 (standard): 160 chars/segment.
- UCS-2 (Unicode/emojis): 70 chars/segment.
- Long messages are segmented and billed per segment. Validate length client/server-side.
- Sender ID (
from
field):- Alphanumeric: Requires pre-registration, regulations vary by country, may not support replies.
- Numeric/Short Code: May allow replies, regulations vary.
- Consult Infobip docs/support for country-specific rules.
- Country-Specific Regulations: Adhere to local laws regarding content, opt-in, sender IDs.
- Delivery Reports: Implement webhooks for real-time status updates (requires a public endpoint).
- Time Zones: API times are typically UTC. Handle conversions appropriately for user display.
9. Implementing Performance Optimizations
For higher volume sending:
- Batching: Send multiple messages (different recipients or content) in one API call using the
messages
array in the payload. This reduces HTTP overhead. - Asynchronous Processing: For non-time-critical SMS, queue the sending task (BullMQ, Redis, SQS) and respond immediately to the user ("SMS queued"). Process jobs in the background.
- Connection Pooling: The SDK should manage this. If using raw HTTP clients, ensure keep-alive connections are used.
- Caching: Cache templates or user data before calling the API, not typically the API response itself for transactional SMS.
- Load Testing: Use tools (
k6
,artillery
) to test your endpoint's performance under load and identify bottlenecks.
10. Adding Monitoring, Observability, and Analytics
Gain visibility into your production system:
- Health Checks:
/api/health
endpoint returning200 OK
. - Performance Metrics: Track Infobip API call duration, endpoint latency (
/api/send-sms
), resource usage (CPU/memory). Use Vercel Analytics, Datadog, New Relic, Prometheus/Grafana. - Error Tracking: Integrate Sentry, Bugsnag, Rollbar to capture and analyze exceptions in API routes.
- Logging Aggregation: Centralize structured logs (Datadog, Logz.io, CloudWatch Logs, Loki).
- Dashboards: Visualize key metrics: SMS success/failure rate, endpoint latency (p50, p99), error rates, Infobip API duration.
- Alerting: Set up alerts for high error rates, high latency, specific Infobip errors (e.g., auth failures), resource exhaustion.
11. Troubleshooting and Caveats
Common Infobip integration issues:
- Authentication Errors (
401 Unauthorized
): CheckINFOBIP_API_KEY
,INFOBIP_BASE_URL
accuracy and environment loading. Ensure Base URL includeshttps://
if needed. Restart server after.env.local
changes. - Invalid Destination Number (
400 Bad Request
): Verify E.164 format (+...
). Check Infobip portal logs for specifics (e.g.,EC_INVALID_DESTINATION_ADDRESS
). - Free Trial Limitations: Can only send to your registered phone number.
- Sender ID Issues (
REJECTED...
): Use registered/approved sender IDs. Check country rules. Check account balance for paid accounts. - Message Content Rejected (
REJECTED_...
): Review content for spam triggers or regulatory violations. - Rate Limiting by Infobip: Implement client-side rate limiting/queuing or contact Infobip for higher throughput.
- SDK Issues: Check SDK documentation/version. Look for known issues on GitHub.
- Network Issues: Implement retries. Check server connectivity to Infobip endpoints.
Debugging Tips:
- Infobip Portal Logs: Essential for detailed request/delivery status.
- Log Request/Response: Log the exact payload sent to Infobip and the full response received.
- Isolate: Test with
curl
using the simplest valid payload.
12. Deployment and CI/CD
Deploying your Next.js app:
Deployment Platforms:
- Vercel/Netlify: Easy deployment via Git push. Configure Infobip environment variables in platform settings.
- Docker: Containerize the app. Deploy to AWS (ECS, EKS, App Runner), Google Cloud (Cloud Run, GKE), Azure (App Service, AKS), etc. Inject environment variables at runtime.
- Node.js Server: Run
next build && next start
on EC2, Droplets, etc. Manage environment variables via system or.env
files (usingdotenv
library for production builds).
Environment Variables in Production:
- Set
INFOBIP_API_KEY
,INFOBIP_BASE_URL
,INFOBIP_SENDER_ID
(if used) in the deployment environment's configuration. Do not deploy.env.local
. - Consider separate Infobip API keys for dev/staging/production.
CI/CD Pipeline (Example using GitHub Actions for Vercel):
- Create
.github/workflows/deploy.yml
:
# .github/workflows/deploy.yml
name: Deploy to Vercel
on:
push:
branches:
- main # Or your production branch
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4 # Use a recent version
# Optional: Add build, lint, test steps before deploying
# - name: Setup Node.js
# uses: actions/setup-node@v4
# with:
# node-version: '18' # Specify your Node version
# - name: Install Dependencies
# run: npm ci
# - name: Run Lint Check
# run: npm run lint
# - name: Run Tests
# # Ensure INFOBIP env vars are NOT needed or are mocked for unit tests
# run: npm test
- name: Deploy to Vercel
uses: amondnet/vercel-action@v20 # Or official Vercel CLI action
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required: Vercel API token
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} # Required: Vercel team/org ID
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} # Required: Vercel project ID
# Optional: Deploy to production environment (if main branch)
# vercel-prod: ${{ github.ref == 'refs/heads/main' }}
- Configure GitHub Secrets: Add
VERCEL_TOKEN
,VERCEL_ORG_ID
,VERCEL_PROJECT_ID
in your repository's Settings > Secrets and variables > Actions. - Vercel Environment Variables: Ensure
INFOBIP_API_KEY
,INFOBIP_BASE_URL
, etc., are set in the Vercel Project Settings UI (for Production, Preview, Development environments as needed).
Rollback Procedures:
- Vercel/Netlify allow instant rollbacks to previous deployments via their dashboards.
- For container/server setups, use blue-green deployments or revert to a previous stable Docker image tag/commit.
13. Verification and Testing
Ensure your integration is reliable:
Manual Verification Steps:
- Deploy to staging.
- Configure staging environment variables.
- Use
curl
/Postman/frontend to hit the deployed/api/send-sms
endpoint. - Check: Receive
200 OK
with Infobip IDs. - Check: SMS received on the target phone (respect free trial limits).
- Check: Invalid inputs return
400 Bad Request
. - Check: Invalid API key returns
401 Unauthorized
. - Check: Review logs (deployment platform & Infobip portal).
Automated Testing:
-
Unit Tests (API Route Logic): Use Jest to test
pages/api/send-sms.js
.- Mock
@infobip-api/sdk
to avoid actual API calls. - Test valid requests, missing/invalid inputs, SDK success, SDK errors (various types).
- Assert response status code, body, and SDK mock calls.
Example Unit Test Snippet (using Jest):
// pages/api/__tests__/send-sms.test.js import { createMocks } from 'node-mocks-http'; import sendSmsHandler from '../send-sms'; // Adjust path relative to test file import { Infobip } from '@infobip-api/sdk'; // Import to enable mocking // Mock the Infobip SDK constructor and its chained methods jest.mock('@infobip-api/sdk', () => { const mockSend = jest.fn(); // Create a mock function for 'send' return { Infobip: jest.fn().mockImplementation(() => ({ channels: { sms: { send: mockSend, // Assign the mock function here }, }, })), AuthType: { ApiKey: 'ApiKey' }, // Provide AuthType used // Expose the mockSend function for use in tests if needed directly // This approach might vary based on exact mocking needs __mockSend: mockSend, }; }); // Get a reference to the mock 'send' function // This line might need adjustment based on how Jest resolves the mock instance. // Often, you interact with the mock instance created *inside* the test. // Let's get the mock via the imported module after it's been mocked. const { __mockSend: mockInfobipSend } = require('@infobip-api/sdk'); describe('/api/send-sms handler', () => { beforeEach(() => { // Reset mock state and environment variables before each test mockInfobipSend.mockClear(); jest.resetModules(); // May be needed if the handler imports the SDK at top level process.env.INFOBIP_API_KEY = 'fake-key'; process.env.INFOBIP_BASE_URL = 'fake.api.infobip.com'; process.env.INFOBIP_SENDER_ID = 'InfoSMS'; // Default sender for tests }); // Add test cases here... // Example: Test successful SMS sending test('should return 200 on successful SMS send', async () => { const mockSuccessResponse = { data: { bulkId: 'test-bulk-id', messages: [ { messageId: 'test-message-id', status: { groupName: 'PENDING', name: 'PENDING_ACCEPTED' }, to: '+14155552671', }, ], }, }; mockInfobipSend.mockResolvedValue(mockSuccessResponse); const { req, res } = createMocks({ method: 'POST', body: { to: '+14155552671', text: 'Test message', }, }); await sendSmsHandler(req, res); expect(res._getStatusCode()).toBe(200); expect(JSON.parse(res._getData())).toEqual(expect.objectContaining({ success: true, message: 'SMS sent successfully.', infobipResponse: expect.objectContaining({ messageId: 'test-message-id', status: 'PENDING_ACCEPTED', }), })); expect(mockInfobipSend).toHaveBeenCalledTimes(1); expect(mockInfobipSend).toHaveBeenCalledWith(expect.objectContaining({ messages: expect.arrayContaining([ expect.objectContaining({ destinations: [{ to: '+14155552671' }], text: 'Test message', from: 'InfoSMS', // Check default sender is used }), ]), })); }); // Example: Test missing 'to' field test('should return 400 if \'to\' field is missing', async () => { const { req, res } = createMocks({ method: 'POST', body: { text: 'Test message', // 'to' field is missing }, }); await sendSmsHandler(req, res); expect(res._getStatusCode()).toBe(400); expect(JSON.parse(res._getData())).toEqual({ message: 'Missing or invalid \'to\' field (phone number).' }); expect(mockInfobipSend).not.toHaveBeenCalled(); }); // Add more tests for other scenarios (invalid 'text', Infobip API errors, etc.) });
- Mock
-
Integration Tests: Test the API route without mocking the SDK, using test Infobip credentials (if available) or a dedicated test environment. This verifies actual interaction but incurs costs/uses quotas.
-
End-to-End (E2E) Tests: Use tools like Cypress or Playwright to simulate user interaction (if you build a frontend) that triggers the API call and potentially verifies SMS reception (difficult to automate fully).