Skip to main content

Adding a Channel

ClawDesk's channel system uses a layered trait architecture. To add a new channel (e.g., Microsoft Teams, Matrix), you implement the core Channel trait and optionally adopt Layer 1 capability traits.

Architecture Overview

Layer 0 (Required):   Channel           — Send/receive messages
Layer 1 (Optional): Threaded — Thread support
Streaming — Real-time streaming
Reactions — Emoji reactions
GroupManagement — Group/room management
Directory — User directory lookup
Pairing — DM pairing

Step 1: Create the Channel Module

Add a new module in crates/clawdesk-channels/src/:

crates/clawdesk-channels/src/
├── lib.rs
├── telegram.rs
├── discord.rs
├── slack.rs
└── teams.rs ← New file

Step 2: Implement the Channel Trait

The core Channel trait is defined in clawdesk-channel:

// crates/clawdesk-channels/src/teams.rs

use async_trait::async_trait;
use clawdesk_channel::{Channel, ChannelError};
use clawdesk_types::{ChannelId, Message, Session};

pub struct TeamsChannel {
client: TeamsClient,
config: TeamsConfig,
}

#[async_trait]
impl Channel for TeamsChannel {
fn id(&self) -> &ChannelId {
&self.config.channel_id
}

fn name(&self) -> &str {
"microsoft-teams"
}

async fn send(&self, session: &Session, message: &Message) -> Result<(), ChannelError> {
self.client
.post_message(&session.external_id, &message.content)
.await
.map_err(|e| ChannelError::ConnectionFailed(e.to_string()))
}

async fn receive(&self) -> Result<Option<Message>, ChannelError> {
// Poll or webhook-based receive logic
self.client
.poll_messages()
.await
.map_err(|e| ChannelError::ConnectionFailed(e.to_string()))
}
}

Step 3: Add Optional Layer 1 Traits

Implement additional capabilities as needed:

use clawdesk_channel::{Threaded, Streaming};

#[async_trait]
impl Threaded for TeamsChannel {
async fn reply_in_thread(
&self,
thread_id: &str,
message: &Message,
) -> Result<(), ChannelError> {
self.client
.reply_to_thread(thread_id, &message.content)
.await
.map_err(|e| ChannelError::ConnectionFailed(e.to_string()))
}

async fn create_thread(
&self,
session: &Session,
title: &str,
) -> Result<String, ChannelError> {
self.client
.create_thread(&session.external_id, title)
.await
.map_err(|e| ChannelError::ConnectionFailed(e.to_string()))
}
}

Step 4: Add Configuration

Define the channel's configuration struct:

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TeamsConfig {
pub channel_id: ChannelId,
pub tenant_id: String,
pub client_id: String,
pub client_secret: String,
pub webhook_url: Option<String>,
}

Add the configuration to the global config TOML:

[[channels]]
type = "microsoft-teams"
tenant_id = "your-tenant-id"
client_id = "your-client-id"
client_secret = "${TEAMS_CLIENT_SECRET}"

Step 5: Register the Channel

Add the channel to the registry in crates/clawdesk-channels/src/lib.rs:

mod teams;
pub use teams::{TeamsChannel, TeamsConfig};

pub fn register_channels(registry: &mut ChannelRegistry, config: &AppConfig) {
// ... existing channels ...

if let Some(teams_cfg) = &config.channels.teams {
let channel = TeamsChannel::new(teams_cfg.clone());
registry.register(Box::new(channel));
}
}

Step 6: Write Tests

Unit Tests

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn teams_channel_has_correct_name() {
let config = TeamsConfig::test_default();
let channel = TeamsChannel::new(config);
assert_eq!(channel.name(), "microsoft-teams");
}
}

Integration Tests

#[tokio::test]
async fn teams_send_and_receive_roundtrip() {
let channel = TeamsChannel::new(TeamsConfig::test_default());
let session = Session::new("teams-test");
let message = Message::user("Hello Teams!");

let result = channel.send(&session, &message).await;
assert!(result.is_ok());
}

Step 7: Update Documentation

  1. Add the channel to docs/usage/channels.md
  2. Add configuration reference to docs/getting-started/configuration.md
  3. Update the channel trait matrix in architecture docs

Checklist

  • Implement Channel trait (Layer 0)
  • Implement relevant Layer 1 traits
  • Add {Channel}Config with serde support
  • Register in ChannelRegistry
  • Add unit tests
  • Add integration tests
  • Update documentation
  • Add environment variable entries
  • Handle rate limiting via clawdesk-channel::RateLimiter
  • Implement health checks via clawdesk-channel::HealthCheck
info

See the existing Telegram and Discord implementations for complete reference examples.