Channels are separate Cloudflare Workers that handle platform-specific messaging. Each channel connects to the Gateway via Service Bindings (RPC).
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β GSV Gateway Worker β
β ββββββββββββββββββββ β
β β Gateway DO βββββ Service Bindings ββββ β
β β Session DOs β β β
β ββββββββββββββββββββ β β
β β β β
β env.DISCORD ββββββββββββββββββ β β
β env.WHATSAPP βββββββββββββββββΌβββββββββββββββ€ β
β env.EMAIL ββββββββββββββββββββ β β
βββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββββββββββββ βββββββββββββββββββββ βββββββββββββββββββββ
β Discord Worker β β WhatsApp Worker β β Email Worker β
β β β β β β
β DiscordGateway β β WhatsAppAccount β β EmailAccount β
β (Durable Object) β β (Durable Object) β β (Durable Object) β
β β β β β β
β - WebSocket to β β - Baileys WS β β - IMAP polling β
β Discord API β β - QR auth β β - SMTP send β
β - Heartbeats β β - Media upload β β - Webhook recv β
βββββββββββββββββββββ βββββββββββββββββββββ βββββββββββββββββββββ
All channel workers implement ChannelWorkerInterface (see gateway/src/channel-interface.ts):
interface ChannelWorkerInterface {
// Identity
readonly channelId: string;
readonly capabilities: ChannelCapabilities;
// Lifecycle
start(accountId: string, config: Record<string, unknown>): Promise<StartResult>;
stop(accountId: string): Promise<StopResult>;
status(accountId?: string): Promise<ChannelAccountStatus[]>;
// Messaging
send(accountId: string, message: ChannelOutboundMessage): Promise<SendResult>;
setTyping?(accountId: string, peer: ChannelPeer, typing: boolean): Promise<void>;
// Auth (optional)
login?(accountId: string, options?: { force?: boolean }): Promise<LoginResult>;
logout?(accountId: string): Promise<LogoutResult>;
}Connection: Discord Gateway WebSocket (persistent via Durable Object)
Architecture:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Discord Channel Worker β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β WorkerEntrypoint (implements ChannelWorkerInterface) β β
β β β β
β β start() β gets/creates DiscordGateway DO β β
β β send() β calls Discord REST API β β
β β status()β queries DO state β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β DiscordGateway DO (one per bot token/account) β β
β β β β
β β - Maintains WebSocket to Discord Gateway β β
β β - Handles IDENTIFY, HEARTBEAT, RESUME β β
β β - Dispatches MESSAGE_CREATE β Gateway.channelInboundβ β
β β - Uses alarm() for heartbeat timing β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β Service Binding: GATEWAY β gsv-gateway β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Config:
channels:
discord:
enabled: true
accounts:
default:
botToken: "..."
# OR use secret
botTokenSecret: "DISCORD_BOT_TOKEN"Capabilities:
- Chat types: dm, group (guild channels), thread
- Media: yes (embeds, attachments)
- Reactions: yes
- Typing: yes
- Threads: yes
- Editing: yes
- QR login: no (token-based)
Connection: Baileys WebSocket (via Durable Object)
Architecture: (Existing, needs update to new interface)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β WhatsApp Channel Worker β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β WorkerEntrypoint (implements ChannelWorkerInterface) β β
β β β β
β β start() β gets/creates WhatsAppAccount DO β β
β β send() β calls DO.sendMessage() β β
β β login() β initiates QR flow β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β WhatsAppAccount DO (one per phone number) β β
β β β β
β β - Baileys socket connection β β
β β - Auth state in DO storage β β
β β - QR code generation for login β β
β β - Message handling β Gateway.channelInbound β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β Service Binding: GATEWAY β gsv-gateway β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Config:
channels:
whatsapp:
enabled: true
accounts:
default:
# No token needed - uses QR loginCapabilities:
- Chat types: dm, group
- Media: yes (images, audio, video, documents)
- Reactions: yes
- Typing: yes
- Threads: no (WhatsApp doesn't have threads)
- QR login: yes
Connection: IMAP polling + SMTP sending (or API like SendGrid/Mailgun)
Architecture:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Email Channel Worker β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β WorkerEntrypoint (implements ChannelWorkerInterface) β β
β β β β
β β start() β creates EmailAccount DO, starts polling β β
β β send() β SMTP or API call β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β EmailAccount DO β β
β β β β
β β Option A: IMAP polling β β
β β - alarm() triggers IMAP check every N minutes β β
β β - Tracks last seen message ID β β
β β β β
β β Option B: Webhook receiver β β
β β - SendGrid/Mailgun inbound parse webhook β β
β β - Worker fetch() handles POST from email service β β
β β β β
β β Either way β Gateway.channelInbound β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β Service Binding: GATEWAY β gsv-gateway β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Config:
channels:
email:
enabled: true
accounts:
default:
# Option A: Direct IMAP/SMTP
imap:
host: "imap.gmail.com"
user: "bot@example.com"
password: "..." # or passwordSecret
smtp:
host: "smtp.gmail.com"
user: "bot@example.com"
password: "..."
# Option B: Email service API
provider: "sendgrid" # or "mailgun", "postmark"
apiKey: "..."
inboundWebhookSecret: "..." # verify webhook signaturesCapabilities:
- Chat types: dm (email is 1:1 or mailing list)
- Media: yes (attachments)
- Reactions: no
- Typing: no
- Threads: yes (email threading via In-Reply-To)
// gateway/src/channel-registry.ts
export class ChannelRegistry {
private channels: Map<string, ChannelWorkerInterface>;
constructor(env: Env) {
this.channels = new Map();
// Register channels from service bindings
if (env.DISCORD) this.channels.set("discord", env.DISCORD);
if (env.WHATSAPP) this.channels.set("whatsapp", env.WHATSAPP);
if (env.EMAIL) this.channels.set("email", env.EMAIL);
}
get(channelId: string): ChannelWorkerInterface | undefined {
return this.channels.get(channelId);
}
list(): string[] {
return Array.from(this.channels.keys());
}
}- Channel DO receives platform message (Discord MESSAGE_CREATE, WhatsApp msg, email)
- Channel DO calls
env.GATEWAY.channelInbound(channelId, accountId, message) - Gateway routes to appropriate Session DO
- Session processes with LLM
- Session broadcasts response
- Gateway calls
channel.send(accountId, outboundMessage)
Alchemy conditionally deploys channels based on config:
// gateway/alchemy/index.ts
import { Worker } from "alchemy";
// Always deploy gateway
const gateway = new Worker("gsv-gateway", {
name: "gsv-gateway",
entrypoint: "./src/index.ts",
// ...
});
// Conditionally deploy channels
if (config.channels?.discord?.enabled) {
const discord = new Worker("gsv-channel-discord", {
name: "gsv-channel-discord",
entrypoint: "../adapters/discord/src/index.ts",
durableObjects: [{ name: "DISCORD_GATEWAY", className: "DiscordGateway" }],
services: [{ binding: "GATEWAY", service: "gsv-gateway" }],
});
}
if (config.channels?.whatsapp?.enabled) {
const whatsapp = new Worker("gsv-channel-whatsapp", {
name: "gsv-channel-whatsapp",
entrypoint: "../adapters/whatsapp/src/index.ts",
durableObjects: [{ name: "WHATSAPP_ACCOUNT", className: "WhatsAppAccount" }],
services: [{ binding: "GATEWAY", service: "gsv-gateway" }],
});
}
if (config.channels?.email?.enabled) {
const email = new Worker("gsv-channel-email", {
name: "gsv-channel-email",
entrypoint: "../adapters/email/src/index.ts",
durableObjects: [{ name: "EMAIL_ACCOUNT", className: "EmailAccount" }],
services: [{ binding: "GATEWAY", service: "gsv-gateway" }],
});
}- Create shared types package (
@gsv/channel-interface) - Update WhatsApp channel to implement new interface
- Build Discord channel from scratch
- Build Email channel
- Update Gateway to use ChannelRegistry
- Update Alchemy for conditional deployment
Easy to add following this pattern:
- Telegram - Bot API, simple HTTP polling or webhook
- Slack - Socket Mode or Events API
- Matrix - Client-Server API
- SMS - Twilio/Vonage API