Theo Docs
TheoKitTheoKit-SDKTheoUITheo PaaS
Concepts

Gateways

Build agents that chat on Telegram, Discord, Slack, WhatsApp, Teams, and Email.

Canonical contract: packages/sdk/docs.md — "Slack gateway (v1.19+)".

A gateway is a channel adapter — it lets your agent receive messages from a platform (Telegram, Slack, ...) and reply, hiding platform-specific shape behind a portable MessageEvent union.

Shipped adapters

PackagePlatformTransport
@theokit/gateway-telegramTelegramlong-polling + webhook
@theokit/gateway-discordDiscordWebSocket Gateway
@theokit/gateway-slackSlackSocket Mode
@theokit/gateway-whatsappWhatsAppMeta Cloud API webhook + whatsapp-web.js bridge
@theokit/gateway-teamsMicrosoft Teams@microsoft/teams.apps v2 SDK + Express webhook
@theokit/gateway-emailEmailIMAP IDLE inbound + SMTP outbound (nodemailer + imapflow + mailparser)
@theokit/gateway-smsSMSTwilio + Plivo + Vonage backends (HTTP webhook + REST outbound)
@theokit/gateway-mattermostMattermostWebSocket gateway (@mattermost/client v9+) + REST v4
@theokit/gateway-lineLINE Messaging APIWebhook-only (HMAC-SHA256 signed) + Reply/Push fallback
@theokit/gateway-matrixMatrix protocolSync long-poll via matrix-js-sdk v32+; federated by default

Each adapter is its own workspace package with peer-dep policy (ADR D171) — install only the ones you use.

