E-Mails aus deinen Apps. Über deine SMTP-Server. Unter deiner Kontrolle.
Relanto bündelt den Versand aus all deinen Anwendungen hinter einer HTTP-API: getrennte SMTP-Konfigurationen, Token mit Scopes, Job-Queue mit Retries und ein Admin-Panel, das jeden Versand nachvollziehbar macht.
- Kein SaaS, kein Vendor-Lock-in – ein Container auf deinem Server reicht
- Jede Anwendung erhält eigene Credentials und SMTP-Konfiguration
- Versandstatus und API-Fehler einsehbar, ohne Container-Logs zu durchsuchen
Problem und Ansatz. Sobald mehrere Anwendungen E-Mails versenden, wiederholen sich SMTP-Konfiguration, Zugangsschutz, Fehlerbehandlung und Betriebslogik. Relanto bündelt diese Aufgaben hinter einer selbst gehosteten HTTP-API: Anwendungen authentifizieren sich mit eigenen, begrenzbaren Zugängen und übergeben den Versand zentral an Relanto (Architekturüberblick).
Architektur und Trade-off. Die React-Router-Anwendung trägt Admin-Oberfläche und HTTP-Schicht; das Backend kapselt Domänenlogik, SQLite-Persistenz und einen Worker für Queue und Aufbewahrung. SQLite und der Worker im Webserver-Prozess halten die Bereitstellung kompakt, setzen aber bewusst auf genau eine laufende Instanz statt horizontalem Multi-Instanz-Betrieb mit einer gemeinsamen Datenbankdatei (Architekturüberblick, Container-Build).
Sicherheit. SMTP-Passwörter werden mit AES-256-GCM verschlüsselt gespeichert, Client-Secrets nur als SHA-256-Hash; sensible Fehlermeldungen werden bereinigt (Sicherheitsmodul). Vor SMTP-Verbindungen prüft Relanto jede aufgelöste Zieladresse und blockiert nicht öffentlich routbare Netze, um SSRF zu begrenzen (Implementierung, SSRF-Tests). Tests sichern außerdem ab, dass strukturierte Betriebslogs weder Nachrichteninhalte noch Zugangsdaten enthalten (Logging-Tests). Das Laufzeit-Image startet als unprivilegierter Benutzer und besitzt einen Healthcheck (Dockerfile).
Teststrategie. Paketbezogene Vitest-Suiten und verbindliche Coverage-Schwellen prüfen Frontend und Backend (Teststrategie). Ein Vertragstest hält die generierte OpenAPI-Spezifikation synchron mit den registrierten Routen und prüft die Abdeckung in beide Richtungen (OpenAPI-Test). Die CI ergänzt diese Prüfungen um Produktionsabhängigkeits-Audit, Container-Build, Schwachstellen-Scan und Smoke-Tests (CI-Workflow).
Mein Beitrag. Ich habe Relanto konzipiert und umgesetzt – vom Domänenmodell und API-Vertrag über Authentifizierung, Queue-Verarbeitung und Admin-Oberfläche bis zu den Betriebs- und Testpfaden. Die technischen Entscheidungen und ihre Grenzen habe ich in der Entwicklerdokumentation nachvollziehbar festgehalten.
Token & Scopes – Zugriff, der zur Anwendung passt
Jede App bekommt genau die Rechte, die sie braucht. Kompromittierte Tokens rotierst du mit einem Klick.
Queue & Retries – kein Mailausfall durch SMTP-Wackler
SMTP-Wackler kosten keine Mails: Jobs werden eingereiht, wiederholt und bleiben nachvollziehbar.
Admin-Panel – Sichtbarkeit ohne Logdateien
Sieh, was rausging, was hängt und warum ein Request fehlschlug – ohne Container-Logs zu greppen.
Self-hosted – dein Server, deine Daten
Ein Container, eine SQLite-Datei, dein Server. Backup ist ein Skript, kein Support-Ticket.
- Container starten – Quellcode klonen, Image lokal bauen, Pflicht-Env-Vars setzen und ein Volume für die SQLite-Datenbank bereitstellen.
- App, SMTP-Config und Token anlegen – im Admin-Panel Anwendungen registrieren, SMTP-Zugänge hinterlegen und API-Tokens mit den nötigen Scopes erstellen.
- E-Mails versenden – Token über
POST /api/v1/tokenholen, dannPOST /api/v1/sendaufrufen.
git clone https://github.com/sebastian-software/relanto.git
cd relanto
podman build \
--build-arg RELANTO_GIT_SHORT_SHA=$(git rev-parse --short HEAD) \
-t relanto:local .
podman run -d -p 3000:3000 \
-e APP_SESSION_SECRET=$(openssl rand -hex 32) \
-e MAILER_SECRET_KEY=$(openssl rand -hex 32) \
-e MAILER_DB_PATH=/var/lib/relanto/mailer.sqlite \
-e POCKET_ID_ISSUER=https://pocket-id.example.com \
-e POCKET_ID_CLIENT_ID=mailer \
-e POCKET_ID_REDIRECT_URI=https://mailer.example.com/auth/callback \
-v relanto-data:/var/lib/relanto:U \
relanto:localFür Docker podman durch docker ersetzen und die Volume-Berechtigungen wie unter Betrieb und Datensicherung beschrieben setzen. Der Standard-Build aus dem öffentlichen Quellcode benötigt keine npm-, GitHub- oder Registry-Zugangsdaten.
Das GHCR-Paket ist derzeit privat; ein anonymer Pull wird nicht zugesichert. Das künftig vorgebaute Image unterstützt ausschließlich linux/amd64. Der Build aus dem Quellcode bleibt der Fallback, ist aber keine Zusage für ARM-Unterstützung. Technische Details stehen in der Frontend-Dokumentation, der Freigabeprozess im GHCR-Runbook.
Erst PR 2 wird den anonymen GHCR-Pull dokumentieren: nach dem Merge des gehärteten Workflows, einem realen Release mit bestandenem Archiv-, Digest- und Scan-Gate („keine erkannten Secrets unter den aktiven Trivy-Regeln“), korrigierter Paketverknüpfung und Workflow-Berechtigung, dem irreversiblen manuellen Wechsel auf Public in den GitHub Package Settings sowie einem erfolgreichen anonymen Pull-Nachweis.
Für Produktivbetrieb mit systemd-Integration: deploy/quadlet/relanto.container.example
Relanto eignet sich für Teams, die:
- E-Mails aus eigenen Anwendungen oder internen Systemen versenden wollen
- mehrere SMTP-Konfigurationen sauber trennen müssen
- Versandzugriffe per Token und Scope-Modell absichern wollen
- den Versandstatus einzelner Jobs nachvollziehen müssen
- eine einfache, selbst betriebene Lösung ohne externe SaaS-Abhängigkeit suchen
- API-basierter Versand von HTML- und Text-E-Mails
- SMTP-Konfigurationen pro Anwendung
- getrennte Rollen für System-Admins und Application-Admins
- Token-Modell mit klaren Scopes für Versand, Status und Verwaltung
- Job-Queue mit Retry-Logik und Statusverfolgung
- Admin-Oberfläche für Konfiguration, Tokens und Betriebsdaten
- Backup- und Restore-Pfad für die persistenten Daten
Relanto ist für einen einfachen, kontrollierten Single-Instance-Betrieb ausgelegt.
- Betrieb als Container
- Persistente SQLite-Datenbank auf einem Volume
- OIDC-Login für den System-Admin über Pocket ID
- Interner Worker für Queue-Verarbeitung im selben Prozess wie der Webserver
Wichtig:
- Die SQLite-Daten müssen auf persistentem Storage liegen.
- Mehrere parallele Instanzen mit derselben lokalen SQLite-Datei sind nicht vorgesehen.
- Der Worker startet beim Prozessstart automatisch und verarbeitet wartende Jobs ohne zusätzlichen Trigger.
- Repository klonen und das lokale Image
relanto:localwie im Schnellstart bauen - Persistentes Volume für die Datenbank bereitstellen, zum Beispiel unter
/var/lib/relanto - Pflichtvariablen setzen:
APP_SESSION_SECRETMAILER_DB_PATHMAILER_SECRET_KEYPOCKET_ID_ISSUERPOCKET_ID_CLIENT_IDPOCKET_ID_REDIRECT_URI
- Optional:
API_FAILURE_RETENTION_DAYSsetzen, um die Aufbewahrungsdauer für API-Fehlereinträge im Admin-Panel zu steuern (Default 30 Tage). - Relanto als einzelne Instanz starten
- System-Admin per OIDC anmelden und Anwendungen, SMTP-Konfigurationen und Tokens anlegen
- Im Admin-Bereich steht das Panel „API-Fehler" zur Verfügung. Es zeigt 4xx-Antworten der API mit Ursache, Pfad und Client-Bezug, ohne Request-Bodies oder Geheimnisse zu speichern.
Die vollständige Referenz aller Umgebungsvariablen (Pflicht/Optional, Default, Zweck) steht in packages/frontend/README.md.
Die API verwendet client_id und client_secret als dauerhafte Client-Credentials.
Ein integrierender Client muss:
client_idundclient_secretsicher serverseitig speichern.- Über
POST /api/v1/tokenein kurzlebiges JWT Access Token anfordern. - Das JWT bei API-Requests als
Authorization: Bearer <access_token>mitsenden. - Bei Ablauf oder
401ein neues JWT anfordern und den Request einmalig erneut versuchen. - Kein
client_secretin Browser- oder Mobile-App-Code einbetten.
Das JWT ist 15 Minuten gültig. Es gibt keine Refresh-Tokens. Der Login in die Admin-Oberfläche bleibt davon getrennt und läuft weiter über Pocket ID.
Anwendungstoken können je nach Scope für unterschiedliche Integrationen verwendet werden:
send: E-Mails überPOST /api/v1/sendversenden oder einreihenreadStatus: Job-Status überGET /api/v1/jobsundGET /api/v1/jobs/:jobIdlesen sowie Delivery-Status überGET /api/v1/jobs/:jobId/delivery-statusoderPOST /api/v1/jobs/delivery-statuspollenreadConfig: die aktuelle SMTP-Konfiguration der Anwendung überGET /api/v1/configlesenvalidate: SMTP-Verbindung validierenmanageTokens: Tokens auflisten, rotieren, widerrufen und deren Scopes aktualisieren (nur für Application-Admin-Tokens)manageApplications: Anwendungen, SMTP-Konfigurationen und Job-Queue-Operationen verwalten (nur für Application-Admin-Tokens)
Die vollständige Scope-Referenz mit Endpunkten steht in LLMs.txt.
GET /api/v1/config ist nur für Anwendungstoken gedacht. Die Antwort enthält technische SMTP-Konfigurationsdaten wie Host, Port, TLS-Einstellungen, Timeouts, Absenderadresse, Statusfelder und Metadaten, aber keinen SMTP-Benutzernamen und keine Secrets.
Die persistenten Relanto-Daten liegen im named volume relanto-data.
Der Container läuft aus Sicherheitsgründen als unprivilegierter Benutzer node (uid/gid 1000) und nicht als root. Das Datenvolume muss deshalb diesem Benutzer gehören, sonst kann der Prozess die SQLite-Datenbank nicht schreiben. Bei einem frisch angelegten, leeren Volume übernimmt Podman die Verzeichnis-Ownership aus dem Image automatisch. Das Quadlet-Beispiel setzt zusätzlich die Mount-Option :U (Volume=relanto-data:/var/lib/relanto:U), die das Volume bei jedem Start rekursiv auf uid/gid 1000 chownt und damit auch ein bereits vorhandenes, root-owned Volume beschreibbar macht. Ohne Quadlet lässt sich das Volume alternativ einmalig manuell angleichen, zum Beispiel mit podman unshare chown -R 1000:1000 auf dem Volume-Pfad.
Für Backup und Restore gibt es Host-Skripte:
Das Backup erzeugt einen konsistenten Online-Snapshot der SQLite-Datenbank über sqlite3 ".backup". Dadurch bleibt das Backup auch dann konsistent, wenn Relanto währenddessen weiter schreibt – die laufende Instanz muss für das Backup nicht gestoppt werden. Der Snapshot ist WAL-sicher: Er liefert eine eigenständige Datenbankdatei ohne begleitende -wal/-shm-Dateien. Das Helper-Image benötigt sqlite3; das voreingestellte Alpine-Image installiert es beim Backup automatisch nach (dafür ist beim Backup Netzwerkzugriff nötig).
Beim Restore muss die laufende Relanto-Instanz weiterhin vorher gestoppt werden. Der bestehende Volume-Inhalt – inklusive eventueller alter -wal/-shm-Dateien – wird vor dem Entpacken gelöscht, sodass die wiederhergestellte Datenbank ein sauberer, konsistenter Stand ist.
Relanto stellt zwei Endpunkte für Betrieb und Verfügbarkeitsüberwachung bereit:
GET /health– unauthentifizierte Verfügbarkeitsprüfung für Load Balancer und Uptime-Monitoring. Antwortet mit200, wenn Datenbank und Worker-Loop gesund sind, sonst mit503.GET /metrics– detaillierte Betriebsdaten (Queue-Status, Versand- und Fehlerraten, SMTP-Konfigurationsstatus, Worker-Tick, Speicherverbrauch). Erfordert einen Bearer-Token aus der UmgebungsvariableMETRICS_TOKEN; ohne gesetzten Token antwortet der Endpunkt mit404.
Eine fertige Uptime-Kuma-Konfiguration mit empfohlenen Monitors und Schwellwerten steht in docs/health-and-metrics-relanto.md.
- Technische und Deployment-Details: packages/frontend/README.md
- Podman/Quadlet-Beispiel: relanto.container.example
- Deployment-Pipeline (Container-Build, Quadlet, GHCR und Releases): packages/frontend/README.md#deployment
- Changelogs: Frontend · Backend
- Mitwirken: CONTRIBUTING.md
- Sicherheitslücken vertraulich melden: SECURITY.md
Der Quellcode von Relanto ist unter der MIT-Lizenz veröffentlicht.
Bearbeite README.md.src und führe mise run readme:write aus.
Einrichtung und Prüfungen beschreiben den Ablauf.
TypeScript, React & Rust consulting
Experts in Agentic Software Development
Work with us · More open source
Copyright © 2026 Sebastian Software GmbH