Channel Plugins
Channel plugins add communication methods to TeamWeb AI. They are configured per assistant and handle both sending messages out and receiving inbound webhooks.
Directory Layout
my_channel/
manifest.py
channel.pyExample: A Simple SMS Channel
manifest.py:
from typing import Any
from app.plugins.base import PluginManifest, PluginType
manifest = PluginManifest(
name="sms_provider",
plugin_type=PluginType.CHANNEL,
channel_type="messaging",
version="1.0.0",
description="Send and receive SMS messages",
author="Your Name",
config_schema={
"api_key": {
"type": "string",
"required": True,
"secret": True,
"label": "API Key",
},
"phone_number": {
"type": "string",
"required": True,
"label": "Phone Number",
"placeholder": "+1234567890",
},
},
)
def get_setup_docs(app_config: dict[str, Any], **kwargs: Any) -> str:
public_url = app_config.get("PUBLIC_URL", "https://yourdomain.com")
assistant_id = kwargs.get("assistant_id", "<assistant_id>")
webhook_url = (
f"{public_url}/webhooks/channels/sms_provider/{assistant_id}/inbound"
)
return (
"<h6>SMS Setup</h6>"
"<ol>"
"<li>Configure your SMS provider to send webhooks to:"
f"<code>{webhook_url}</code></li>"
"</ol>"
)channel.py:
import logging
from typing import Any
from app.plugins.base import Attachment, BaseChannel, InboundMessage
logger = logging.getLogger(__name__)
class SmsChannel(BaseChannel):
"""Send and receive SMS messages.
Instantiated with config from AssistantPlugin.config_json.
"""
def __init__(self, api_key: str, phone_number: str, **kwargs: Any) -> None:
self.api_key = api_key
self.phone_number = phone_number
def send_message(
self,
to: str,
subject: str,
body_html: str,
body_text: str,
reply_to: str | None = None,
headers: dict[str, str] | None = None,
attachments: list[Attachment] | None = None,
) -> dict[str, Any]:
"""Send an SMS message.
Args:
to: Recipient phone number.
subject: Ignored for SMS.
body_html: Ignored for SMS.
body_text: Message text content.
reply_to: Ignored for SMS.
headers: Optional channel-specific headers.
attachments: Ignored for SMS.
Returns:
Dict with message_id from the SMS provider.
"""
# Call your SMS provider API here
# ...
return {"message_id": "msg_123"}
def parse_inbound(self, payload: dict[str, Any]) -> InboundMessage:
"""Parse an inbound SMS webhook into a normalised message.
Args:
payload: Raw webhook payload from the SMS provider.
Returns:
Normalised InboundMessage.
"""
return InboundMessage(
sender=payload.get("from", ""),
recipient=payload.get("to", ""),
body_text=payload.get("text", ""),
message_id=payload.get("id"),
)
def build_reply_to(self, conversation_reply_token: str) -> str:
"""Build a reply-to identifier with the conversation token.
For channels that support reply threading, embed the token so
inbound messages can be routed to the correct conversation.
Args:
conversation_reply_token: UUID token for the conversation.
Returns:
Reply-to string (e.g. phone number with token suffix).
"""
return ""Channel Type Constraint
The channel_type in the manifest must be one of "email", "messaging", "voice", or "browser". Only one channel of each type may be active per assistant. Enabling a second messaging channel automatically disables the first.
Feedback Support
Channel plugins can declare support for TeamWeb AI’s feedback system by setting flags on the manifest:
manifest = PluginManifest(
name="my_channel",
plugin_type=PluginType.CHANNEL,
channel_type="browser",
# ...
supports_message_feedback=True, # Per-message thumbs up/down
supports_session_feedback=True, # Post-session rating form
)| Flag | Default | Description |
|---|---|---|
supports_message_feedback | False | The channel UI can show thumbs up/down on assistant messages |
supports_session_feedback | False | The channel UI can show a feedback form when a session ends |
When these flags are True, the channel configuration page in the admin UI shows additional Feedback Settings where admins can choose which user types see feedback controls.
Only channels with a UI controlled by TeamWeb AI should set these flags (e.g., browser_internal and browser_external). External platforms like Slack, Telegram, and email don’t have a natural place for feedback controls.
Constructor
The channel class is instantiated with the plugin configuration unpacked as keyword arguments from AssistantPlugin.config_json. Define your __init__ parameters to match the keys in your config_schema. Always include **kwargs to handle any extra fields gracefully.
Webhook URL
Inbound messages are received at:
/webhooks/channels/<plugin_name>/<assistant_id>/inboundTeamWeb AI automatically routes the payload to your parse_inbound() method. Use get_setup_docs() in your manifest to display the correct webhook URL in the configuration UI.
InboundMessage Fields
| Field | Description |
|---|---|
sender | Sender identifier (email, phone, user ID) |
recipient | Recipient identifier |
subject | Message subject (if applicable) |
body_text | Plain text message body |
body_html | HTML body (if available) |
message_id | Channel-specific message ID |
reply_token | Token for routing to an existing conversation |
attachments | List of attachment dicts with filename, content_type, data |
send_message Parameters
| Parameter | Description |
|---|---|
to | Recipient address (email, channel ID, phone number) |
subject | Message subject (ignore if not applicable to your channel) |
body_html | HTML body content |
body_text | Plain text body content |
reply_to | Reply-to address for threading |
headers | Channel-specific headers dict (e.g. slack_thread_ts) |
attachments | List of Attachment objects with filename, data (bytes), and content_type |
Return a dict with at least a message_id key.
Reply Threading
For channels that support reply threading, implement build_reply_to() to embed a conversation token into a reply address. TeamWeb AI calls this when sending the first message in a conversation so that subsequent inbound messages can be routed back to the same conversation.
For example, the Postmark email channel embeds the token in the local part of a reply-to email address: reply+{token}@yourdomain.com.
Request Verification
For channels that support request signing (e.g. Slack’s HMAC signatures), implement a verify_request class method that validates the webhook signature. TeamWeb AI calls this before processing the payload:
@classmethod
def verify_request(
cls,
signing_secret: str,
timestamp: str,
body: bytes,
signature: str,
) -> bool:
"""Verify the webhook request signature.
Args:
signing_secret: The app's signing secret.
timestamp: Request timestamp header value.
body: Raw request body bytes.
signature: Signature header value.
Returns:
True if the signature is valid.
"""
# Implement your verification logic here
return True