16 KiB
ManaMail — Module Plan
Status (2026-04-13)
Phase 1 Code ist committed. Service + Frontend-Modul existieren, aber der Service läuft noch nicht in Production.
Offene Punkte — Phase 1 Betrieb
- Fritz!Box Port-Forwarding — Ports 25, 587, 465 (TCP) → Mac Mini (192.168.178.131) für eingehende Mails
- Stalwart JMAP-Port verifizieren —
http://mana-mail:8080im Docker-Netzwerk erreichbar? Ggf. Port 8080 indocker-compose.macmini.ymlzum Stalwart-Container hinzufügen - Docker-Compose Production —
mana-mail-serviceContainer zudocker-compose.macmini.ymlhinzufügen (Port 3042, depends_on: postgres + mana-mail) - Cloudflare Tunnel — Route
mail-api.mana.how→http://mana-mail-service:3042incloudflared-config.yml - Env-Vars Production —
STALWART_JMAP_URL,STALWART_ADMIN_PASSWORD,MANA_MAIL_URLin.env.macminisetzen - DB Schema pushen —
./scripts/setup-databases.sh mailauf Production ausführen - Frontend URL —
PUBLIC_MANA_MAIL_URL=https://mail-api.mana.howin Production-Env für die Mana Web App - Testen — Account-Provisioning bei User-Registrierung, Thread-Abfrage, Mail-Senden
Offene Punkte — Phase 2 KI-Features
- Thread-Zusammenfassungen via
mana-llm(summary →mail.thread_metadata) - Smart Reply — 3 KI-generierte Antwortvorschläge pro Thread
- Auto-Kategorisierung —
important/newsletter/social/todo - Aktions-Extraktion — Termine, Aufgaben, Rechnungen aus Mails erkennen
- SSE-Stream
/api/v1/mail/livefür Echtzeit-Benachrichtigungen
Offene Punkte — Phase 3 Multi-Account
- Gmail OAuth + Gmail API Integration
- Outlook OAuth + Microsoft Graph Integration
- Generisches IMAP/SMTP für andere Provider
- Unified Inbox über alle Accounts
Offene Punkte — Phase 4 Erweitert
- "Später senden" (Scheduled Send)
- "Erinnere mich" (Snooze)
- Mail-Templates mit Variablen
- Mail-Regeln (Auto-Label, Auto-Forward)
- Cross-Module-Linking: Mail → Todo, Mail → Kalender, Mail → Kontakte, Mail → Finance
Motivation
Mana hat bereits eine vollständige Mail-Infrastruktur (Stalwart, mana-notify, DNS/DKIM/SPF), die bisher nur für Transaktionsmails genutzt wird. Ein Mail-Modul macht @mana.how-Adressen für jeden User möglich und schafft einen integrierten Mail-Client mit KI-Features — tief verknüpft mit Todo, Kalender, Kontakte und anderen Modulen.
Bestehendes
| Komponente | Status | Details |
|---|---|---|
| Stalwart Mail Server | Produktiv | Rust, Container mana-mail, Ports 25/587/465/993/8443 |
| DNS (MX, SPF, DKIM, DMARC) | Konfiguriert | mail.mana.how → 194.191.241.139 |
| mana-notify | Produktiv | Go, Port 3013, SMTP-Gateway mit Retry/Queue |
| Mailpit (Dev) | Konfiguriert | Port 1025/8025 für lokale Entwicklung |
| Stalwart Admin API | Verfügbar | Account-CRUD, DKIM-Management |
| Inbound Port-Forwarding | TODO | Fritz!Box Ports 25/587/465 → Mac Mini |
Stalwart JMAP-Support
Stalwart unterstützt JMAP (RFC 8620) nativ — ein modernes, REST-basiertes Mail-Protokoll. Vorteile gegenüber IMAP:
- JSON über HTTP (kein kompliziertes IMAP-State-Management)
- Push-Benachrichtigungen bei neuen Mails (EventSource)
- Effizientes Delta-Sync (nur Änderungen seit letztem State)
- Batch-Requests (mehrere Operationen in einem Call)
- Attachment-Upload via Blob-Store
JMAP wird der primäre Zugriffspfad für den mana-mail Service.
Architektur
Browser (Mana Web)
↓ REST / SSE
mana-mail Service (Hono/Bun, Port 3042)
↓ JMAP (HTTP) ↓ SMTP (Port 587)
Stalwart Stalwart
(Lesen/Sync) (Senden)
↓
Internet ← MX: mail.mana.how
Warum ein eigener Service (nicht direkt JMAP vom Browser)?
- Credentials — JMAP-Auth-Credentials dürfen nicht im Browser landen
- KI-Processing — Zusammenfassungen, Kategorisierung laufen serverseitig
- Cross-Module-Linking — Server kann Mails mit Todo/Kalender/Kontakte verknüpfen
- Rate-Limiting & Caching — Server cached Thread-Listen, limitiert API-Calls
- Account-Provisioning — Stalwart-Account-Erstellung bei User-Registrierung
Phase 1: Fundament (MVP)
1.1 Service: services/mana-mail/
Stack: Hono/Bun, Drizzle ORM, PostgreSQL (mana_platform.mail Schema)
Port: 3042
services/mana-mail/
├── src/
│ ├── index.ts # Bootstrap + Route-Mounting
│ ├── config.ts # Env-Vars laden
│ ├── routes/
│ │ ├── threads.ts # GET /threads, GET /threads/:id
│ │ ├── messages.ts # PUT /messages/:id (read/star/archive)
│ │ ├── send.ts # POST /send, POST /draft
│ │ ├── labels.ts # GET /labels, POST /labels
│ │ ├── accounts.ts # GET /accounts (user's mail accounts)
│ │ └── internal.ts # POST /internal/on-user-created
│ ├── services/
│ │ ├── jmap-client.ts # JMAP-Verbindung zu Stalwart
│ │ ├── mail-service.ts # Business-Logik (Thread-Aufbau, Suche)
│ │ ├── send-service.ts # SMTP-Senden via Stalwart
│ │ └── account-service.ts# Stalwart Account-Provisioning
│ ├── middleware/
│ │ └── jwt-auth.ts # JWT-Validierung (wie mana-credits)
│ └── db/
│ └── schema/
│ └── mail.ts # Drizzle Schema (pgSchema('mail'))
├── package.json
├── tsconfig.json
├── CLAUDE.md
└── Dockerfile
1.2 Datenbank-Schema (mana_platform, Schema mail)
-- User-Mailbox-Einstellungen (nicht die Mails selbst — die leben in Stalwart)
mail.accounts (
id UUID PK,
user_id TEXT NOT NULL REFERENCES auth.users(id),
email TEXT NOT NULL UNIQUE, -- z.B. till@mana.how
display_name TEXT,
provider TEXT DEFAULT 'stalwart', -- Phase 2: 'gmail', 'outlook'
is_default BOOLEAN DEFAULT true,
signature TEXT, -- HTML-Signatur
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ
)
-- Lokaler Label-Cache (Stalwart Labels + User-eigene)
mail.labels (
id UUID PK,
account_id UUID REFERENCES mail.accounts(id),
stalwart_id TEXT, -- JMAP mailboxId
name TEXT NOT NULL,
color TEXT,
type TEXT DEFAULT 'user', -- 'system' | 'user'
sort_order INT DEFAULT 0
)
-- KI-generierte Metadaten pro Thread (Cache)
mail.thread_metadata (
id UUID PK,
account_id UUID REFERENCES mail.accounts(id),
thread_id TEXT NOT NULL, -- JMAP threadId
summary TEXT, -- KI-Zusammenfassung
category TEXT, -- 'important'|'newsletter'|'social'|'todo'
sentiment TEXT, -- 'positive'|'neutral'|'negative'
linked_items JSONB, -- [{appId, recordId}] Cross-Module-Links
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
UNIQUE(account_id, thread_id)
)
Keine Mail-Inhalte in der DB — Mails leben in Stalwart, der Service ist ein Proxy/Cache.
1.3 API-Endpunkte
User-Endpoints (JWT-Auth):
| Method | Path | Beschreibung |
|---|---|---|
GET |
/api/v1/mail/threads |
Thread-Liste (paginiert, Filter nach Label/Unread) |
GET |
/api/v1/mail/threads/:id |
Thread mit allen Messages |
PUT |
/api/v1/mail/messages/:id |
Status ändern (read/unread, star, archive, label) |
POST |
/api/v1/mail/send |
Mail senden (oder Reply) |
POST |
/api/v1/mail/draft |
Entwurf speichern |
DELETE |
/api/v1/mail/draft/:id |
Entwurf löschen |
GET |
/api/v1/mail/labels |
Labels abrufen |
POST |
/api/v1/mail/labels |
Label erstellen |
GET |
/api/v1/mail/accounts |
User's Mail-Accounts |
PUT |
/api/v1/mail/accounts/:id |
Account-Einstellungen (Signatur etc.) |
GET |
/api/v1/mail/live |
SSE-Stream für neue Mails (JMAP EventSource) |
Service-Endpoints (X-Service-Key):
| Method | Path | Beschreibung |
|---|---|---|
POST |
/api/v1/internal/mail/on-user-created |
Account in Stalwart anlegen |
POST |
/api/v1/internal/mail/on-user-deleted |
Account in Stalwart deaktivieren |
1.4 JMAP-Client
// services/mana-mail/src/services/jmap-client.ts
class JmapClient {
constructor(private baseUrl: string, private adminToken: string) {}
// Authentifizierung: Service nutzt Admin-Token, scoped auf User-Account
async getThreads(accountId: string, opts: {
limit?: number;
position?: number;
filter?: { inMailbox?: string; isUnread?: boolean };
sort?: { property: string; isAscending: boolean }[];
}): Promise<Thread[]>
async getThread(accountId: string, threadId: string): Promise<ThreadDetail>
async getEmails(accountId: string, emailIds: string[]): Promise<Email[]>
async setEmailFlags(accountId: string, emailId: string, flags: {
isRead?: boolean;
isFlagged?: boolean;
mailboxIds?: Record<string, boolean>;
}): Promise<void>
async sendEmail(accountId: string, email: {
to: Address[]; cc?: Address[]; bcc?: Address[];
subject: string; body: string; htmlBody?: string;
inReplyTo?: string; references?: string[];
attachments?: Blob[];
}): Promise<string> // returns emailId
async subscribe(accountId: string, onNewEmail: (email: Email) => void): EventSource
}
1.5 Account-Provisioning (bei User-Registrierung)
In mana-auth/src/routes/auth.ts ergänzen (fire-and-forget Pattern):
// Nach erfolgreicher Registrierung:
fetch(`${config.manaMailUrl}/api/v1/internal/mail/on-user-created`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Service-Key': config.serviceKey },
body: JSON.stringify({
userId: response.user.id,
email: body.email,
name: body.name,
}),
}).catch(() => {});
mana-mail erstellt dann:
- Stalwart-Account via Admin-API (
POST /api/principal) mail.accountsEintrag in DB- System-Labels (Inbox, Sent, Drafts, Trash, Spam, Archive)
Adress-Vergabe:
- Default:
username@mana.how(aus Auth-Username) - Fallback bei Kollision:
username123@mana.how - Aliases möglich:
vorname.nachname@mana.how
1.6 Frontend-Modul: modules/mail/
modules/mail/
├── module.config.ts # appId: 'mail', tables: [mailCache, mailDrafts]
├── types.ts # Thread, Message, Label, MailAccount
├── collections.ts # Dexie-Cache-Tabellen
├── queries.ts # useThreads(), useThread(), useLabels()
├── stores/
│ ├── mail.svelte.ts # send, reply, markRead, star, archive, moveToLabel
│ └── drafts.svelte.ts # saveDraft, deleteDraft (local-first)
├── api.ts # fetchWithAuth Wrapper für mana-mail Service
├── ListView.svelte # Inbox-Ansicht (Thread-Liste)
├── views/
│ ├── ThreadView.svelte # Thread-Detail mit Message-Liste
│ └── ComposeView.svelte # Neue Mail / Reply schreiben
└── index.ts
Dexie-Cache (local-first für Offline):
// database.ts v9
mailCache: 'id, threadId, accountId, date, isRead, isFlagged, [accountId+date]'
mailDrafts: 'id, accountId, replyToId'
mailCachespeichert die letzten ~500 Thread-Headers für Offline-ZugriffmailDraftsspeichert Entwürfe lokal (wie alle Module, encrypted)- Encryption:
title(subject),snippet,bodyin mailCache;to,subject,bodyin mailDrafts
ListView.svelte (Inbox):
- Thread-Liste mit Absender, Betreff, Snippet, Datum, Unread-Badge
- Filter: Inbox / Sent / Drafts / Archive / Labels
- Suche über Betreff + Absender
- Pull-to-refresh / SSE für Live-Updates
- Swipe-Gesten: Links = Archivieren, Rechts = Markieren
ThreadView.svelte (Detail):
- Chronologische Message-Liste im Thread
- Reply/Reply-All/Forward Buttons
- Attachment-Anzeige + Download
- "Als Aufgabe erstellen" → Todo-Modul
- "Termin erstellen" → Kalender-Modul
ComposeView.svelte (Schreiben):
- To/Cc/Bcc mit Kontakte-Autocomplete
- Rich-Text-Editor (oder Markdown)
- Attachment-Upload via mana-media
- Signatur automatisch anhängen
- Entwurf-Auto-Save (local-first in Dexie)
Phase 2: KI-Features
2.1 Thread-Zusammenfassungen
- Bei jedem neuen Thread:
mana-llmoderlocal-llmerstellt Summary - Gespeichert in
mail.thread_metadata.summary - Im ListView als Tooltip oder aufklappbarer Abschnitt
2.2 Smart Reply
- 3 Antwort-Vorschläge basierend auf Thread-Kontext
- Generiert via
mana-llmAPI - One-Click-Send oder als Basis für eigene Antwort
2.3 Auto-Kategorisierung
- Neue Mails werden automatisch kategorisiert:
important/newsletter/social/todo - Basis: Absender-Reputation, Betreff-Analyse, Inhalt
- User kann Kategorisierung korrigieren (→ lernt pro User)
2.4 Aktions-Extraktion
- KI erkennt: "Meeting am Donnerstag um 14 Uhr" → Kalender-Vorschlag
- "Bitte bis Freitag erledigen" → Todo-Vorschlag
- "Rechnung anbei" → Finance-Modul Vorschlag
- Angezeigt als Action-Chips unter der Mail
Phase 3: Multi-Account
3.1 Externe Accounts
- Gmail via Google OAuth + Gmail API
- Outlook via Microsoft OAuth + Microsoft Graph
- Generisches IMAP/SMTP für andere Provider
3.2 Unified Inbox
- Alle Accounts in einer Thread-Liste
- Account-Badge pro Thread (welcher Account)
- Antwort automatisch vom richtigen Account
Phase 4: Erweitert
4.1 Scheduling
- "Später senden" (Mail in Queue mit Zeitstempel)
- "Erinnere mich" (Snooze — Mail verschwindet und taucht zu Zeitpunkt wieder auf)
4.2 Templates
- Häufige Antworten als wiederverwendbare Templates
- Variablen-Platzhalter:
{{name}},{{datum}}
4.3 Mail-Regeln
- Automatische Label-Zuweisung basierend auf Absender/Betreff
- Auto-Forward, Auto-Archive
- Integration mit
automationsModul
Infrastruktur-Voraussetzungen
Sofort nötig (vor Phase 1)
- Fritz!Box Port-Forwarding: 25, 587, 465 → Mac Mini (192.168.178.131)
- Stalwart JMAP-Port (8080 intern) im Docker-Netzwerk verfügbar machen
- Port 3042 in
PORT_SCHEMA.mdreservieren MANA_MAIL_URLzu.env.developmenthinzufügen
Docker-Compose Erweiterung
mana-mail:
build: ./services/mana-mail
container_name: mana-mail-service
ports:
- "3042:3042"
environment:
PORT: 3042
DATABASE_URL: postgresql://mana:${DB_PASSWORD}@postgres:5432/mana_platform
MANA_AUTH_URL: http://mana-auth:3001
MANA_SERVICE_KEY: ${MANA_SERVICE_KEY}
STALWART_JMAP_URL: http://mana-mail:8080
STALWART_ADMIN_USER: admin
STALWART_ADMIN_PASSWORD: ${STALWART_ADMIN_PASSWORD}
CORS_ORIGINS: http://localhost:5173,https://mana.how
depends_on:
- postgres
- mana-mail # Stalwart container
networks:
- mana-network
Cross-Module-Integration
| Aktion | Source | Target | Mechanismus |
|---|---|---|---|
| Mail → Aufgabe | todo | manaLinks + tasksStore.createTask() |
|
| Mail → Termin | calendar | manaLinks + Datums-Extraktion |
|
| Mail → Kontakt | contacts | Absender-Matching / Auto-Create | |
| Mail → Notiz | notes | Inhalt kopieren + manaLinks |
|
| Mail → Datei | storage | Attachment in MinIO ablegen | |
| Mail → Rechnung | finance | KI-Erkennung "Rechnung" | |
| Kontakt → Mail | contacts | "E-Mail senden" Button | |
| Todo → Mail | todo | "Per Mail teilen" |
Implementierungs-Reihenfolge
- Infra: Port-Forwarding, JMAP-Port, Port reservieren
- Service:
mana-mailscaffolden (Hono/Bun, Config, Health) - JMAP-Client: Stalwart-Verbindung, Thread/Email-Queries
- Account-Provisioning: on-user-created Hook in mana-auth
- API: Thread-Liste, Thread-Detail, Send, Draft
- Frontend: Modul-Gerüst (types, collections, queries, stores)
- ListView: Inbox-UI mit Thread-Liste
- ThreadView: Detail-Ansicht mit Messages
- ComposeView: Schreiben/Reply mit Kontakte-Autocomplete
- SSE: Live-Updates für neue Mails
- KI Phase 2: Zusammenfassungen, Smart Reply, Kategorisierung
- Multi-Account Phase 3: Gmail/Outlook OAuth