Send SMS with Cloudflare Workers

Send a text from a Cloudflare Worker with the TextKit SDK. The key lives in a Worker secret.

Prompt for your AI agent

Paste this into Claude Code, Cursor or any coding agent to have it do the integration:

text
Add SMS sending to this Cloudflare Workers project with TextKit.

- Docs for this stack:
  https://jointextkit.com/docs/send-with-cloudflare-workers.md
- API: POST https://api.jointextkit.com/v1/messages with `Authorization: Bearer
  <key>` and a JSON body `{"to": "+1...", "body": "..."}`.
- Use the official SDK: `npm install jointextkit`, pass the key from the
  Worker's `env` (a secret), then `new
  TextKit(env.TEXTKIT_API_KEY).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.
  • A Workers project created with npm create cloudflare@latest.
  • Put the key in an environment variable:
bash
export TEXTKIT_API_KEY=ssms_sk_test_...

1. Install the SDK and add the key

bash
npm install jointextkit
npx wrangler secret put TEXTKIT_API_KEY

For wrangler dev, put TEXTKIT_API_KEY=ssms_sk_test_... in .dev.vars.

2. Send an SMS

Replace src/index.ts:

typescript
import { TextKit, TextKitError } from 'jointextkit';

interface Env {
  TEXTKIT_API_KEY: string;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    if (request.method !== 'POST') return new Response('Method not allowed', { status: 405 });

    const { to, body } = (await request.json()) as { to: string; body: string };
    const sms = new TextKit(env.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;
    }
  },
};

3. Try it, then deploy

bash
npx wrangler dev

curl -X POST http://localhost:8787 \
  -H "Content-Type: application/json" \
  -d '{"to": "+15005550006", "body": "Hello from TextKit"}'

npx wrangler deploy

It returns {"id": "msg_...", "status": "delivered"}.

This endpoint texts whatever number it receives. Put it behind your own sign-in before you deploy it, or call send from 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.

StatusCodeWhat to do
400invalid_requestA field is missing or malformed; param names it.
401invalid_api_keyThe key is missing, revoked or mistyped.
402insufficient_creditsAdd credits; nothing was sent or charged.
403recipient_opted_outThey texted STOP. Do not retry.
429rate_limitedWait Retry-After seconds, then retry.

Every code is in Errors.

Go live

  1. Add a number in the console.
  2. 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.
  3. 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.