messaging-center/SPEC.md

15 KiB

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

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

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):

{
  "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

{
  "type": "route",
  "payload": {
    "attachment_id": "att_xyz",
    "destination": "docsys",
    "options": {}
  }
}

send - Send message (alias for POST /messages)

{
  "type": "send",
  "payload": {
    "channel": "signal",
    "to": "+17272252475",
    "body": "Got it!"
  }
}

delete - Delete message (email)

{
  "type": "delete",
  "payload": {
    "message_id": "msg_abc"
  }
}

archive - Archive message (email)

{
  "type": "archive",
  "payload": {
    "message_id": "msg_abc"
  }
}

forward - Forward attachment to chat

{
  "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:

{
  "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

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