474 lines
15 KiB
Markdown
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.*
|