The Matrix adapter (ADRs D413-D421) wraps matrix-js-sdk@^32.0.0 (~2MB lazy-loaded peer-dep). Federation is transparent (D420) — your bot at @bot:matrix.org can be added to rooms by users on any homeserver and the protocol routes events automatically. DM detection (D416) uses the canonical heuristic memberCount === 2 → dm; else group. Aliases (#general:matrix.org) are resolved to room ids on first send + cached (D419). EC-3 absorbed: matrix-js-sdk initial sync delivers ~10 historical events per joined room (a 50-room bot would fire 500 LLM calls on boot); the adapter filters event.getTs() < Date.now() - 60_000 so only live messages dispatch. E2EE rooms refused with a one-shot stderr warn (D418, deferred to v0.2). MSC4140 threads deferred to v0.2 (D417). See examples/matrix-bot/.

The LINE adapter (ADRs D405-D412) is webhook-only (D406 — LINE doesn't offer a WebSocket gateway). HMAC-SHA256 signature validation on every POST via crypto.timingSafeEqual (D408 — refuses unsigned mode at construction). sendMessage uses Reply token first (free, 60s TTL, one-shot) and falls back to Push API when token expired (D407) — ReplyTokenCache LRU(1000 entries) handles the lifecycle automatically. Source-type mapping (D410): userdm, group/roomgroup. Mention guard (D409) uses LINE's out-of-band event.message.mentionees: [{ userId }] array (no inline-text confusion). EC-4 absorbed: webhook delivers 9 event types (follow, unfollow, postback, beacon, image/sticker/audio/video messages, etc.) — adapter filters at the top so handlers never see TypeError on event.message.text for non-text events. 5000-char surrogate-safe multipart split (D411). See examples/line-bot/.

The Mattermost adapter (ADRs D397-D404) uses the modern @mattermost/client@^9.0.0 SDK with WebSocket gateway for real-time posted events and Client4 REST for outbound. Works with self-hosted Mattermost (any baseUrl) and Mattermost Cloud. PAT auth (D401); OAuth deferred to v0.2. Channel-type mapping: Ddm, G/O/Pgroup (raw type preserved in event.mattermost.channelType). Thread replies bidirectional via root_idtopicId (D399). requireMention: true default for non-DM channels (D403). EC-2 absorbed: mention check prioritizes metadata.mentions array (unambiguous user-id list); text fallback uses word-boundary regex \b@${botUsername}\b so @theory_dept does NOT match a bot called theo. See examples/mattermost-bot/.

The SMS adapter ships three backends (ADR D389): Twilio, Plivo, and Vonage. Pick via backend: "twilio" | "plivo" | "vonage". Each backend validates inbound HMAC signatures BEFORE handler dispatch (ADR D392); webhook public endpoint without signing secret is refused at construction time (EC-1 absorbed). Outbound text is segmented into (i/N) parts at 1600 chars per part, grapheme-cluster-safe via Intl.Segmenter (ADR D393, EC-7). All phone numbers are normalized to E.164 via libphonenumber-js (D391). v0.1 covers texto only — MMS/group SMS/budget-per-message are deferred to v0.2. See examples/sms-bot/.

The WhatsApp adapter ships two backends (ADR D303): the official Meta WhatsApp Business Cloud API (Bearer token + signed webhook) and the unofficial whatsapp-web.js subprocess bridge (personal accounts). Pick via backend: "cloud" | "web".

The Microsoft Teams adapter is built on the modern @microsoft/teams.apps v2 SDK (ADR D315). The SDK handles JWT validation, mention stripping, and proactive send routing — we expose BasePlatformAdapter over it. Setup requires Azure AD App Registration + Azure Bot Service. See examples/teams-bot/ for the 8-step walkthrough.

The Email adapter speaks the 2026 community-standard Node stack: nodemailer for SMTP outbound, imapflow for IMAP IDLE inbound (RFC 2177 push, ~28-min auto-refresh), and mailparser for RFC 5322 parsing. Threading is reconstructed automatically from Message-ID / In-Reply-To / References (ADR D337, RFC 5322 §3.6.4) so replies land in the same thread in the user's inbox. The adapter ships hardened filters for own-address loopback (EC-1 CRITICAL), automated senders (Auto-Submitted, Precedence: bulk|list, noreply@, postmaster@), and an optional allowedSenders allowlist with bracket-form normalization (EC-3). Works with Gmail App Passwords, Outlook/Office 365, Yahoo, and Fastmail. See examples/email-bot/.

Architecture

Platform (Telegram/Slack/...)


PlatformAdapter (per-platform package)
        │ MessageEvent (portable, discriminated by `platform`)

SessionRouter   ←→   Agent.resume / Agent.getOrCreate


DeliveryRouter  ←→   Cron (for scheduled delivery)

The BasePlatformAdapter (ADR D172) handles lifecycle, retry, and hook plumbing. New adapters subclass it.

Minimal Slack example

import { SlackAdapter } from "@theokit/gateway-slack";
import { Agent } from "@theokit/sdk";

const adapter = new SlackAdapter({
  appToken: process.env.SLACK_APP_TOKEN!,
  botToken: process.env.SLACK_BOT_TOKEN!,
});

adapter.onInbound(async (event) => {
  const agent = await Agent.getOrCreate({
    agentId: `slack-${event.slack!.channel.id}`,
    apiKey: process.env.OPENROUTER_API_KEY,
    model: { id: "openai/gpt-4o-mini" },
  });
  const reply = await (await agent.send(event.text)).wait();
  await adapter.send({ chatId: event.slack!.channel.id, text: reply.result! });
  await agent.dispose();
});

await adapter.connect();

Portable vs platform-specific (ADR D180)

MessageEvent always has portable fields (text, userId, topicId). Platform-specific structures sit behind event.{telegram,discord,slack}?.raw — escape hatch for richer rendering (Adaptive Cards, Block Kit, etc.).

Hooks (gateway-level)

Gateways expose their own hook contract (ADR D176) — separate from agent plugin hooks. Hook signature mirrors pre_tool_call veto pattern (ADR D177).

Roadmap

v1.4 ships: ✅ @theokit/gateway-whatsapp, ✅ @theokit/gateway-teams, ✅ @theokit/gateway-email (this release). Next: @theokit/skills-google-workspace. See CLAUDE.md Adoption Roadmap.

API reference

The gateway packages are separate from @theokit/sdk — see each package's README:

On this page