Skip to content

Messaging Bridge (Experimental)

msgbridge is a standalone binary that connects to the Aether gateway as a Bridge-type principal (br::{impl}::{spec}). It acts as a bidirectional relay: outbound messages from Aether agents are delivered to Discord channels, Microsoft Teams conversations, or email addresses; inbound messages from those platforms are wrapped in a typed JSON envelope and forwarded into Aether as agent or user messages.

The binary maintains a small database (PostgreSQL or SQLite) of channel mappings — named rules that bind a platform channel to an Aether target — and user mappings that correlate platform user IDs to Aether user IDs. All message routing decisions go through these mappings; there is no open-relay behaviour.

  • Notifications — an agent publishes a status update and msgbridge delivers it to a Slack-like Teams channel or Discord server.
  • Human-in-the-loop — a Discord or Teams user replies to an agent prompt; msgbridge routes the reply back to the originating agent.
  • Lightweight chat front-ends — surface a single Aether agent or workflow behind a familiar chat interface without building a custom bot.
  • Email integration — send structured alerts or reports via SMTP from within an agent workflow.
External platform msgbridge Aether gateway
───────────────── ───────────────────── ──────────────────
Discord / Teams ──► inbound adapter BridgeClient
Email (IMAP TBD) ──► Router.HandleInbound ──► br::impl::spec
│
Agents / Users
│
Discord / Teams ◄── Router.HandleOutbound ◄── BridgePayload JSON
Email (SMTP) ◄── outbound adapter

msgbridge identifies itself to the gateway with a Bridge principal identity: br::{implementation}::{specifier}. The implementation field is a stable logical name (e.g. aether-msgbridge); the specifier distinguishes individual instances. Together they form the topic the gateway routes bridge traffic to.

Agents send outbound bridge requests by publishing a BridgePayload JSON object (see Outbound payload). Inbound messages from platform users arrive as an InboundPayload envelope on the agent’s subscribed topic. Channel mappings control which Aether topics receive inbound traffic and which direction (inbound, outbound, or bidirectional) a mapping permits.

