messaging-center/SPEC.md

474 lines
15 KiB
Markdown

# 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>`.
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.*