Messaging Channels
Wayland Core ships 10 messaging channel adapters. Each implements a uniform Channel trait and runs on its own tokio task. All channels fan inbound events into a single broadcast stream that the agent loop subscribes to, and the agent can send to any registered channel through the send_message tool.
The adapters are organized across three crate layers:
wcore-channels: theChanneltrait,ChannelEventenum,ChannelConfigschema, andChannelManagerwcore-channel-<platform>(10 crates): one per platformwcore-channels-registry: factory dispatch table andauto_register_from_user_config
The 10 adapters
Section titled “The 10 adapters”| Platform | Inbound | Outbound | Notes |
|---|---|---|---|
| Slack | Events API webhook (HMAC-SHA256 verified) | chat.postMessage | |
| Discord | Gateway v10 WebSocket (MESSAGE_CREATE) | REST POST /api/v10/channels/{id}/messages | Privileged MESSAGE_CONTENT intent required |
| Telegram | getUpdates long-poll background task | sendMessage | |
| Signal | JSON-RPC receive from signal-cli subprocess | JSON-RPC send over stdin | Requires signal-cli on PATH |
Meta webhook (X-Hub-Signature-256 verified) | Meta Cloud API POST /{phone_number_id}/messages | ||
| SMS | Twilio webhook (HMAC-SHA1 verified) | Twilio REST POST Accounts/{sid}/Messages.json | |
IMAP polling with UID cursor (spawn_blocking) | SMTP via lettre (rustls-tls) | IMAP section is optional; omit for outbound-only | |
| Matrix | REST poll | Matrix REST | |
| MS Teams | Send-only at this version | Bot Framework Connector REST, OAuth2 client-credentials | See caveat below |
| iMessage | Polls ~/Library/Messages/chat.db (read-only SQLite) | osascript AppleScript subprocess | macOS only; see caveat below |
:::caution MS Teams is send-only
wcore-channel-msteams does not yet receive inbound messages. Inbound webhook receipt is deferred to v0.8.3. Do not rely on inbound Teams events at this version.
:::
:::caution iMessage is macOS-only
The "imessage" factory is compiled only when target_os = "macos". On Linux and Windows, the registry returns None for any config with platform = "imessage" and logs a skip at boot. iMessage also requires Full Disk Access (to read chat.db) and macOS Automation TCC consent (for osascript).
:::
Config files
Section titled “Config files”Each channel is configured with one TOML file at ~/.wayland/channels/<name>.toml. The file stem must match the name field inside the file. Credentials are never stored in config files; credential_handle fields are keys passed to the OS keychain (or whichever CredentialsStore backend is active).
At minimum, every file needs name, platform, and an [options] table.
name = "my-channel"platform = "<platform-string>"enabled = true # default true; set false to keep the file without registering
[options]# platform-specific keys herename = "acme-slack"platform = "slack"
[options]workspace_name = "acme"credential_handle_bot_token = "slack.acme.bot_token"credential_handle_signing_secret = "slack.acme.signing_secret"# optional:# default_channel_id = "C0123456789"# max_retry_attempts = 5Required keys: workspace_name, credential_handle_bot_token, credential_handle_signing_secret.
Discord
Section titled “Discord”name = "acme-discord"platform = "discord"
[options]credential_handle = "discord.acme.bot_token"# optional:# allowed_channel_ids = ["111222333444555666"]# intents = 33792 # default: GUILD_MESSAGES | MESSAGE_CONTENTThe MESSAGE_CONTENT intent (bitmask 33792) must be enabled in the Discord developer portal for the bot to receive message text.
Telegram
Section titled “Telegram”name = "acme-tg"platform = "telegram"
[options]credential_handle = "telegram.acme.bot_token"# optional:# allowed_chat_ids = ["-100123456789"]# long_poll_timeout_secs = 30 # default 30, max 120# parse_mode = "MarkdownV2" # MarkdownV2 | HTML | MarkdownSignal
Section titled “Signal”name = "acme-signal"platform = "signal"
[options]account = "+15551234567"# optional:# signal_cli_path = "/usr/local/bin/signal-cli" # default: finds signal-cli on PATH# send_timeout_secs = 10Signal credentials live in signal-cli’s own state directory, not in the engine credential store. The _credentials argument is intentionally ignored by this adapter.
name = "acme-whatsapp"platform = "whatsapp"
[options]workspace_name = "acme"phone_number_id = "1234567890"credential_handle_access_token = "whatsapp.acme.access_token"credential_handle_app_secret = "whatsapp.acme.app_secret"# optional:# default_recipient = "+15551234567"# graph_version = "v18.0"# max_retry_attempts = 5The credential_handle_app_secret is used for X-Hub-Signature-256 webhook verification.
SMS (Twilio)
Section titled “SMS (Twilio)”name = "acme-sms"platform = "sms"
[options]from_number = "+15550001111"credential_handle_account_sid = "sms.acme.account_sid"credential_handle_auth_token = "sms.acme.auth_token"# optional:# max_retry_attempts = 5Inbound messages arrive via Twilio webhook; the adapter verifies the HMAC-SHA1 signature.
name = "acme-email"platform = "email"
[options]from_address = "bot@acme.com"
[options.smtp]host = "smtp.acme.com"# port = 587 # defaultuser_credential_handle = "email.acme.smtp_user"password_credential_handle = "email.acme.smtp_pass"
# omit [options.imap] entirely for outbound-only[options.imap]host = "imap.acme.com"# port = 993 # defaultuser_credential_handle = "email.acme.imap_user"password_credential_handle = "email.acme.imap_pass"# mailbox = "INBOX" # default# poll_interval_secs = 60 # default# allowed_senders = ["trusted@example.com"]The allowed_senders filter compares against the bare address in the From: header. The From: header is not an authenticated principal; SMTP does not bind the envelope sender to the connecting party, and no SPF/DKIM/DMARC verification is performed here. Treat allowed_senders as a delivery-side filter, not as authentication.
Matrix
Section titled “Matrix”name = "acme-matrix"platform = "matrix"
[options]homeserver_url = "https://matrix.org"user_id = "@wayland-bot:matrix.org"credential_handle_access_token = "matrix.acme.token"MS Teams
Section titled “MS Teams”name = "acme-teams"platform = "msteams"
[options]credential_handle_app_id = "msteams.acme.app_id"credential_handle_app_password = "msteams.acme.app_password"# optional:# service_url = "https://smba.trafficmanager.net/amer/" # default: Americas endpointFor other regions, set service_url to the appropriate Bot Framework Connector endpoint (e.g. https://smba.trafficmanager.net/emea/).
iMessage
Section titled “iMessage”name = "acme-imessage"platform = "imessage"
[options]# all fields optional# poll_interval_ms = 2000 # default 2000, clamped to [500, 60000]# allowed_handles = ["+15551234567", "user@example.com"]No credential_handle fields are needed. Access is controlled by macOS TCC permissions: Full Disk Access for reading chat.db, and Automation consent for sending via osascript.
Auto-registration at boot
Section titled “Auto-registration at boot”wcore-channels-registry::auto_register_from_user_config scans ~/.wayland/channels/*.toml at engine boot (bootstrap.rs:1880-1900). Files are processed in sorted filename order so registration order is deterministic. For each file:
- The file stem must equal the
namefield inside the file; mismatches are logged and skipped. - Files with
enabled = falseare skipped without error. - Unknown
platformvalues are logged atwarnand skipped. - Parse failures and construction errors are logged at
warnand skipped.
A single bad config file cannot prevent the other channels from registering; the function always returns Ok(count) where count is the number successfully registered.
$WAYLAND_HOME overrides the base directory; the channels directory is always <WAYLAND_HOME>/channels/.
The send_message tool
Section titled “The send_message tool”The agent can send a message to any registered channel using the send_message tool (wcore-tools/src/send_message.rs, tool name "send_message"). The tool takes a platform name, a target identifier (channel name, phone number, chat ID, etc.), and a text payload.
The tool is wired to a MessageTransport at construction time, which in turn calls ChannelManager::send_to. If no transport is wired (for example in a one-shot CLI invocation without a channel session), the tool returns a structured error rather than silently no-oping.
Example: ask the agent to send a Slack notification:
Send "Build passed" to the acme-slack channel.The agent will invoke send_message with platform = "slack" and the channel name from your registered config.
Webhook signature verification
Section titled “Webhook signature verification”Three adapters perform HMAC verification on inbound webhooks before accepting any event:
| Adapter | Algorithm | Header |
|---|---|---|
| Slack | HMAC-SHA256 | X-Slack-Signature + X-Slack-Request-Timestamp (5-minute replay window) |
| HMAC-SHA256 | X-Hub-Signature-256 | |
| SMS (Twilio) | HMAC-SHA1 | Twilio X-Twilio-Signature |
Requests that fail signature verification are rejected before any event is enqueued. The signing secrets are resolved from the credential store at start() time, not stored in the TOML config.