JavaScript SDK
Send SMS from Node.js and TypeScript with the official @textbee/sdk package, a zero dependency wrapper around the textbee.dev REST API.
@textbee/sdk is the official JavaScript and TypeScript client for textbee.dev. It wraps the same REST API documented elsewhere in these docs, so anything you can do with the SDK you can also do with plain HTTP.
It has zero dependencies, ships its own TypeScript types, and runs on Node 18 or newer, as well as Bun, Deno, Cloudflare Workers, and Vercel Edge.
If you would rather call the API directly, see Sending SMS.
Install
pnpm add @textbee/sdkQuickstart
Generate an API key from your dashboard, then:
import { Textbee } from '@textbee/sdk'
const textbee = new Textbee({ apiKey: process.env.TEXTBEE_API_KEY })
await textbee.sendSms({
recipients: ['+251912345678'],
message: 'Hello from textbee!',
})
Never hardcode your API key. Read it from an environment variable so it stays out of your source control.
Send options
sendSms requires only message and recipients. The rest are optional.
| Option | Type | Purpose |
| --- | --- | --- |
| deviceId | string | Which phone sends the message |
| simSubscriptionId | number | Which SIM sends it on a multi-SIM phone |
| scheduledAt | string or Date | Send later instead of now |
await textbee.sendSms({
recipients: ['+251912345678'],
message: 'Your appointment is tomorrow at 9am',
deviceId: '65f0000000000000000000aa',
simSubscriptionId: 2,
scheduledAt: new Date(Date.now() + 60 * 60 * 1000),
})
deviceId
Leave it out and textbee chooses the sender for you: your default device first, and otherwise the enabled device with the most recent heartbeat. Pass one to force a specific phone. You can list your device ids with getDevices().
A malformed id fails the request rather than falling back to another phone, so a typo can never send from the wrong device.
simSubscriptionId
Find this value in the textbee Android app under Dashboard, in the SIM Cards section. Each SIM shows its subscription id next to it with a copy button.
Leave it out and the phone decides: it uses the preferred SIM set in the app's settings, or the system default if none is set.
Be aware that this value is not validated. If the id does not match a SIM currently in the phone it is ignored, and the message goes out from the preferred or default SIM instead. Nothing fails and no error is returned, so if the SIM matters, confirm which number the message arrived from.
scheduledAt
Accepts an ISO 8601 string or a Date. It must be in the future, and you can schedule up to 72 hours ahead. Omit it to send immediately.
Devices
const devices = await textbee.getDevices()
const device = await textbee.getDevice(deviceId)
// Change which device handles sends that omit deviceId
await textbee.setDefaultDevice(deviceId)
Message history and delivery status
getMessages is paginated, filterable, and searchable:
const { data, meta } = await textbee.getMessages(deviceId, {
type: 'received', // 'all' | 'sent' | 'received'
page: 1,
limit: 50,
search: 'invoice',
})
console.log(meta.total, meta.totalPages)
To follow a single message or a whole batch:
// One message and its current status
const sms = await textbee.getSms(deviceId, smsId)
// A batch, using the smsBatchId returned by sendSms
const { batch, messages } = await textbee.getSmsBatch(deviceId, smsBatchId)
Verifying webhooks
textbee signs every webhook delivery with HMAC-SHA256 and sends the hex digest in the X-Signature header. The SDK ships a helper so you do not have to implement the comparison yourself.
Pass the raw request body rather than a re-serialized object whenever your framework exposes it. Re-serializing a parsed object usually produces the same bytes, but not always, and a mismatch there looks like an invalid signature.
import { verifyWebhookSignature } from '@textbee/sdk'
app.post(
'/webhooks/textbee',
express.raw({ type: 'application/json' }),
async (req, res) => {
const valid = await verifyWebhookSignature({
payload: req.body.toString('utf8'),
signature: req.get('x-signature'),
signingSecret: process.env.TEXTBEE_WEBHOOK_SECRET,
})
if (!valid) return res.sendStatus(401)
const event = JSON.parse(req.body.toString('utf8'))
// handle the event
res.sendStatus(200)
},
)
See Webhooks for the event types and payload shapes.
Handling errors
Any non-2xx response throws a TextbeeError carrying the HTTP status and the parsed response body. Network failures reject with the underlying fetch error instead, so you can tell the two apart.
import { TextbeeError } from '@textbee/sdk'
try {
await textbee.sendSms({ recipients: ['+251912345678'], message: 'hi' })
} catch (error) {
if (error instanceof TextbeeError) {
console.error(error.status, error.message)
} else {
throw error
}
}
Client options
new Textbee({
apiKey: 'your-api-key',
baseUrl: 'https://api.textbee.dev/api/v1', // override for self-hosted instances
})
What the SDK does not cover yet
The SDK currently focuses on sending and reading messages. Bulk sending and some device management operations are REST only for now. Use the Sending bulk SMS guide for those.
Links
- Package on npm: @textbee/sdk
- Source and issues: github.com/textbee/textbee-js