Skip to content

System Architecture Overview

SPOT uses a microservices architecture with pluggable analyzers and workflow-based orchestration.

High-Level Architecture

graph TB
    Client[Users, dashboard, CLI]
    Retriever[Mail retrievers]
    Gateway[API Gateway<br/>:8001]
    MO[Mail Orchestrator]
    AO[Analyzer Orchestrator]
    KS[Knowledge service]

    A1[Analyzers]
    CP[Context providers]
    R1[Responders]

    DB[(PostgreSQL)]
    Redis[(Redis)]
    RMQ[RabbitMQ]

    Client -->|raw message| Gateway
    Retriever -->|POST /internal/ingest| Gateway
    Gateway -->|parse, store raw + view| DB
    Gateway -->|email id| MO
    Gateway -->|Publish to spot.analysis| RMQ
    MO -->|Publish to spot.analysis| RMQ
    Gateway -->|Rate limiting, job records| Redis

    RMQ -->|Consume from orchestrator.analysis| AO

    AO -->|POST /internal/analyze| A1
    A1 -->|AnalyzerVerdict| AO
    A1 -.->|raw message, parts| Gateway
    A1 -.->|query| KS
    CP -->|documents| KS
    Gateway -->|POST /internal/sync| CP

    AO -->|Store Results| DB
    AO -->|Publish to spot.results| RMQ
    RMQ -->|orchestrator.dispatch| AO
    AO -->|POST /internal/respond| R1

Component Overview

Component Technology Dependencies Purpose
API Gateway FastAPI RabbitMQ, Redis, PostgreSQL External API, authentication, intake
Analyzer Orchestrator Python asyncio RabbitMQ, Redis, PostgreSQL Workflow execution, verdict policy, responder dispatch
Mail Orchestrator Python asyncio RabbitMQ, Redis Queues retrievers' messages, waits for verdicts
Knowledge service FastAPI, pgvector PostgreSQL, Redis, Ollama Knowledge Store
Plugin Manager Python Docker (through a socket proxy) Runs the plugin containers
Plugins External services spot-sdk 2 Analyzers, context providers, mail retrievers, responders

Core Components

API Gateway

  • Port: 8001 in development; https://api.DOMAIN, behind Traefik, in a deployment
  • Technology: FastAPI (Python)
  • Responsibilities:
  • Authentication & authorization (OAuth2/JWT for users, scoped tokens for plugins)
  • Intake: parses each raw message once and stores it, encrypted
  • Routing: chooses each email's workflow and its mode (observe or enforce)
  • Job submission via RabbitMQ
  • Status queries from PostgreSQL and Redis
  • Plugin catalog, installer and configuration (spot.yaml, with its history)
  • The audit log

Analyzer Orchestrator

  • Technology: Python asyncio + RabbitMQ
  • Responsibilities:
  • DAG-based workflow execution
  • Analyzer calls over HTTP, with each analyzer's token
  • Verdict decided by the workflow's verdict_policy
  • Failure handling & retries: a failure is unknown, never benign
  • Dispatch of verdicts to responders
  • Shadow runs: candidate workflows on production's results, compared with its verdicts

Mail Orchestrator

  • Technology: Python asyncio
  • Responsibilities:
  • Receives the emails retrievers ingested, once the API Gateway has stored them
  • Publishes them for analysis
  • Waits for the verdict when the retriever asks for it

Plugins (Pluggable)

  • Analyzers: rules, NLP, ML, LLM and MISP analyzers answer a verdict, a score and signals
  • Context providers: fill the Knowledge Store (employee directory, ...)
  • Mail retrievers: send raw messages from a mail system (the milter, ...)
  • Responders: act on verdicts

Infrastructure Services

PostgreSQL

  • Port: 5432 (internal network only)
  • Stores: Emails (raw and view, encrypted), analysis results and signals, user accounts, plugin tokens, the Knowledge Store

Redis

  • Port: 6379 (internal network only)
  • Purpose: Rate limiting, job records, plugin catalog, caches

RabbitMQ

  • Ports: 5672 (AMQP), 15672 (Management UI), internal network only; the api-gateway reaches the Management UI over it
  • Exchanges:
  • spot.analysis (TOPIC) - Job routing
  • spot.results (TOPIC) - Verdicts of finished jobs
  • spot.config (TOPIC) - Configuration reloads
  • Queues:
  • orchestrator.analysis - Job consumption
  • orchestrator.analysis.sync - The ingests of retrievers that wait for their verdict
  • orchestrator.dispatch - Verdicts to dispatch to responders
  • orchestrator.shadow - Production's results, for the shadow workflows
  • orchestrator.benchmark - The emails of a platform benchmark run (a backtest)
  • <queue>.dead-letter - The messages each queue parked after its delivery limit

Design Principles

1. Separation of Concerns

  • API Gateway: External interface
  • Orchestrators: Business logic
  • Analyzers: Specialized analysis
  • Infrastructure: Data & messaging

2. Asynchronous Processing

  • Non-blocking I/O throughout
  • RabbitMQ for job distribution
  • Polling for status updates

3. Pluggable Architecture

  • Plugins as independent HTTP services on plugin contract 2 (spot-sdk 2)
  • Configured in spot.yaml under plugins:, by kind

4. Configuration via Environment + YAML

  • Infrastructure settings and secrets via environment variables
  • Platform settings, plugins, workflows and routing via spot.yaml, on the spot-config volume; every change is a commit of its history
  • No database-backed config

Scalability

Horizontal Scaling

  • Plugins: Scale independently based on workload
  • API Gateway: Behind load balancer
  • Orchestrators: Single instance (workflow consistency)

Vertical Scaling

  • PostgreSQL: SSD storage, more RAM
  • RabbitMQ: Increase connection limits
  • Redis: Memory tuning

Security

Authentication

  • OAuth2 with JWT tokens for users and OAuth clients (client credentials)
  • A scoped token for each plugin, in both directions

Network Security

  • Postgres, Redis and RabbitMQ on an internal Docker network with no route out and no host port
  • Installed plugins on an internal network (spot-plugins-isolated) with no route out: they reach the api-gateway, the knowledge service, the analyzer-orchestrator and, through the egress gateway, the hosts their settings name
  • The analyzer-orchestrator API requires the internal key on every route except /health and /version
  • Only Traefik is exposed: the dashboard and the API Gateway behind it, over TLS. With the mail-milter profile, the milter's port too, on RETRIEVER_MILTER_BIND

Next Steps