@iblusend/sdk
v0.2.2
Published
Official TypeScript SDK for the iBluSend iMessage API: send texts, media, voice memos and tapbacks, read conversations and groups, manage contacts and opt-out, verify webhooks, prepare FaceTime calls. Zero dependencies.
Maintainers
Readme
@iblusend/sdk
The official TypeScript client for the iBluSend iMessage API: send texts, media, voice memos and tapbacks from your own Macs, read conversations and groups, manage contacts and opt-out, verify webhooks, and prepare FaceTime Audio calls.
- Typed requests and responses for every endpoint, pinned to the public OpenAPI contract by tests
- Zero runtime dependencies — Node 18+, Bun, Deno, Cloudflare Workers, anywhere
fetchexists - Webhook signature verification with a constant-time compare
- Sensible retries, idempotency keys, per-request timeouts
- ESM and CommonJS builds, bundled type definitions
Docs: https://iblusend.com/developers/docs/sdk · API reference: https://iblusend.com/developers/docs · Spec: https://iblusend.com/openapi.yaml
Install
npm install @iblusend/sdkQuickstart
import { IBluSend } from "@iblusend/sdk";
const client = new IBluSend({ apiKey: process.env.IBLU_KEY! });
// Paced send (default) — queues through the drip pipeline
const sent = await client.messages.send({ to: "+13035550101", message: "Hey 👋" });
// Instant send, safe to retry thanks to the Idempotency-Key
await client.messages.send(
{ to: "+13035550101", message: "Your code is 4821", send_mode: "instant" },
{ idempotencyKey: "otp-4821-user-77" },
);
const status = await client.messages.status(sent.message_id);
const thread = await client.conversations.get({ phone: "+13035550101", limit: 50 });Create a key under Developer → API Keys. An iblu_test_… key runs against the sandbox on every plan, including free — client.sandbox reads true.
What the client covers
| Resource | Methods |
|---|---|
| client.messages | send, sendVoiceMemo, status |
| client.conversations | get, list, messages |
| client.groups | list, get, send, create, rename, setPhoto, addMembers, removeMembers, leave, command |
| client.lines | list |
| client.signals | react, unreact, typing, markRead |
| client.contacts | list, create, update |
| client.optOut | get, set |
| client.bot | get, set |
| client.capability | check |
| client.usage | get |
| client.inbound | ingest |
| client.calls | lines, prepare, get, listIncoming, end, transfer, settings, updateSettings, logs, log |
calls.settings and calls.updateSettings arrive in 0.2.1; earlier versions do not have them. calls.logs and calls.log arrive in 0.2.2.
Anything else: client.request(operationId, { path, query, body, options }) returns { data, response, idempotentReplay } for any operation in OPERATIONS.
Verify a webhook
// Next.js App Router
import { parseWebhook, readWebhookHeaders, WebhookSignatureError } from "@iblusend/sdk";
export async function POST(req: Request) {
const raw = await req.text(); // the RAW body — never re-serialize parsed JSON
try {
const event = await parseWebhook(raw, readWebhookHeaders(req.headers), process.env.IBLU_WEBHOOK_SECRET!);
// event.event, event.event_id (dedupe on it), event.data
return new Response("ok");
} catch (err) {
if (err instanceof WebhookSignatureError) return new Response("bad signature", { status: 401 });
throw err;
}
}verifyWebhookSignature(rawBody, signatureHeader, secret) returns a boolean if you only want the check.
Errors and retries
import { IBluSendError, IBluSendConnectionError } from "@iblusend/sdk";
try {
await client.messages.send({ to: "+13035550101", message: "hi", send_mode: "instant" });
} catch (err) {
if (err instanceof IBluSendError) {
err.status; // 409
err.code; // "no_online_device"
err.rateLimit; // on a rate-limit 429: { retryAfter, resetAt, limit, remaining }
} else if (err instanceof IBluSendConnectionError) {
err.timedOut; // true when the client-side timeout fired
}
}Reads, the idempotent DELETEs, and any POST carrying an idempotencyKey are retried up to twice (configurable) on a dropped connection, 408, 502, 503, 504, or a 429 whose Retry-After is ten seconds or less. A plain POST is never retried. A 429 asking for a longer wait is thrown at once with err.rateLimit filled in.
Instant sends are limited to 10 a minute per key. Daily limits count distinct cold recipients per line; a send past them fails with 429 device_daily_cap_exceeded and no Retry-After. Retrying the same new contact later that day will not help; replies to contacts who wrote back still go out.
Options
new IBluSend({
apiKey, // required
baseUrl, // default https://api.iblusend.com/functions/v1
appBaseUrl, // default https://iblusend.com (Calls API)
fetch, // bring your own
timeoutMs: 30_000, // per request
maxRetries: 2, // retryable requests only
});License
MIT