Terminal window
# Build
cd server && go build -o msgbridge ./cmd/msgbridge
# Run with a config file
./msgbridge --config configs/msgbridge.yaml
# AetherLite mode (SQLite, no PostgreSQL required)
./msgbridge --config configs/msgbridge.yaml --lite --sqlite-path ./msgbridge.db
# Dev mode (no config file required, uses built-in defaults)
./msgbridge --dev
mode: postgres # or "sqlite"
aether:
address: "localhost:50051"
implementation: "aether-msgbridge"
specifier: "instance-1"
credentials:
api_key: "" # set via AETHER_API_KEY env var
tls:
cert_file: ""
key_file: ""
ca_file: ""
postgres:
host: "localhost"
port: 5432
database: "aether"
user: "aether"
password: "" # set via POSTGRES_PASSWORD env var
ssl_mode: "disable"
max_connections: 10
max_idle_connections: 5
sqlite:
path: "msgbridge.db" # used when mode: sqlite
platforms:
discord:
enabled: false
bot_token: "" # set via DISCORD_BOT_TOKEN env var
application_id: ""
teams:
enabled: false
app_id: ""
app_password: "" # set via TEAMS_APP_PASSWORD env var
tenant_id: ""
webhook_port: 8081
email:
enabled: false
smtp:
host: ""
port: 587
username: ""
password: "" # set via SMTP_PASSWORD env var
from_address: ""
tls: true
admin:
enabled: true
port: 31882
api_key: "" # set via MSGBRIDGE_ADMIN_API_KEY env var
logging:
level: "info" # debug | info | warn | error
format: "" # "console" for human output, "" for JSON
KeyTypeDefaultDescription
modestringpostgresStorage backend: postgres or sqlite.
aether.addresshost:port—Gateway gRPC address. Required.
aether.implementationstring—Bridge implementation name. Required.
aether.specifierstring—Unique instance specifier. Required.
aether.credentials.api_keystring(none)API key for gateway authentication.
aether.tls.cert_filepath(none)Client TLS certificate.
aether.tls.key_filepath(none)Client TLS private key.
aether.tls.ca_filepath(none)CA certificate for gateway verification.
postgres.hoststring—PostgreSQL host. Required unless mode: sqlite.
postgres.portint—PostgreSQL port.
postgres.databasestring—Database name.
postgres.userstring—Database user.
postgres.passwordstring—Database password.
postgres.ssl_modestringdisablePostgreSQL SSL mode.
postgres.max_connectionsint(driver default)Max open DB connections.
postgres.max_idle_connectionsint(driver default)Max idle DB connections.
sqlite.pathpathmsgbridge.dbSQLite file path. Required when mode: sqlite.
platforms.discord.enabledboolfalseEnable Discord adapter.
platforms.discord.bot_tokenstring—Discord bot token.
platforms.discord.application_idstring—Discord application ID.
platforms.teams.enabledboolfalseEnable Microsoft Teams adapter.
platforms.teams.app_idstring—Teams bot app ID.
platforms.teams.app_passwordstring—Teams bot app password.
platforms.teams.tenant_idstring—Azure tenant ID.
platforms.teams.webhook_portint8081Local HTTP port for Teams Activity webhook.
platforms.email.enabledboolfalseEnable email adapter.
platforms.email.smtp.hoststring—SMTP server host.
platforms.email.smtp.portint—SMTP server port.
platforms.email.smtp.usernamestring—SMTP username.
platforms.email.smtp.passwordstring—SMTP password.
platforms.email.smtp.from_addressstring—Envelope From address.
platforms.email.smtp.tlsboolfalseUse TLS for SMTP.
admin.enabledbooltrueEnable admin REST API.
admin.portint31882Admin API listen port.
admin.api_keystring(none)API key for admin endpoints (open if unset).
logging.levelstringinfoLog level: debug, info, warn, error.
logging.formatstring(auto)console for human output; empty for JSON.

All secrets should be supplied via environment variables rather than written into the config file.

VariableOverridesDescription
AETHER_ADDRESSaether.addressGateway address.
AETHER_IMPLEMENTATIONaether.implementationBridge implementation name.
AETHER_SPECIFIERaether.specifierBridge specifier.
AETHER_API_KEYaether.credentials.api_keyGateway API key.
POSTGRES_HOSTpostgres.hostPostgreSQL host.
POSTGRES_PORTpostgres.portPostgreSQL port.
POSTGRES_USERpostgres.userPostgreSQL user.
POSTGRES_PASSWORDpostgres.passwordPostgreSQL password.
POSTGRES_DATABASEpostgres.databasePostgreSQL database name.
DISCORD_BOT_TOKENplatforms.discord.bot_tokenDiscord bot token.
DISCORD_APPLICATION_IDplatforms.discord.application_idDiscord application ID.
TEAMS_APP_IDplatforms.teams.app_idTeams bot app ID.
TEAMS_APP_PASSWORDplatforms.teams.app_passwordTeams bot app password.
TEAMS_TENANT_IDplatforms.teams.tenant_idAzure tenant ID.
TEAMS_WEBHOOK_PORTplatforms.teams.webhook_portTeams webhook listen port.
SMTP_HOSTplatforms.email.smtp.hostSMTP host.
SMTP_PORTplatforms.email.smtp.portSMTP port.
SMTP_USERNAMEplatforms.email.smtp.usernameSMTP username.
SMTP_PASSWORDplatforms.email.smtp.passwordSMTP password.
SMTP_FROM_ADDRESSplatforms.email.smtp.from_addressSMTP from address.
AETHER_LOG_LEVELlogging.levelLog level override.
MSGBRIDGE_ADMIN_ENABLEDadmin.enabledToggle admin REST API.
MSGBRIDGE_ADMIN_PORTadmin.portAdmin API port.
MSGBRIDGE_ADMIN_API_KEYadmin.api_keyAdmin API key.

