Zum Inhalt

E-Mail

Architektur

Internet → Traefik → hosting-postfix (SMTP)
                   → hosting-dovecot (IMAP + OAuth2)
                   → hosting-rspamd (Spam-Filter)

Platform API → IMAP Proxy (aioimaplib) → Dovecot
             → SMTP Service (aiosmtplib) → Postfix
             → Mail-Watcher (IMAP IDLE) → WebSocket Events
Komponente Image Port Beschreibung
Postfix alpine:3.19 25, 465, 587 SMTP (MX, SMTPS, Submission)
Dovecot xynap/dovecot-oauth2:2.3.21 993 IMAPS + OAuth2 (Custom Build)
Rspamd - 11332 Milter fuer Postfix
Platform API - 8099 Webmail-Proxy, Watcher, Signaturen

Dovecot Docker-Image

Custom-Build aus Source (Alpine-Paket hat keinen OAuth2 passdb-Treiber):

  • Dockerfile: /etc/xynap/hosting/dovecot/Dockerfile
  • Multi-Stage: Builder (compile) → Runtime (alpine:3.19 + openssl + libcurl + mariadb-connector-c)
  • Auth-Mechanisms: plain login oauthbearer xoauth2 (Passwort + OAuth2 parallel)

Authentifizierung

Alle Mail-Endpoints akzeptieren zwei Auth-Methoden:

Methode Header Scope
Service-Token x-service-token: <token> Platform (alle Domains/Accounts sichtbar)
Bearer Token Authorization: Bearer <jwt> Tenant-Scope (nur eigene Daten)

Service-Token Beispiel:

curl -H "x-service-token: $TOKEN" https://platform.xynap.tech/api/v1/mail/domains

Mail-Administration API

Domains

# Domains auflisten
GET /api/v1/mail/domains

# Domain erstellen (erstellt auch DNS-Records: MX, SPF, DMARC, DKIM)
POST /api/v1/mail/domains
{"domain": "example.com", "customer_id": 1}

Mailboxen

# Mailboxen einer Domain
GET /api/v1/mail/domains/{id}/accounts

# Mailbox erstellen
POST /api/v1/mail/domains/{id}/accounts
{"local_part": "info", "password": "...", "quota_mb": 1024}

# Passwort aendern
PUT /api/v1/mail/accounts/{id}
{"password": "neues-passwort"}

# Mailbox sperren/entsperren
POST /api/v1/mail/accounts/{id}/suspend
POST /api/v1/mail/accounts/{id}/unsuspend

Aliase

# Aliase einer Domain
GET /api/v1/mail/domains/{id}/aliases

# Alias erstellen
POST /api/v1/mail/domains/{id}/aliases
{"source": "sales", "destination": "info@example.com"}

Webmail API

Nativer IMAP/SMTP-Proxy — ersetzt Roundcube.

Ordner & Nachrichten

# Ordnerliste mit Unread-Count
GET /api/v1/mail/webmail/folders

# Nachrichten eines Ordners (paginiert)
GET /api/v1/mail/webmail/messages/{folder}?page=1&per_page=50

# Einzelne Nachricht (HTML Body)
GET /api/v1/mail/webmail/messages/{folder}/{uid}

# Nachricht loeschen (in Trash verschieben)
DELETE /api/v1/mail/webmail/messages/{folder}/{uid}

# Nachricht verschieben
POST /api/v1/mail/webmail/messages/{folder}/{uid}/move
{"target": "Archive"}

# Flags setzen (Gelesen, Stern, etc.)
POST /api/v1/mail/webmail/messages/{folder}/{uid}/flags
{"flags": ["\\Seen", "\\Flagged"]}

Senden

# Mail senden (HTML + Plain-Text)
POST /api/v1/mail/webmail/send
{
  "to": ["empfaenger@example.com"],
  "cc": [],
  "bcc": [],
  "subject": "Betreff",
  "html": "<p>HTML Body</p>",
  "plain": "Plain-Text Fallback",
  "in_reply_to": "<message-id>"  # optional, fuer Reply
}

Signaturen

# Signaturen auflisten
GET /api/v1/mail/signatures/

# Signatur erstellen
POST /api/v1/mail/signatures/
{
  "name": "Standard",
  "html": "<p>Mit freundlichen Gruessen</p>",
  "is_default": true,
  "rules": [
    {"match_type": "domain", "match_value": "example.com"}
  ]
}

