The Transport interface
Every transport implements one small interface (internal/transport/transport.go):
type Transport interface {
Name() string
Start(ctx context.Context, handler Handler) error
Stop() error
}
Above the transport sits the Router, which handles per-sender session lookup, allowlist enforcement, and dispatch to the Agent. Below sits the transport-specific wire code.
None of the shipped transports expose a public HTTP surface by default. This is a deliberate posture choice — rousseau daemons should be safe to run behind NAT with no port-forwarding rules.
Supported transports
| Transport | Inbound | Outbound | Backing library / protocol | Auth | One-line setup |
|---|---|---|---|---|---|
| yes | yes | go.mau.fi/whatsmeow |
Device pair (QR) | rousseau whatsapp --allow <jid> |
|
| Signal | yes | yes | signal-cli JSON-RPC |
Pre-registered account | rousseau signal --account +447900123456 |
| Telegram | yes | yes | Bot API long-polling | BotFather token | rousseau telegram --token <token> |
| Matrix | yes | yes | Client-server API /sync |
Access token | rousseau matrix --homeserver-url … --access-token … |
| Slack | yes | yes | Socket Mode + Web API | xapp-* + xoxb-* |
rousseau slack --app-token … --bot-token … |
| Discord | yes | yes | Gateway v10 + REST | Bot token | rousseau discord --token <token> |
| iMessage | yes | yes | BlueBubbles HTTP polling | Server password | rousseau imessage --base-url … --password … |
| yes | yes | IMAP + SMTP | Username + password | rousseau email --imap-addr … --smtp-addr … |
|
| SMS | no | yes | Twilio or Vonage REST | Account SID / API key | rousseau sms --provider twilio --account-sid … --auth-token … |
Why no public HTTP surface
Two design choices keep every listed transport away from a public webhook:
- WebSocket-based inbound. Slack Socket Mode and Discord Gateway are outbound-only from the daemon's perspective — the daemon dials the vendor over TLS and messages arrive on the same connection.
- Polling. WhatsApp, Telegram, Matrix, iMessage, and email pull for updates on their own cadence. There is no webhook the vendor calls into.
SMS is the exception, and rousseau resolves it by making SMS send-only. Inbound SMS would require a Twilio / Vonage webhook, which is exactly the surface this project refuses to introduce.
Router behaviour
The router (internal/transport/router.go) sits between every transport and the Agent:
- Session isolation. Every distinct
Fromvalue gets its ownSession, so parallel conversations do not cross-contaminate. WhatsApp LID identities are normalised to phone JIDs first (seeinternal/transport/whatsapp/resolve.go). - Allowlist. Every transport that supports inbound has an
Allowlist []stringin its config. Empty means "accept every sender" — for daemons you always want at least one entry. - Dispatch. The router serialises turns per session so a user cannot stack two concurrent inbound messages.
Adding a tenth transport
Implement transport.Transport (three methods). Add a Config type mirroring the block layout under internal/config/. Wire a CLI command in internal/cli/. That is the surface — the agent core stays untouched.