Skip to main content
spectrum-ts is a unified messaging SDK for TypeScript. Write your logic once, deliver it across every platform — iMessage, WhatsApp Business, your terminal, or a custom platform you build yourself.

Installation

spectrum-ts is the batteries-included package — it bundles the runtime and every official provider.
npm install spectrum-ts
pnpm add spectrum-ts
yarn add spectrum-ts
bun add spectrum-ts
For a leaner install, depend on @spectrum-ts/core plus only the providers you need:
bun add @spectrum-ts/core @spectrum-ts/imessage @spectrum-ts/telegram
Either way, the spectrum-ts/providers/<platform> import paths work as long as the matching provider package is installed. Requires TypeScript 5 or later (TypeScript 6 is also supported).

Core concepts

Spectrum is built around four primitives:
PrimitiveWhat it represents
MessageAn incoming piece of content — text, attachments, or structured data — from any platform.
SpaceA conversation context. A DM, a group chat, a terminal session. You send messages into a space.
UserA participant on a platform, identified by a platform-specific ID.
Platform providerA platform adapter (iMessage, terminal, WhatsApp, or your own) that translates platform-specific protocols into Spectrum’s unified interface.
Every message arrives as a [Space, Message] tuple. The space gives you the ability to respond; the message gives you the content and metadata.

Quickstart

Get your credentials

Find your PROJECT_ID and SECRET_KEY in your project Settings on the dashboard. You can pass these to Spectrum(...) directly, or omit them and let Spectrum read SPECTRUM_PROJECT_ID and SPECTRUM_PROJECT_SECRET from the environment. The same explicit-wins-over-env fallback applies to webhookSecret (SPECTRUM_WEBHOOK_SECRET) and to every provider’s config. Provider config fallbacks are automatic and convention-based: any text config field can be supplied through the environment as SPECTRUM_<PLATFORM>_<FIELD>, where <PLATFORM> is the provider’s id upper-cased (spaces and punctuation become _) and <FIELD> is the config key in UPPER_SNAKE_CASE. For example Telegram’s botToken reads from SPECTRUM_TELEGRAM_BOT_TOKEN, and WhatsApp Business’s phoneNumberId reads from SPECTRUM_WHATSAPP_BUSINESS_PHONE_NUMBER_ID. Explicit config always wins over the environment — see the provider setup pages for each field’s env var name.

Run your first app

import { Spectrum } from "spectrum-ts";
import { imessage } from "spectrum-ts/providers/imessage";

const app = await Spectrum({
  projectId: "your-project-id",
  projectSecret: "your-project-secret",
  providers: [
    imessage.config(),
  ],
});

for await (const [space, message] of app.messages) {
  if (message.content.type === "text") {
    console.log(`[${message.platform}] ${message.sender.id}: ${message.content.text}`);
    await space.send("hello world");
  }
}
Projectless providers (like terminal) can be used without credentials:
import { Spectrum } from "spectrum-ts";
import { terminal } from "spectrum-ts/providers/terminal";

const app = await Spectrum({
  providers: [terminal.config()],
});

The app instance

Spectrum() returns a — an object that merges a message stream with platform-specific custom event streams.
app.messages                 // AsyncIterable<[Space, Message]>
await app.send(space, ...)   // send into a space
await app.responding(space, fn)  // run fn with a typing indicator
await app.webhook(req, handler)  // handle an inbound webhook delivery
await app.stop()             // graceful shutdown
Custom events emitted by providers are exposed as flat async iterables on the same object — see Custom events and lifecycle. app.webhook() handles both native Spectrum webhooks and Fusor webhooks through the same method. See Webhooks for setup and framework adapters.

Multi-platform in three lines

Combine providers to receive and send across platforms simultaneously:
import { Spectrum } from "spectrum-ts";
import { imessage } from "spectrum-ts/providers/imessage";
import { terminal } from "spectrum-ts/providers/terminal";

const app = await Spectrum({
  projectId: process.env.PROJECT_ID!,
  projectSecret: process.env.PROJECT_SECRET!,
  providers: [
    imessage.config(),
    terminal.config(),
  ],
});

for await (const [space, message] of app.messages) {
  await space.responding(async () => {
    await message.reply("Hello from Spectrum.");
  });
}
Messages from every provider merge into the single app.messages stream. The message.platform field identifies the source.

Logging

Spectrum emits structured logs across the core runtime and providers. Control the verbosity with logLevel:
const app = await Spectrum({
  projectId: process.env.PROJECT_ID!,
  projectSecret: process.env.PROJECT_SECRET!,
  providers: [imessage.config()],
  logLevel: "debug",
});
Log output is sanitized — sensitive fields like tokens and secrets are redacted from error attributes before they reach any log destination.

Telemetry

Spectrum has built-in OpenTelemetry instrumentation. Enable it by passing telemetry: true:
const app = await Spectrum({
  projectId: process.env.PROJECT_ID!,
  projectSecret: process.env.PROJECT_SECRET!,
  providers: [imessage.config()],
  telemetry: true,
});
When enabled, Spectrum traces initialization, provider setup, message send/receive/get flows, space resolution, and custom events. Each span includes attributes like the provider name, space ID, content type, and sender kind. Traces are sent to the Photon OTLP endpoint by default. Standard OTEL_EXPORTER_OTLP_* environment variables override the default endpoint and headers. Calling app.stop() flushes any pending telemetry data before shutting down.