Skip to content

Integrations — Architecture

Audience: Architects, tech leads, senior engineers.


Context and Purpose

The Integrations module exists to provide a unified, secure mechanism for connecting Swisper to external services. Each integration type has a different connection protocol (OAuth 2.0, verification code, API key), but all share common concerns: encrypted credential storage, connection lifecycle management, and a unified status API for the frontend.

The driving architectural requirements are:

  • Encrypted at rest — All tokens, API keys, and configuration must be PGP-encrypted in PostgreSQL
  • Separate storage models — OAuth tokens (with refresh logic, expiry, sync state) have different storage needs than simple key-value integrations, justifying two tables
  • Unified status — The frontend needs a single API call to show the state of all integrations regardless of their underlying storage model

Architecture Overview

graph TD
    subgraph Frontend["Frontend"]
        UI["Integrations Settings"]
        CB["OAuth Callback Pages"]
    end

    subgraph API["API Layer (swisper/api/routes/)"]
        IE["integrations_email.py"]
        IS["integrations_status.py"]
        IT["integrations_telegram.py"]
        ITH["integrations_threema.py"]
        IW["integrations_wealthos.py"]
    end

    subgraph Services["Services"]
        TRS["TelegramRegistrationService"]
        THRS["ThreemaRegistrationService"]
    end

    subgraph Storage["Database (PGP-encrypted)"]
        ET["emailtoken table"]
        UI_T["integrations table"]
    end

    subgraph External["External Providers"]
        GOOGLE["Google OAuth / Gmail API"]
        MS["Microsoft OAuth / Graph API"]
        TBOT["Telegram Bot API"]
        TGW["Threema Gateway"]
        WOS["WealthOS API"]
    end

    UI --> IS
    CB --> IE
    UI --> IT & ITH & IW

    IE --> ET
    IE --> GOOGLE & MS
    IT --> TRS --> UI_T
    IT --> TBOT
    ITH --> THRS --> UI_T
    IW --> UI_T

    IS --> ET & UI_T

Component Responsibilities

Component File Responsibility
Email routes api/routes/integrations_email.py OAuth callback handling (code → token exchange), disconnect, default provider selection, email signature CRUD
Status route api/routes/integrations_status.py Aggregates connection state from both emailtoken and integrations tables into a unified response
Telegram routes api/routes/integrations_telegram.py Generate verification code, verify code (poll getUpdates for matching message), disconnect
Threema routes api/routes/integrations_threema.py Connect (store Threema ID), status check, disconnect
WealthOS routes api/routes/integrations_wealthos.py Connect (store API key), disconnect
TelegramRegistrationService services/signals/telegram_registration_service.py Verification code generation, Telegram Bot API polling, chat_id extraction and storage
ThreemaRegistrationService services/signals/threema_registration_service.py Threema ID validation, integration record creation

Data Model

EmailToken (table: emailtoken)

Stores OAuth credentials for email/calendar providers. One row per provider per user.

Field Type Purpose
id UUID Primary key
user_id UUID FK → user, CASCADE
email PGPString User's email address (encrypted)
access_token PGPString OAuth access token (encrypted)
refresh_token PGPString OAuth refresh token (encrypted, nullable)
expires_in int Token lifetime in seconds
token_type str Default "Bearer"
provider Enum GMAIL or OFFICE365
is_default bool Whether this is the default send provider
default_email_signature PGPString HTML email signature (encrypted, nullable)
last_email_sync datetime Last successful email ingestion timestamp
last_calendar_sync datetime Last successful calendar sync timestamp
sync_state PGPString Provider-specific sync cursor (encrypted, nullable)

UserIntegration (table: integrations)

Stores non-OAuth integrations with a typed JSON config field.

Field Type Purpose
id UUID Primary key
user_id UUID FK → user, CASCADE
integration_type Enum TELEGRAM, THREEMA, WEALTHOS
integration_category str notification_channel or data_integration
config PGPString Encrypted JSON (schema depends on integration_type)
is_active bool Whether the integration is currently connected
connected_at datetime When the integration was established
disconnected_at datetime When the integration was disconnected (nullable)
last_used_at datetime Last time the integration was used (nullable)
notifications_enabled bool Per-channel notification toggle (nullable for data integrations)

Config Schemas by Type

Type Config Fields
Telegram verification_code (during setup), chat_id (after verification)
Threema threema_id (8-char uppercase ID)
WealthOS api_key

Key Design Decisions

Decision: Two separate tables for OAuth vs. key-based integrations

  • Chosen: emailtoken for OAuth providers, integrations for everything else
  • Rejected: Single unified table for all integration types
  • Rationale: OAuth tokens have provider-specific fields (access/refresh tokens, expiry, sync state, email signature) that don't apply to key-based integrations. A single table would require many nullable columns. Separate tables keep each model focused, at the cost of the status API needing to query both.

Decision: PGP encryption for all credential fields

  • Chosen: PostgreSQL PGP symmetric encryption (PGPString column type) for tokens, API keys, and config JSON
  • Rejected: Application-layer encryption, vault-based secrets
  • Rationale: PGP encryption at the database column level means credentials are encrypted even in database backups and direct SQL access. It requires no external infrastructure (like HashiCorp Vault) while meeting the security requirements.

Decision: Polling-based Telegram verification instead of webhook

  • Chosen: Backend polls getUpdates from the Telegram Bot API to find the verification code message
  • Rejected: Telegram webhook that pushes messages to the backend
  • Rationale: Polling is simpler to deploy (no public webhook URL needed) and works behind firewalls. The verification is a one-time operation, so the polling overhead is negligible.

Interfaces and Contracts

Interface Direction Consumer Contract
GET /integrations/status Outbound Frontend integrations page Returns IntegrationsStatusResponse with optional gmail, office365, telegram, threema, wealthos keys
POST /integrations/email/gmail/callback Inbound Frontend OAuth redirect { code, user_id } → exchanges code for tokens, stores encrypted
POST /integrations/telegram/generate-code Outbound Frontend Returns 4-digit verification code
POST /integrations/telegram/verify Inbound Frontend Polls Telegram Bot API, extracts chat_id, stores in integrations
EmailToken → Provider adapters Outbound Productivity agent, background jobs GmailProvider / Office365Provider read tokens, auto-refresh if expired
UserIntegration → Signal channels Outbound SignalsService, notification jobs TelegramChannel / ThreemaChannel read config.chat_id / config.threema_id for message delivery

Known Trade-offs and Debt

Item Impact Remediation
No webhook-based email sync Email and calendar data are fetched via polling jobs, introducing a delay between arrival and availability in Swisper Implement Gmail push notifications and Microsoft Graph subscriptions for real-time sync
One token per provider Reconnecting replaces the existing token. Users cannot connect multiple Gmail accounts Support multi-account by removing the upsert constraint and adding account selection
Telegram polling for verification The getUpdates call retrieves recent messages and scans for the code. In high-traffic bots, the code message may be missed Consider Telegram webhooks or a longer polling window
No token health monitoring If a refresh token is silently revoked, the integration fails on next use with no proactive alert to the user Add a periodic token health check job that tests connectivity and alerts on failure