Installation and Setup
Install the HTTP client for making API requests:
npm install axiosEnvironment Variables
Create a .env.local file:
# .env.local
NESTSMS_API_KEY=nsms_live_your_api_key_here
NESTSMS_API_URL=https://api.nestsms.comServer Actions
Use Server Actions to securely communicate with the NestSMS API from your Next.js application without exposing your API key to the client.
Send SMS Server Action
Create a server action to send SMS:
// app/actions/sms.ts
"use server";
export async function sendSMS(phoneNumber: string, message: string) {
try {
const response = await fetch("https://api.nestsms.com/api/v1/sms/send", {
method: "POST",
headers: {
"X-API-Key": process.env.NESTSMS_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
to: phoneNumber,
message: message,
sender_id: "YourBrand",
message_type: "transactional",
}),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || "Failed to send SMS");
}
return {
success: true,
jobId: data.job_id,
message: data.message,
};
} catch (error) {
console.error("SMS Error:", error);
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error",
};
}
}Check SMS Status
// app/actions/sms.ts
"use server";
export async function checkSMSStatus(messageId: string) {
try {
const response = await fetch(`https://api.nestsms.com/api/v1/sms/status/${messageId}`, {
method: "GET",
headers: {
"X-API-Key": process.env.NESTSMS_API_KEY!,
},
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || "Failed to check status");
}
return {
success: true,
status: data.status,
deliveredAt: data.delivered_at,
sentAt: data.sent_at,
};
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error",
};
}
}Get Account Balance
// app/actions/sms.ts
"use server";
export async function getBalance() {
try {
const response = await fetch("https://api.nestsms.com/api/v1/sms/balance", {
method: "GET",
headers: {
"X-API-Key": process.env.NESTSMS_API_KEY!,
},
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || "Failed to fetch balance");
}
return {
success: true,
balance: data.balance,
currency: data.currency,
smsSentToday: data.sms_sent_today,
};
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error",
};
}
}Client Component Usage
// app/components/sms-form.tsx
"use client";
import { useState } from "react";
import { sendSMS } from "../actions/sms";
export function SMSForm() {
const [phone, setPhone] = useState("");
const [message, setMessage] = useState("");
const [loading, setLoading] = useState(false);
const [result, setResult] = useState<any>(null);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setLoading(true);
setResult(null);
const res = await sendSMS(phone, message);
setResult(res);
setLoading(false);
if (res.success) {
setPhone("");
setMessage("");
}
}
return (
<div>
<form onSubmit={handleSubmit}>
<div>
<label>Phone Number:</label>
<input
type="tel"
value={phone}
onChange={(e) => setPhone(e.target.value)}
placeholder="9840123456"
required
/>
</div>
<div>
<label>Message:</label>
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Your message here"
required
maxLength={720}
/>
<small>{message.length}/720 characters</small>
</div>
<button type="submit" disabled={loading}>
{loading ? "Sending..." : "Send SMS"}
</button>
</form>
{result && (
<div className={result.success ? "success" : "error"}>
{result.success ? (
<>
<p>✓ SMS sent successfully!</p>
<p>Job ID: {result.jobId}</p>
</>
) : (
<p>✗ Error: {result.error}</p>
)}
</div>
)}
</div>
);
}API Routes
Use Next.js API Routes when you want to expose your own backend endpoint that communicates with the NestSMS API.
Create API Route
// app/api/sms/send/route.ts
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { to, message, senderName } = body;
// Validate input
if (!to || !message) {
return NextResponse.json(
{ success: false, error: "Missing required fields" },
{ status: 400 },
);
}
// Call NestSMS API
const response = await fetch("https://api.nestsms.com/api/v1/sms/send", {
method: "POST",
headers: {
"X-API-Key": process.env.NESTSMS_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
to,
message,
sender_id: senderName || "YourBrand",
message_type: "transactional",
}),
});
const data = await response.json();
if (!response.ok) {
return NextResponse.json({ success: false, error: data.error }, { status: response.status });
}
return NextResponse.json({
success: true,
jobId: data.job_id,
recipientsCount: data.recipients_count,
cost: data.cost,
});
} catch (error) {
console.error("API Error:", error);
return NextResponse.json({ success: false, error: "Internal server error" }, { status: 500 });
}
}Bulk SMS API Route
// app/api/sms/bulk/route.ts
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { recipients, message } = body;
// Validate - max 1000 recipients per batch
if (!Array.isArray(recipients) || recipients.length > 1000) {
return NextResponse.json(
{ success: false, error: "Recipients must be an array (max 1000)" },
{ status: 400 },
);
}
const response = await fetch("https://api.nestsms.com/api/v1/sms/send", {
method: "POST",
headers: {
"X-API-Key": process.env.NESTSMS_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
to: recipients,
message,
sender_id: "YourBrand",
message_type: "promotional",
}),
});
const data = await response.json();
return NextResponse.json(data, { status: response.status });
} catch (error) {
return NextResponse.json({ success: false, error: "Internal server error" }, { status: 500 });
}
}Status Check API Route
// app/api/sms/status/[messageId]/route.ts
import { NextRequest, NextResponse } from "next/server";
export async function GET(request: NextRequest, { params }: { params: { messageId: string } }) {
try {
const { messageId } = params;
const response = await fetch(`https://api.nestsms.com/api/v1/sms/status/${messageId}`, {
method: "GET",
headers: {
"X-API-Key": process.env.NESTSMS_API_KEY!,
},
});
const data = await response.json();
return NextResponse.json(data, { status: response.status });
} catch (error) {
return NextResponse.json({ success: false, error: "Internal server error" }, { status: 500 });
}
}OTP Verification
Use the NestSMS API to send one-time passwords for authentication and verification workflows.
Send OTP
// app/actions/otp.ts
"use server";
import { cookies } from "next/headers";
export async function sendOTP(phoneNumber: string) {
// Generate 6-digit OTP
const otp = Math.floor(100000 + Math.random() * 900000).toString();
// Send SMS
const response = await fetch("https://api.nestsms.com/api/v1/sms/send", {
method: "POST",
headers: {
"X-API-Key": process.env.NESTSMS_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
to: phoneNumber,
message: `Your verification code is ${otp}. Valid for 5 minutes.`,
sender_id: "YourApp",
message_type: "otp",
}),
});
const data = await response.json();
if (!response.ok) {
return { success: false, error: data.error };
}
// Store OTP in cookie (encrypted in production!)
cookies().set("otp", otp, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
maxAge: 300, // 5 minutes
});
return { success: true, jobId: data.job_id };
}
export async function verifyOTP(phoneNumber: string, enteredOTP: string) {
const storedOTP = cookies().get("otp")?.value;
if (!storedOTP) {
return { success: false, error: "OTP expired" };
}
if (storedOTP !== enteredOTP) {
return { success: false, error: "Invalid OTP" };
}
// Clear OTP
cookies().delete("otp");
return { success: true };
}Responses, Errors, and Best Practices
Successful SMS Send Response
{
"success": true,
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"message": "SMS queued successfully",
"recipients_count": 1,
"cost": 1.5
}Bulk SMS Response
{
"success": true,
"job_id": "660f9511-f39c-52e5-b827-557766551111",
"message": "Bulk SMS queued successfully",
"recipients_count": 150,
"cost": 225.0
}Status Check Response
{
"success": true,
"message_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "delivered",
"to": "9840123456",
"message": "Your OTP is 123456",
"sender_id": "YourApp",
"sent_at": "2025-08-21T14:30:00Z",
"delivered_at": "2025-08-21T14:30:05Z",
"cost": 1.5
}Error Response
{
"success": false,
"error": "Insufficient balance",
"code": "INSUFFICIENT_BALANCE"
}Error Handling
async function sendSMSWithErrorHandling(phone: string, message: string) {
try {
const response = await fetch("https://api.nestsms.com/api/v1/sms/send", {
method: "POST",
headers: {
"X-API-Key": process.env.NESTSMS_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({ to: phone, message, sender_id: "YourBrand" }),
});
const data = await response.json();
if (!response.ok) {
// Handle specific error codes
switch (data.code) {
case "INSUFFICIENT_BALANCE":
return { error: "Please top up your account balance" };
case "INVALID_TO_FORMAT":
return { error: "Invalid phone number format" };
case "MESSAGE_TOO_LONG":
return { error: "Message exceeds maximum length" };
case "RATE_LIMIT_EXCEEDED":
return {
error: `Rate limit exceeded. Retry after ${data.retry_after}s`,
};
default:
return { error: data.error || "Failed to send SMS" };
}
}
return { success: true, data };
} catch (error) {
return { error: "Network error. Please try again." };
}
}Best Practices
- Environment Variables: Always use environment variables for API keys
- Error Handling: Implement comprehensive error handling
- Rate Limiting: Respect API rate limits (60 req/min)
- Message Length: Keep messages under 720 characters (text) or 335 (unicode)
- Status Tracking: Store
job_idto track delivery status - Batch Processing: For bulk SMS, send maximum 1000 recipients per request
Next Steps