NornWeave Architecture

NornWeave’s architecture uses thematic naming inspired by Norse mythology, with each component having a clear technical purpose.

Component Overview

LayerNameRoles
Storage LayerUrdr (The Well)Database adapters for persistence
Attachment StorageUrdr (Adapters)File storage (Local, S3, GCS, Database)
Ingestion EngineVerdandi (The Loom)Webhook processing, HTML to Markdown parsing
API & OutboundSkuld (The Prophecy)REST API, email sending, rate limiting
API GatewayYggdrasilCentral router connecting all providers
MCP ToolsHuginn & MuninnRead/write tools for AI agents

Component connections and actions

The diagram below shows how the main components connect and what actions they perform on each other.

  flowchart LR
    subgraph external [External]
        Provider[Email Providers<br/>Mailgun, SES, SendGrid, Resend]
    end

    subgraph internal [Internal Network]
        subgraph agents [AI Agents]
            Agent[AI Agent<br/>REST or MCP client]
        end

        subgraph yggdrasil [Yggdrasil - Gateway]
            G[API Router]
        end

        subgraph verdandi [Verdandi - Ingestion]
            V[Parse webhook<br/>HTML→Markdown<br/>Threading]
        end

        subgraph urdr [Urdr - Storage]
            U[(PostgreSQL<br/>SQLite)]
        end

        subgraph skuld [Skuld - Outbound]
            S[Send email<br/>Rate limit]
        end

        subgraph mcp [Huginn & Muninn]
            M[MCP read/write]
        end
    end

    Provider -->|"POST /webhooks/{provider}"| G
    G -->|"route webhook"| V
    V -->|"store message & thread"| U
    Agent -->|"GET /v1/threads, POST /v1/messages"| G
    M -->|"calls API"| G
    G -->|"fetch thread/messages"| U
    G -->|"send message"| S
    S -->|"deliver via API"| Provider
    S -->|"persist sent message"| U
  • Inbound: Provider sends webhook → Yggdrasil routes → Verdandi parses and threads → Urdr stores.
  • Read: Agent (or MCP) calls API → Yggdrasil → Urdr returns thread/messages.
  • Reply: Agent (or MCP) posts message → Yggdrasil → Skuld sends via provider and Urdr stores the sent message.

System Architecture Diagram

  flowchart TB
    subgraph external [External]
        subgraph providers [Email Providers]
            MG[Mailgun]
            SES[AWS SES]
            SG[SendGrid]
            RS[Resend]
        end
        subgraph cloudstorage [Cloud Storage]
            S3[AWS S3]
            GCS[Google Cloud Storage]
        end
    end
    
    subgraph internal [Internal Network]
        subgraph agents [AI Agents]
            Agent1[Claude / Cursor<br/>stdio]
            Agent2[Web Clients<br/>SSE]
            Agent3[LangChain / Cloud<br/>HTTP]
        end

        subgraph mcp [Huginn & Muninn - MCP Server]
            Transports["Transports<br/>stdio | SSE | HTTP"]
            Resources["Resources<br/>email://inbox | email://thread"]
            Tools["Tools<br/>create_inbox | send_email | search_email<br/>list_attachments | get_attachment_content"]
        end

        subgraph yggdrasil [Yggdrasil - API Gateway]
            WH[Webhook Routes]
            API["REST API v1<br/>inboxes | threads | messages | attachments"]
        end
        
        subgraph verdandi [Verdandi - Ingestion Engine]
            Parser[HTML Parser]
            Sanitizer[Cruft Remover]
            Threader[Threading Logic]
            AttExtract[Attachment Extractor]
        end
        
        subgraph urdr [Urdr - Storage Layer]
            PG[(PostgreSQL)]
            SQLite[(SQLite)]
            subgraph attstorage [Attachment Storage]
                Local[Local Filesystem]
                DBBlob[Database BLOB]
            end
        end
        
        subgraph skuld [Skuld - Outbound Sender]
            Sender[Email Sender]
            RateLimiter[Rate Limiter]
        end
    end
    
    providers --> WH
    WH --> verdandi
    verdandi --> urdr
    AttExtract --> attstorage
    AttExtract -.-> cloudstorage
    Agent1 --> Transports
    Agent2 --> Transports
    Agent3 --> Transports
    Transports --> Resources
    Transports --> Tools
    Resources --> API
    Tools --> API
    API --> urdr
    API --> skuld
    API --> attstorage
    API -.-> cloudstorage
    skuld --> providers

Data Flow

Inbound Email Flow

  1. External user sends an email to your inbox address
  2. Email provider (Mailgun, etc.) calls the webhook endpoint
  3. Yggdrasil receives the webhook and routes to the appropriate handler
  4. Verdandi parses the raw email:
    • Converts HTML to Markdown
    • Removes reply cruft (“On Jan 1, John wrote:”)
    • Extracts attachments
  5. Verdandi determines threading using In-Reply-To and References headers
  6. Urdr stores the message and updates the thread