# Passende Signatur fuer Empfaenger
GET /api/v1/mail/signatures/match?to=user@example.com

Kontakte-Autocomplete

# Kontakt-Suche (CardDAV + letzte Empfaenger)
GET /api/v1/mail/webmail/contacts/search?q=max

Echtzeit-Events (Mail-Watcher)

Der Mail-Watcher ueberwacht Postfaecher via IMAP IDLE und sendet Events ueber WebSocket.

Methoden (Auto-Detect pro Server)

Methode Beschreibung Prioritaet
IMAP IDLE Server-Push bei neuen Nachrichten 1 (primaer)
Dovecot Push HTTP-Webhook vom Mail-Server 2
Polling STATUS-Check alle 60 Sekunden 3 (Fallback)

WebSocket Events

// Neue Nachricht
{"type": "mail.new_message", "data": {
  "folder": "INBOX",
  "message": {"uid": 42, "from_addr": "sender@example.com", "subject": "..."}
}}

// Ordner-Update (Unread-Count)
{"type": "mail.folder_update", "data": {
  "folder": "INBOX",
  "unseen": 5,
  "exists": 42
}}

Watcher-Status

GET /api/v1/mail/webmail/watcher/status

Mail OAuth2 (XOAUTH2 / OAUTHBEARER)

Platform API fungiert als OAuth2 Authorization Server fuer den Mail-Stack. Externe IMAP/SMTP-Clients (Thunderbird, Apple Mail, etc.) koennen sich per OAuth2-Token authentifizieren.

Flow

1. User Login      → POST /api/v1/auth/login           → JWT (type=access)
2. Mail-Token      → POST /api/v1/auth/mail-token       → JWT (type=mail_access, 60 Min)
3. IMAP Connect    → AUTHENTICATE XOAUTH2 <token>       → Dovecot
4. Introspection   → Dovecot POST /introspect            → Platform API validiert Token
5. Login OK        → IMAP Session aktiv

Token anfordern

# Mail-OAuth2-Token fuer eingeloggten User (erfordert aktives Mail-Konto)
POST /api/v1/auth/mail-token
Authorization: Bearer <access_token>

# Optional: spezifisches Mail-Konto angeben
POST /api/v1/auth/mail-token
{"email": "info@example.com"}

Response:

{
  "access_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "email": "user@example.com"
}

Token Introspection (RFC 7662)

Intern — Dovecot ruft diesen Endpoint auf um Tokens zu validieren:

POST /api/v1/auth/token/introspect
Content-Type: application/x-www-form-urlencoded

token=eyJ...

Response:

{
  "active": true,
  "username": "user@example.com",
  "scope": "imap smtp",
  "token_type": "Bearer",
  "exp": 1773178446
}

Dovecot-Konfiguration

# auth_mechanisms (parallel: Passwort + OAuth2)
auth_mechanisms = plain login oauthbearer xoauth2

# OAuth2 passdb (Token Introspection via Platform API)
passdb {
  driver = oauth2
  mechanisms = xoauth2 oauthbearer
  args = /etc/dovecot/auth-oauth2.conf.ext
}

auth-oauth2.conf.ext:

introspection_url = http://platform-api:8099/api/v1/auth/token/introspect
introspection_mode = post
username_attribute = username
active_attribute = active
active_value = true

IMAP-Client Beispiel (XOAUTH2)

import base64, imaplib

user = "user@example.com"
token = "<mail_access JWT>"

# XOAUTH2 SASL String
auth_string = f"user={user}\x01auth=Bearer {token}\x01\x01"
auth_b64 = base64.b64encode(auth_string.encode()).decode()

imap = imaplib.IMAP4_SSL("mail.xynap.tech", 993)
imap.authenticate("XOAUTH2", lambda x: auth_string.encode())
imap.select("INBOX")

DNS-Records (automatisch)

Bei Domain-Erstellung werden folgende Records angelegt:

Typ Name Wert
MX @ nexus.isn-systems.com (Prio 10)
TXT @ v=spf1 ip4:46.4.96.105 -all
TXT _dmarc v=DMARC1; p=quarantine; rua=...
TXT mail._domainkey DKIM Public Key
CNAME autoconfig nexus.isn-systems.com

Mail-Storage

/var/lib/xynap/mail/
  ├── vhosts/{domain}/{user}/   # Maildir
  ├── dkim/                      # DKIM-Keys
  └── ssl/                       # SSL-Zertifikate

vmail User: UID/GID 5000