# Messaging Center — Specification **Version:** 0.1.0 **Status:** Draft **Author:** James ⚡ ## Overview Unified messaging hub that aggregates multiple communication channels into a single, normalized interface. OpenClaw (or any client) receives metadata via webhook, makes decisions, and sends commands back via REST API. **Key principle:** Files never transit through OpenClaw. MC stores attachments locally, sends metadata only, executes routing commands. ## Architecture ``` ┌─────────────────────────────────────────────────────────────────┐ │ MESSAGING CENTER │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ Email │ │WhatsApp │ │ Signal │ │ SMS │ │Voicemail│ │ │ │ (IMAP) │ │(meow) │ │ (cli) │ │ (future)│ │(future) │ │ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ │ │ │ │ │ │ └───────────┴───────────┼───────────┴───────────┘ │ │ ▼ │ │ ┌───────────────────┐ │ │ │ Adapter Layer │ │ │ └─────────┬─────────┘ │ │ ▼ │ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ Core Engine │ │ │ │ - Normalize messages │ │ │ │ - Store attachments (local) │ │ │ │ - Transcribe voice (Fireworks Whisper) │ │ │ │ - Queue outbound messages │ │ │ │ - Execute commands │ │ │ └───────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌───────────────┼───────────────┐ │ │ ▼ ▼ ▼ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ REST API │ │ Webhooks │ │ Web GUI │ │ │ │ (commands) │ │ (events) │ │ (oversight) │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ OPENCLAW │ │ - Receives message metadata (no files) │ │ - Makes decisions │ │ - Sends commands back to MC │ └─────────────────────────────────────────────────────────────────┘ ``` ## Tech Stack | Component | Technology | |-----------|------------| | Language | Go 1.25 | | Database | SQLite (embedded, simple) | | Web GUI | Go + templ + htmx | | Auth | OAuth 2.0 (local provider, or external like Authentik) | | Transcription | Fireworks Whisper API | | Email | go-imap v2 | | WhatsApp | whatsmeow | | Signal | signal-cli JSON-RPC (external daemon) | ## Data Model ### Message ```go type Message struct { ID string `json:"id"` Source string `json:"source"` // email, whatsapp, signal, sms, voicemail Direction string `json:"direction"` // inbound, outbound From Contact `json:"from"` To Contact `json:"to"` Timestamp time.Time `json:"timestamp"` Type string `json:"type"` // text, voice, image, document, email Subject string `json:"subject,omitempty"` Body string `json:"body"` // text content or transcription BodyHTML string `json:"body_html,omitempty"` Attachments []Attachment `json:"attachments,omitempty"` Status string `json:"status"` // received, processing, delivered, failed Raw json.RawMessage `json:"raw,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type Contact struct { ID string `json:"id"` // phone number, email, etc. Name string `json:"name,omitempty"` Type string `json:"type"` // phone, email, username } type Attachment struct { ID string `json:"id"` MessageID string `json:"message_id"` Type string `json:"type"` // mime type Filename string `json:"filename,omitempty"` Size int64 `json:"size"` LocalPath string `json:"-"` // not exposed via API Transcription string `json:"transcription,omitempty"` Status string `json:"status"` // stored, processing, routed, deleted } ``` ### Command ```go type Command struct { ID string `json:"id"` Type string `json:"type"` // send, route, delete, archive, forward Payload json.RawMessage `json:"payload"` Status string `json:"status"` // pending, executing, completed, failed Error string `json:"error,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } ``` ## REST API Base URL: `http://localhost:8040/api/v1` ### Authentication OAuth 2.0 Bearer tokens. All API endpoints require `Authorization: Bearer `. Token endpoint: `POST /oauth/token` - Grant types: `client_credentials`, `authorization_code` - Scopes: `messages:read`, `messages:write`, `commands:write`, `admin` ### Endpoints #### Messages | Method | Endpoint | Description | Scope | |--------|----------|-------------|-------| | GET | /messages | List messages (paginated, filterable) | messages:read | | GET | /messages/:id | Get single message | messages:read | | GET | /messages/:id/attachments/:att_id | Download attachment | messages:read | | POST | /messages | Send new message | messages:write | **Query parameters for GET /messages:** - `source` - filter by source (email, whatsapp, signal) - `direction` - inbound, outbound - `type` - text, voice, image, document, email - `since` - ISO8601 timestamp - `until` - ISO8601 timestamp - `limit` - max results (default 50, max 200) - `offset` - pagination offset **POST /messages (send):** ```json { "channel": "whatsapp", "to": "+17272252475", "body": "Hello!", "attachments": [ {"id": "att_xyz"} // reference existing attachment ] } ``` #### Commands | Method | Endpoint | Description | Scope | |--------|----------|-------------|-------| | POST | /commands | Execute command | commands:write | | GET | /commands/:id | Get command status | commands:write | **Command types:** **route** - Send attachment to external system ```json { "type": "route", "payload": { "attachment_id": "att_xyz", "destination": "docsys", "options": {} } } ``` **send** - Send message (alias for POST /messages) ```json { "type": "send", "payload": { "channel": "signal", "to": "+17272252475", "body": "Got it!" } } ``` **delete** - Delete message (email) ```json { "type": "delete", "payload": { "message_id": "msg_abc" } } ``` **archive** - Archive message (email) ```json { "type": "archive", "payload": { "message_id": "msg_abc" } } ``` **forward** - Forward attachment to chat ```json { "type": "forward", "payload": { "attachment_id": "att_xyz", "channel": "whatsapp", "to": "+17272252475", "caption": "Here's that file" } } ``` #### Channels | Method | Endpoint | Description | Scope | |--------|----------|-------------|-------| | GET | /channels | List configured channels | admin | | GET | /channels/:name/status | Get channel status | admin | | POST | /channels/:name/reconnect | Force reconnect | admin | #### Admin | Method | Endpoint | Description | Scope | |--------|----------|-------------|-------| | GET | /health | Health check (no auth) | - | | GET | /ready | Readiness check (no auth) | - | | GET | /stats | System statistics | admin | ## Webhooks MC sends webhooks to configured endpoints on events. **Webhook payload:** ```json { "event": "message.received", "timestamp": "2026-02-02T21:30:00Z", "data": { "id": "msg_abc123", "source": "whatsapp", "from": {"id": "+17272253810", "name": "Tanya"}, "type": "voice", "body": "Hey, can you check on Sophia's appointment?", "attachments": [ {"id": "att_xyz", "type": "audio/ogg", "size": 8420, "transcription": "Hey, can you check on Sophia's appointment?"} ] } } ``` **Events:** - `message.received` - New inbound message - `message.sent` - Outbound message delivered - `message.failed` - Outbound message failed - `channel.connected` - Channel connected - `channel.disconnected` - Channel disconnected - `command.completed` - Command finished - `command.failed` - Command failed **Webhook auth:** HMAC-SHA256 signature in `X-MC-Signature` header. ## Web GUI Dashboard for human oversight at `http://localhost:8040/` ### Pages 1. **Dashboard** - Overview of all channels, recent messages, system health 2. **Messages** - Searchable/filterable message list with details 3. **Channels** - Channel status, configuration, reconnect buttons 4. **Commands** - Command history and status 5. **Settings** - OAuth clients, webhooks, routing rules ### Features - Real-time updates via SSE (Server-Sent Events) - Mobile responsive (Tailwind CSS) - Dark mode - Message search and filters - Attachment preview (images, PDFs) - Audio playback for voice messages ## Configuration ```yaml # config.yaml server: host: 0.0.0.0 port: 8040 database: path: /var/lib/messaging-center/mc.db storage: path: /var/lib/messaging-center/attachments oauth: issuer: http://localhost:8040 signing_key: ${MC_SIGNING_KEY} access_token_ttl: 1h clients: - id: openclaw secret: ${MC_OPENCLAW_SECRET} scopes: [messages:read, messages:write, commands:write] - id: admin secret: ${MC_ADMIN_SECRET} scopes: [messages:read, messages:write, commands:write, admin] transcription: provider: fireworks api_key: ${FIREWORKS_API_KEY} model: whisper-v3-turbo webhooks: - url: http://localhost:18789/hooks/message secret: ${MC_WEBHOOK_SECRET} events: [message.received, message.sent] routing: docsys: url: http://localhost:8050/ingest auth: bearer ${DOCSYS_TOKEN} channels: email: enabled: true imap: host: 127.0.0.1 port: 1143 username: tj@jongsma.me password: ${PROTON_BRIDGE_PASSWORD} tls: starttls smtp: host: 127.0.0.1 port: 1025 username: tj@jongsma.me password: ${PROTON_BRIDGE_PASSWORD} tls: starttls whatsapp: enabled: true data_dir: /var/lib/messaging-center/whatsapp signal: enabled: true api_url: http://localhost:8080 account: +31634481877 ``` ## Directory Structure ``` messaging-center/ ├── cmd/ │ └── mc/ │ └── main.go ├── internal/ │ ├── adapter/ │ │ ├── adapter.go # Interface │ │ ├── email/ │ │ ├── whatsapp/ │ │ └── signal/ │ ├── api/ │ │ ├── router.go │ │ ├── messages.go │ │ ├── commands.go │ │ ├── channels.go │ │ └── oauth.go │ ├── core/ │ │ ├── engine.go │ │ ├── message.go │ │ ├── command.go │ │ └── transcribe.go │ ├── store/ │ │ ├── sqlite.go │ │ └── migrations/ │ ├── web/ │ │ ├── handlers.go │ │ ├── templates/ │ │ └── static/ │ └── webhook/ │ └── sender.go ├── config.yaml ├── go.mod ├── go.sum ├── Dockerfile ├── docker-compose.yml └── README.md ``` ## Milestones ### v0.1 — Foundation - [ ] Project structure - [ ] SQLite store with migrations - [ ] Core engine (message normalization) - [ ] REST API scaffold - [ ] OAuth 2.0 (client_credentials) - [ ] Health/ready endpoints ### v0.2 — Email Adapter - [ ] IMAP inbound (from mail-bridge) - [ ] SMTP outbound - [ ] Email-specific commands (delete, archive, move) ### v0.3 — WhatsApp Adapter - [ ] whatsmeow integration (from message-bridge) - [ ] Voice transcription (Fireworks) - [ ] Media handling ### v0.4 — Signal Adapter - [ ] signal-cli JSON-RPC client - [ ] Bidirectional messaging ### v0.5 — Web GUI - [ ] Dashboard - [ ] Message browser - [ ] Channel status - [ ] Settings ### v0.6 — Webhooks & Routing - [ ] Webhook sender with HMAC - [ ] docsys routing - [ ] Command execution ### v1.0 — Production Ready - [ ] Systemd service - [ ] Documentation - [ ] Docker image - [ ] Backup/restore --- ## Open Questions 1. **OAuth provider:** Build simple local provider, or integrate with external (Authentik)? - *Recommendation:* Start with simple local provider, extract later if needed 2. **Signal-cli:** Keep as external daemon, or embed? - *Recommendation:* Keep external (Java, complex), adapter calls JSON-RPC 3. **Database:** SQLite vs Postgres? - *Recommendation:* SQLite for now (simpler), abstract for later swap 4. **Message retention:** How long to keep messages/attachments? - **Decision:** Forever. Storage is cheap. --- *Ready to build. Starting with v0.1 foundation.*