Agent Reading Flow

  1. AI Agent calls GET /v1/threads/{id} (or uses MCP)
  2. Yggdrasil routes the request
  3. Urdr fetches the thread and messages
  4. Response formatted as LLM-ready conversation

Agent Reply Flow

  1. AI Agent calls POST /v1/messages (or uses MCP send_email)
  2. Yggdrasil routes the request
  3. Skuld processes the outbound message:
    • Converts Markdown to HTML
    • Adds threading headers
    • Applies rate limiting
  4. Skuld sends via the configured provider
  5. Urdr stores the sent message

MCP Transport Options

The MCP server (Huginn & Muninn) supports three transport types to accommodate different deployment scenarios:

TransportUse CaseHow it Works
stdioClaude Desktop, Cursor, local CLICommunicates via standard input/output
SSEWeb-based MCP clients, browser integrationsServer-Sent Events over HTTP
HTTPCloud deployments, LangChain, load-balanced setupsStreamable HTTP requests
  flowchart LR
    subgraph clients [MCP Clients]
        Claude[Claude Desktop]
        Cursor[Cursor IDE]
        Web[Web Clients]
        Cloud[LangChain / Cloud]
    end
    
    subgraph mcp [MCP Server]
        stdio[stdio transport]
        sse[SSE transport]
        http[HTTP transport]
    end
    
    subgraph api [REST API]
        Yggdrasil[Yggdrasil]
    end
    
    Claude --> stdio
    Cursor --> stdio
    Web --> sse
    Cloud --> http
    stdio --> Yggdrasil
    sse --> Yggdrasil
    http --> Yggdrasil

Sequence Diagram

  sequenceDiagram
    participant User as External User
    participant Provider as Email Provider
    participant Yggdrasil as Yggdrasil - API Gateway
    participant Verdandi as Verdandi - Ingestion Engine
    participant Urdr as Urdr - Storage
    participant Skuld as Skuld - Outbound Sender
    participant Agent as AI Agent

    Note over User,Agent: Inbound Email Flow
    User->>Provider: Sends email
    Provider->>Yggdrasil: POST /webhooks/{provider}
    Yggdrasil->>Verdandi: Parse raw email
    Verdandi->>Verdandi: HTML to Markdown
    Verdandi->>Urdr: Store message and thread
    
    Note over User,Agent: Agent Reading Flow
    Agent->>Yggdrasil: GET /v1/threads/{id}
    Yggdrasil->>Urdr: Fetch thread
    Urdr-->>Agent: Markdown conversation
    
    Note over User,Agent: Agent Reply Flow
    Agent->>Yggdrasil: POST /v1/messages
    Yggdrasil->>Skuld: Send email
    Skuld->>Provider: Deliver via API
    Provider->>User: Email delivered

Database Schema

The storage layer enforces this schema through the StorageAdapter:

Inboxes Table

ColumnTypeDescription
idUUIDPrimary key
email_addressStringUnique email address
nameStringDisplay name
provider_configJSONProvider-specific metadata
created_atTimestampCreation time

Threads Table

ColumnTypeDescription
idUUIDPrimary key
inbox_idUUIDForeign key to Inbox
subjectTextThread subject
last_message_atTimestampLast activity
participant_hashStringHash of participants for grouping

Messages Table

ColumnTypeDescription
idUUIDPrimary key
thread_idUUIDForeign key to Thread
inbox_idUUIDForeign key to Inbox
provider_message_idStringMessage-ID header
directionEnumINBOUND or OUTBOUND
content_rawTextOriginal HTML/Text
content_cleanTextLLM-ready Markdown
metadataJSONHeaders, attachments, etc.
created_atTimestampMessage time

Attachments Table

ColumnTypeDescription
idUUIDPrimary key
message_idUUIDForeign key to Message
filenameStringOriginal filename
content_typeStringMIME type
size_bytesIntegerFile size in bytes
dispositionStringattachment or inline
content_idStringContent-ID for inline images
storage_backendStringlocal, database, s3, or gcs
storage_pathStringPath/key in storage backend
content_hashStringSHA-256 hash for deduplication
contentBinaryOptional: for database blob storage
created_atTimestampCreation time

Attachment Storage Architecture

NornWeave supports multiple storage backends for email attachments:

  flowchart LR
    subgraph API [Attachment API]
        Upload[Upload/Store]
        Download[Download/Retrieve]
    end
    
    subgraph Backends [Storage Backends]
        Local[Local Filesystem]
        DB[Database BLOB]
        S3[AWS S3 / MinIO]
        GCS[Google Cloud Storage]
    end
    
    Upload --> Local
    Upload --> DB
    Upload --> S3
    Upload --> GCS
    
    Download --> Local
    Download --> DB
    Download -.->|Presigned URL| S3
    Download -.->|Presigned URL| GCS
BackendBest ForURL Type
LocalDevelopment, single-serverSigned API URL
DatabaseSimple deployments, small filesSigned API URL
S3Production, scalableNative presigned URL
GCSGoogle Cloud deploymentsNative presigned URL
For cloud storage backends (S3, GCS), download URLs are presigned directly by the cloud provider, allowing clients to download without passing through NornWeave’s API.