Send SMS with Next.js
Send a text from a Next.js App Router route handler with the TextKit SDK. The key stays on the server.
Prompt for your AI agent
Paste this into Claude Code, Cursor or any coding agent to have it do the integration:
Add SMS sending to this Next.js project with TextKit.
- Docs for this stack: https://jointextkit.com/docs/send-with-nextjs.md
- API: POST https://api.jointextkit.com/v1/messages with `Authorization: Bearer
<key>` and a JSON body `{"to": "+1...", "body": "..."}`.
- Use the official SDK on the server only (route handler or server action): `npm
install jointextkit`, then `new TextKit().messages.send({ to, body })`.
- Read the key from the `TEXTKIT_API_KEY` environment variable. Never hardcode
it or send it to the browser.
- Phone numbers are E.164 and US or Canada only (`+1` followed by 10 digits).
- A test key (`ssms_sk_test_`) simulates: `+15005550006` always ends `delivered`
and no phone gets a text.
- Errors are JSON: `{"error": {"code", "message", "param"}}` with a 4xx status.
On `429`, wait `Retry-After` seconds and retry.
- Send only from server code that runs after a real event (a signup, an order).
Never expose an endpoint that texts any number a visitor types.Before you start
- An API key. Create a free account and copy the test key
(
ssms_sk_test_...) from Home in the console. It works at once, no card. - Next.js 13.4 or later with the App Router, on the Node.js runtime.
- Put the key in an environment variable:
export TEXTKIT_API_KEY=ssms_sk_test_...1. Install the SDK
npm install jointextkitAdd the key to .env.local (and to your host's environment variables when you deploy):
TEXTKIT_API_KEY=ssms_sk_test_...Never prefix it with NEXT_PUBLIC_: that would ship it to the browser.
2. Create a route handler
Create app/api/send-sms/route.ts:
import { TextKit, TextKitError } from 'jointextkit';
export async function POST(request: Request) {
const { to, body } = await request.json();
const sms = new TextKit(); // reads TEXTKIT_API_KEY
try {
const message = await sms.messages.send({ to, body });
return Response.json({ id: message.id, status: message.status });
} catch (err) {
if (err instanceof TextKitError) {
return Response.json({ error: err.code, message: err.message }, { status: err.status });
}
throw err;
}
}The client is created inside the handler so next build never needs the key.
3. Try it
npm run dev
curl -X POST http://localhost:3000/api/send-sms \
-H "Content-Type: application/json" \
-d '{"to": "+15005550006", "body": "Hello from TextKit"}'It returns {"id": "msg_...", "status": "delivered"}. A server action works the
same way: call new TextKit().messages.send(...) inside a 'use server' function.
This endpoint texts whatever number it receives. Put it behind your own sign-in before you deploy it, or call
sendfrom code that runs after a real event (a signup, an order). An open endpoint lets anyone text through your account.
What the test key did
+15005550006 is a sandbox number: the send comes back delivered and no
phone gets a text (the response says "simulated": true). Put your own
verified phone in to and the test key texts it for real. See
Sandbox & test numbers.
Handle errors
The SDK throws a TextKitError with the API's code, status and
message (plus param for a bad field and retryAfter on a rate limit). It
retries network failures and 429s for you, with an idempotency key, so a
retry never sends twice.
| Status | Code | What to do |
|---|---|---|
| 400 | invalid_request | A field is missing or malformed; param names it. |
| 401 | invalid_api_key | The key is missing, revoked or mistyped. |
| 402 | insufficient_credits | Add credits; nothing was sent or charged. |
| 403 | recipient_opted_out | They texted STOP. Do not retry. |
| 429 | rate_limited | Wait Retry-After seconds, then retry. |
Every code is in Errors.
Go live
- Add a number in the console.
- Register your use case. We file your 10DLC registration; toll-free numbers use a guided verification form. Until carriers approve it, the number texts only your verified numbers. See Compliance & registration.
- Swap in a live key (
ssms_sk_live_...). The code stays the same.
Next steps
- Webhooks: delivery receipts and replies, pushed to your server.
- Verify: one-time codes in two calls, billed only when verified.
- Send an SMS: every field the endpoint takes.
- All quickstarts: the same guide for other languages and frameworks.