For the full environment variable reference see Environment Variables.

The following platform adapters are present in the codebase and wired into the server:

PlatformInboundOutboundNotes
DiscordYesYesBot token required; listens for messages in mapped channels.
Microsoft TeamsYesYesBot Framework Activity webhook; requires a public HTTPS endpoint for Teams to POST to.
Email (SMTP)NoYesOutbound-only for now. IMAP inbound is explicitly noted in the code as a future addition.

No Slack adapter exists in the codebase. Do not assume Slack support.

Agents send bridge messages by publishing a JSON object to the msgbridge topic (br::{impl}::{spec}):

{
"bridge_action": "send",
"target": {
"mapping": "my-channel-alias"
},
"content": "Hello from Aether",
"subject": "Optional email subject",
"html_content": "<p>Optional HTML body for email</p>",
"embeds": [],
"metadata": {}
}

The target can specify a named channel mapping alias (mapping) or a direct platform/channel pair:

"target": {
"platform": "discord",
"channel_id": "1234567890",
"thread_id": ""
}

Bridge principals sit at a workspace boundary. A single msgbridge instance may relay messages into multiple Aether workspaces, so ACL rules must be configured carefully:

  • The bridge identity br::{impl}::{spec} must be granted send permission for each target agent, user, or workspace topic it needs to reach.
  • Inbound messages from untrusted platform users should be routed to a dedicated workspace or agent that performs its own validation before acting.
  • Wildcard bridge ACL rules (e.g. br::*) are rejected by the gateway; at minimum both impl and a specific or wildcard spec segment must be present (br::my-bridge::*).
  • Per-mapping direction controls (inbound, outbound, or both) limit blast radius if a platform credential is compromised.

Consult your gateway ACL configuration for rule syntax.

All platform credentials (bot tokens, app passwords, SMTP passwords) should be provided via environment variables, not written into the YAML config. In container deployments use a secrets manager (Vault, AWS Secrets Manager, Kubernetes Secrets) to inject them at runtime.

msgbridge maintains an exponential-backoff reconnect loop against the gateway (initial 1 s, max 30 s). Platform adapter connections follow their own retry logic inside each adapter. A FORCE_DISCONNECT from the gateway (e.g. from MaxConnectionAge rotation) is treated as a clean disconnect and triggers an immediate reconnect attempt.

No built-in rate limiting is implemented at the msgbridge layer. Discord and Teams each impose their own API rate limits. If your workload produces high message volumes, add a queuing layer in front of msgbridge or throttle at the agent side before publishing to the bridge topic.

The admin server exposes Prometheus metrics at GET /metrics (port 31882 by default, no auth). Key metrics:

MetricDescription
msgbridge_messages_routed_totalCounter of routed messages, labelled by direction, platform, and status.
msgbridge_message_routing_duration_secondsHistogram of routing latency per direction and platform.
msgbridge_platform_healthyGauge (0/1) per platform adapter health check.
msgbridge_active_mappingsGauge of enabled channel mappings.

A GET /health endpoint (no auth) returns per-platform adapter health status as JSON.

The admin server (port 31882, prefix /api/v1) exposes CRUD endpoints for channel mappings, user mappings, and message logs. Protect it with admin.api_key / MSGBRIDGE_ADMIN_API_KEY in any non-development deployment.

SymptomCheck
Bridge not connecting to gatewayVerify aether.address, API key, and TLS settings. Check gateway logs for rejected connection.
Messages dropped with “no channel mapping found”Create a channel mapping via the admin API for the target platform channel.
Teams inbound not arrivingEnsure the Teams webhook port is reachable from the internet (or Teams service IPs) and that the bot is registered in Azure Bot Service.
Email inbound not workingIMAP inbound is not yet implemented; only SMTP outbound is supported.
High error rate in PrometheusCheck msgbridge_messages_routed_total{status="failed"} and correlate with structured logs on stderr.