Because pindo-sms communicates with the Pindo API using your secret token, you must only call it from server-side code. In a Next.js application, the right place is an API route or a Route Handler — never in a React component that runs in the browser.
Never import or instantiate PindoSMS inside a React component, a client component ('use client'), or any file that is bundled for the browser. Doing so exposes your API token to anyone who inspects the page source or network requests.
Installation
Environment variables
Store your token as a server-only environment variable. Do not prefix it with NEXT_PUBLIC_.
# Server-only — never exposed to the browser
PINDO_API_TOKEN=your_api_token_here
| Variable | Accessible in | Use for |
|---|
PINDO_API_TOKEN | Server only | Secret API token — use this |
NEXT_PUBLIC_PINDO_API_TOKEN | Server and browser | Never store secrets here |
Next.js automatically excludes variables without the NEXT_PUBLIC_ prefix from the client bundle. Keeping the token server-only is enforced at the framework level.
Next.js API route (Pages Router)
Create a route at pages/api/send-sms.ts that reads the token on the server and calls sendSMS:
import type { NextApiRequest, NextApiResponse } from 'next';
import { PindoSMS, SMSPayload } from 'pindo-sms';
const pindo = new PindoSMS(process.env.PINDO_API_TOKEN!);
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
if (req.method !== 'POST') {
return res.status(405).json({ message: 'Method not allowed' });
}
const { to, text, sender } = req.body as SMSPayload;
if (!to || !text || !sender) {
return res.status(400).json({ message: 'Missing required fields: to, text, sender' });
}
try {
const response = await pindo.sendSMS({ to, text, sender });
return res.status(200).json(response);
} catch (error) {
console.error('Failed to send SMS:', error);
return res.status(500).json({ message: 'Failed to send SMS' });
}
}
Next.js Route Handler (App Router)
For projects using the App Router, create a route at app/api/send-sms/route.ts:
app/api/send-sms/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { PindoSMS, SMSPayload } from 'pindo-sms';
const pindo = new PindoSMS(process.env.PINDO_API_TOKEN!);
export async function POST(req: NextRequest) {
const body = await req.json() as SMSPayload;
const { to, text, sender } = body;
if (!to || !text || !sender) {
return NextResponse.json(
{ message: 'Missing required fields: to, text, sender' },
{ status: 400 },
);
}
try {
const response = await pindo.sendSMS({ to, text, sender });
return NextResponse.json(response);
} catch (error) {
console.error('Failed to send SMS:', error);
return NextResponse.json({ message: 'Failed to send SMS' }, { status: 500 });
}
}
Calling the API route from a React component
Your client-side code calls the Next.js API route — not the Pindo API directly:
components/SendSMSForm.tsx
'use client';
import { useState } from 'react';
export function SendSMSForm() {
const [status, setStatus] = useState<string>('');
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
setStatus('Sending...');
const form = event.currentTarget;
const data = Object.fromEntries(new FormData(form));
const res = await fetch('/api/send-sms', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
setStatus(res.ok ? 'SMS sent!' : 'Failed to send SMS.');
}
return (
<form onSubmit={handleSubmit}>
<input name="to" placeholder="+250781234567" required />
<input name="text" placeholder="Your message" required />
<input name="sender" placeholder="MyApp" required />
<button type="submit">Send</button>
{status && <p>{status}</p>}
</form>
);
}
The React component never touches the Pindo API or the token. It only calls your own Next.js route, which keeps the secret on the server.