Self-hosted, multi-user e-mail client — with calendar, contacts, notes, a native Android app, and real-time sync. Your own alternative to Synology MailPlus.
A single Docker container that is not a mail server — it's a client for the mailboxes you already have. It connects to any IMAP / POP3 account to read and to SMTP to send, and adds a calendar, contacts, notes and tasks on top. A polished web UI and a native Android app share one JSON API — and stay in sync live across devices.
Part of the Self family (SelfAuthenticator, SelfArchiver, SelfDashboard …) — same design system, same deploy style (GHCR → Unraid).
TL;DR — IMAP/SMTP mail client · calendar/contacts/notes · web + native Android · live cross-device sync · 3 notification options (FCM / ntfy / background) · 2FA · single container · no external DB.
Features · Blocking senders · Architecture · Notifications · Live sync · Quick start · Unraid · Configuration · Android app · API · Security · Tech stack · Development · Roadmap
- Multiple IMAP / POP3 / SMTP accounts per user
- Thunderbird-style stacked accounts with a per-account folder tree and unread counters
- Read HTML mail (hardened), download/open attachments, SPF/DKIM/DMARC spoofing check
- Compose / reply / forward with a per-account From picker and signatures; drafts, read/delivery receipts
- Star, mark read/unread, delete, move, cross-account transfer
- Multi-select (long-press → select → bulk move / read / unread / star / delete)
- Filter rules (move/star/mark/delete — applied automatically on every background sync) and server-side mailbox migration
- Block sender in one click → mail goes to trash (existing + future); a blocked-senders list lets you undo any block, and the trash auto-empties after 7 days (safe 7-day undo window)
- Auto-empty spam & trash per account (off / immediately / after 7 / 30 days)
- Search with quick filters (unread · has attachment · starred), pagination, snippets
- A local SQLite cache kept warm by a background sync → the UI never waits on a slow provider
📅 Organizer
- Calendar — local events, month/agenda views, birthdays from contacts
- Contacts — rich address book (phones, address, organisation, website, birthday)
- Notes & Tasks
- CalDAV / CardDAV pull from external servers (Nextcloud, Synology …) + subscribable ICS / vCard export feeds
🔔 Notifications — you choose
- FCM (Google Push) — instant, no persistent notification (like Gmail/Synology)
- ntfy — self-hosted push, no Google
- Background check — ~1 min, zero setup
- Per account, per folder — pick exactly which mailboxes notify you
- Configurable in both the web UI and the app — one shared setting
🔄 Live sync (SSE)
- Read/delete/move on your phone → an open web tab updates instantly (and vice-versa)
🔐 Security & platform
- Multi-user with admin/user roles; admin can pre-configure accounts for users
- 2FA / TOTP login with backup codes; biometric app-lock on Android
- Account credentials Fernet-encrypted at rest
- i18n DE/EN, light/dark themes + custom accent colours
- Single container — FastAPI + SQLite, no external database
Block a sender — open the mail → click ⋯ (top right) → 🚫 Block sender → confirm. All mail from that sender (existing + future, inbox + spam) is moved to trash and marked read. The trash auto-empties after 7 days (a safe undo window).
Undo a block — click your 👤 user menu (top right) → 🔀 Rules → the 🚫 Blocked senders box at the top lists every block; hit ↩ Unblock to let future mail through again. To recover a specific message, open it in the Trash folder and move it back to the inbox (within 7 days).
Auto-empty timings (spam & trash: off / immediately / after 7 / 30 days) live under 👤 → ✎ Edit account.
┌──────────────┐ ┌───────────────┐
│ Web UI │ │ Android app │
│ React + Vite │ │ Kotlin/Compose│
└──────┬───────┘ └──────┬────────┘
└───── JSON API + SSE ─────┘
/api/v1 · JWT (Bearer)
│
┌────────▼─────────┐
│ FastAPI core │ SQLite (cache + config)
│ background sync │ Fernet-encrypted secrets
│ + event bus │ in-memory pub/sub → live sync
└────────┬─────────┘
┌──────────┬──────┴───────┬──────────────┬───────────┐
IMAP / POP3 SMTP CalDAV / CardDAV FCM ntfy
(read mail) (send mail) (sync cal & contacts) (Google push) (self-hosted push)
Pick a method in Settings → E-Mail notifications (web user menu → 🔔 / app Settings). The server can drive all three in parallel — e.g. FCM on your main phone, ntfy on a de-Googled one.
| Method | Latency | Persistent notification | Needs |
|---|---|---|---|
| FCM (Google Push) | instant | none | Firebase project + Google Play Services |
| ntfy | instant | none (the ntfy app holds one connection for all apps) | ntfy container + ntfy app |
| Background check | ~1 min | yes (Android requirement for a foreground service) | nothing |
Per-folder: under Choose folders you pick, per account, which folders trigger a push.
Setup — FCM (Google Push)
- Create a free Firebase project at https://console.firebase.google.com.
- Add Android app with package name
com.selfmailer.viewer→ downloadgoogle-services.json(goes into the app build). - Project settings → Service accounts → Generate new private key → download the JSON.
- Put that JSON on the server as
…/selfmailer/data/fcm-service-account.jsonand setSELFMAILER_FCM_CREDENTIALS=/data/fcm-service-account.json→ restart the container. - In the app: notifications on → Google Push (FCM) → pick folders → use Send test push to verify.
The push payload is intentionally minimal ("2 new e-mails"); the app fetches the actual content from your server, so Google never sees mail bodies.
Setup — ntfy (self-hosted)
- Run an ntfy container (e.g.
binwiederhier/ntfy serve, host port8095→ container80,NTFY_BASE_URL=http://<host>:8095). - Install the ntfy app, add your server, subscribe to the topic SelfMailer shows you.
- In SelfMailer: notifications on → ntfy → enter the URL → Save & enable → pick folders.
Every open client (web tab, app in the foreground) holds a thin SSE connection (/api/v1/events/stream). Whenever anyone performs an action — or new mail arrives — the server emits a tiny event and the other clients refresh the affected folder. No polling storm, no Google, near-instant.
cp .env.example .env
# generate a secret and put it in .env (SELFMAILER_SECRET):
python -c "import secrets; print(secrets.token_hex(32))"
docker compose up -dOpen http://<host>:8090 → the first-run setup creates the admin account → then add your mail accounts.
Add the template from
https://raw.githubusercontent.com/s3lfcod3r/selfmailer/main/unraid/selfmailer.xml
or import it under Docker → Add Container → Template. Set the Master Secret, leave the rest on defaults.
Master Secret: never change
SELFMAILER_SECRETafter first run — it encrypts the stored mailbox passwords. Changing it makes them unreadable.Permissions: the container runs non-root (uid
99/ gid100=nobody:users). If a data file is owned by root, fix it once:chown -R 99:100 /mnt/user/appdata/selfmailer(or Tools → Docker Safe New Permissions).
| Variable | Required | Default | Purpose |
|---|---|---|---|
SELFMAILER_SECRET |
✅ | – | JWT signing and at-rest encryption of mailbox passwords (≥ 32 chars). Do not change after setup. |
SELFMAILER_DB_PATH |
– | /data/selfmailer.db |
SQLite path |
SELFMAILER_FCM_CREDENTIALS |
– | /data/fcm-service-account.json |
Path to the Firebase service-account JSON for Google push. Empty/missing = FCM off. |
SELFMAILER_TRANSLATE_URL |
– | – | URL of a self-hosted LibreTranslate instance for in-app e-mail translation (e.g. http://192.168.1.10:5000). Empty = translation off (button hidden). |
SELFMAILER_TRANSLATE_API_KEY |
– | – | Optional API key, only if your LibreTranslate instance requires one. |
SELFMAILER_ADMIN_TOKEN |
– | – | If set, first-run admin setup requires this token |
SELFMAILER_BASE_URL |
– | – | Public base URL (e.g. for feed links) |
SELFMAILER_SYNC_INTERVAL |
– | 300 |
Seconds between background mail syncs (lower = faster push & sync) |
SELFMAILER_DAV_SYNC_INTERVAL |
– | 120 |
Seconds between calendar/contact (DAV) syncs — separate from mail, min 30 |
SELFMAILER_SYNC_DISABLE |
– | 0 |
Turn off the background sync |
SELFMAILER_IMAP_TIMEOUT |
– | 15 |
IMAP socket timeout (seconds) |
SELFMAILER_DAV_BLOCK_PRIVATE |
– | false |
Block private/LAN targets for DAV pull (SSRF strict mode) |
SELFMAILER_JWT_ALGORITHM |
– | HS256 |
HS256 / HS384 / HS512 |
SELFMAILER_BACKUP_ENABLED |
– | true |
Nightly automatic DB backup (~03:00) |
SELFMAILER_BACKUP_KEEP |
– | 7 |
How many backups to keep (older ones are rotated out; 0 = keep all) |
The scheduler takes a consistent nightly snapshot of the SQLite database
(sqlite3 online-backup API, safe during writes) into data/backups/:
data/backups/selfmailer-YYYYMMDD-HHMMSS.db
Only the last SELFMAILER_BACKUP_KEEP snapshots are kept. The folder is
git-ignored. To restore a backup, stop the container, replace the live DB
with a snapshot, then start again:
docker stop selfmailer
cp data/backups/selfmailer-20260101-030000.db data/selfmailer.db
# remove stale WAL/SHM sidecars so the restored file is authoritative
rm -f data/selfmailer.db-wal data/selfmailer.db-shm
docker start selfmailerA real native client (android/SelfMailer.apk) — not a web-view wrapper. Built like Synology MailPlus:
- Hamburger drawer → account switcher + special folders + sub-folders, with unread badges
- Bottom navigation: Mail · Calendar · Notes
- HTML mail, attachments, multi-select with a bulk action bar, search filters, Synology-style compose with From picker
- Biometric app-lock (face / fingerprint / device PIN), unread accent markers
- Notifications: FCM / ntfy / background — your choice, per folder
- Talks to the same
/api/v1as the web UI; server URL entered on first launch
| Group | Endpoints (excerpt) |
|---|---|
| Auth | auth/status, auth/setup, auth/login, auth/login/totp, auth/me, auth/totp/* |
| Accounts | accounts (CRUD), accounts/{id}/test |
mail/{id}/folders[/counts], mail/{id}/messages, …/{uid}, …/flags, …/move, …/send, …/transfer, …/batch-* |
|
| Organizer | calendar/events, contacts, notes, tasks |
| DAV / Feeds | dav/accounts, feeds/token, calendar/export.ics, contacts/export.vcf |
| Push | push (ntfy), push/folders (per-account folders), push/device (FCM tokens), push/test |
| Live | events/stream (Server-Sent Events) |
| Dashboard | dashboard/summary (bundled unseen counts) |
- Login passwords Argon2-hashed; mailbox & DAV passwords Fernet-encrypted at rest (key from
SELFMAILER_SECRET) - JWT pinned to
HS256/384/512; the 2FA intermediate token grants no access - Rate limiting on login/2FA/setup; defensive response headers; anti-enumeration dummy hash
- SSRF guard on DAV pull (loopback/link-local/cloud-metadata always blocked)
- Android:
allowBackup=false, JWT in the Android Keystore, hardened mail-HTML WebView, FileProvider with path-traversal check - Deliberate LAN trade-offs (documented): cleartext HTTP allowed (TLS terminated externally),
SELFMAILER_SECRETdoubles as the encryption seed. Reviewed with the ECC security reviewer.
| Part | Tech |
|---|---|
| Backend | FastAPI · SQLModel · SQLite (WAL) · httpx · aiosmtplib · imap-tools · PyJWT |
| Web | React · Vite · TypeScript · EventSource (SSE) |
| App | Kotlin · Jetpack Compose · OkHttp · WorkManager · Firebase Messaging · BiometricPrompt |
| Push | FCM (Google) · ntfy (self-hosted) |
| Deploy | Docker (multi-stage, non-root) · GHCR · Unraid |
# Backend
cd backend && pip install -r requirements.txt
export SELFMAILER_SECRET=$(python -c "import secrets; print(secrets.token_hex(32))")
uvicorn app.main:app --reload --port 8090
# Web (second terminal)
cd frontend && npm install && npm run dev # http://localhost:5173, proxies /api → :8090Interactive API docs: http://localhost:8090/docs
Run the tests: cd backend && pip install -r requirements.txt -r requirements-dev.txt && pytest --cov=app
(pytest + TestClient against a temp SQLite — currently ~48 % coverage; the IMAP/SMTP/DAV paths need mocking to push higher.)
- Mail (IMAP/POP3/SMTP) · stacked accounts · folder tree + unread counts
- Calendar · Contacts · Notes · Tasks · CalDAV/CardDAV + ICS/vCard feeds
- 2FA / TOTP · filter rules · cross-account transfer · multi-select · search filters
- Native Android app (Synology-style, biometric lock)
- Notifications: FCM + ntfy + background, per account & folder
- Live sync (SSE) between web clients and the Android app (foreground)
- Calendar month grid in the app
- HTTPS reverse-proxy guide
- OAuth for Gmail / Outlook
- Tests toward 80 % coverage
Ein einzelner Docker-Container, der kein Mailserver ist — sondern ein Client für die Postfächer, die du schon hast. Er verbindet sich mit jedem IMAP-/POP3-Konto zum Lesen und mit SMTP zum Senden und bringt Kalender, Kontakte, Notizen und Aufgaben mit. Eine ausgefeilte Web-Oberfläche und eine native Android-App teilen sich eine JSON-API — und bleiben live über Geräte hinweg synchron.
Teil der Self-Reihe (SelfAuthenticator, SelfArchiver, SelfDashboard …) — gleiches Design-System, gleicher Deploy-Stil (GHCR → Unraid).
Kurz — IMAP/SMTP-Mail-Client · Kalender/Kontakte/Notizen · Web + native Android · Live-Sync · 3 Benachrichtigungs-Wege (FCM / ntfy / Hintergrund) · 2FA · ein Container · keine externe DB.
- Mehrere IMAP-/POP3-/SMTP-Konten pro Nutzer
- Thunderbird-Ansicht: gestapelte Konten mit Ordnerbaum und Ungelesen-Zählern
- HTML-Mails lesen (gehärtet), Anhänge öffnen, SPF/DKIM/DMARC-Spoofing-Check
- Schreiben / Antworten / Weiterleiten mit Von-Konto-Auswahl und Signaturen; Entwürfe, Lese-/Empfangsbestätigung
- Stern, gelesen/ungelesen, löschen, verschieben, Konto-übergreifend übertragen
- Mehrfachauswahl (lange drücken → auswählen → Sammelaktionen Verschieben/Gelesen/Ungelesen/Stern/Löschen)
- Filterregeln (Verschieben/Stern/Gelesen/Löschen — werden bei jedem Hintergrund-Sync automatisch angewandt) und serverseitige Postfach-Migration
- Absender blockieren per Klick → Mails wandern in den Papierkorb (vorhandene + künftige); eine Blacklist zeigt alle Blockierungen und lässt sie wieder aufheben, der Papierkorb leert sich nach 7 Tagen (sicheres 7-Tage-Rückgängig-Fenster)
- Spam & Papierkorb automatisch leeren je Konto (aus / sofort / nach 7 / 30 Tagen)
- Suche mit Schnellfiltern (ungelesen · Anhang · Stern), Pagination, Vorschauen
- Lokaler SQLite-Cache, vom Hintergrund-Sync warmgehalten → die UI wartet nie auf einen langsamen Provider
📅 Organizer
- Kalender — lokale Termine, Monats-/Agenda-Ansicht, Geburtstage aus Kontakten
- Kontakte — reiches Adressbuch (Telefon, Adresse, Firma, Website, Geburtstag)
- Notizen & Aufgaben
- CalDAV-/CardDAV-Pull von externen Servern (Nextcloud, Synology …) + abonnierbare ICS-/vCard-Feeds
🔔 Benachrichtigungen — du wählst
- FCM (Google-Push) — sofort, keine Dauer-Benachrichtigung (wie Gmail/Synology)
- ntfy — self-hosted, kein Google
- Hintergrund-Prüfung — ~1 Min, null Setup
- Pro Konto, pro Ordner — wähle genau, welche Postfächer benachrichtigen
- Einstellbar in Web-UI und App — eine gemeinsame Einstellung
🔄 Live-Sync (SSE)
- Lesen/Löschen/Verschieben am Handy → ein offener Web-Tab aktualisiert sich sofort (und umgekehrt)
🔐 Sicherheit & Plattform
- Multi-User mit Admin-/Nutzer-Rollen; Admin kann Konten vorkonfigurieren
- 2FA / TOTP mit Backup-Codes; biometrische App-Sperre unter Android
- Konto-Zugangsdaten Fernet-verschlüsselt at-rest
- i18n DE/EN, helles/dunkles Theme + eigene Akzentfarben
- Ein Container — FastAPI + SQLite, keine externe Datenbank
Blockieren — Mail öffnen → oben rechts auf ⋯ → 🚫 Absender blockieren → bestätigen. Alle Mails dieses Absenders (vorhandene + künftige, Posteingang + Spam) wandern in den Papierkorb und werden als gelesen markiert. Der Papierkorb leert sich nach 7 Tagen automatisch (Rückgängig-Fenster).
Aufheben — oben rechts aufs 👤 Benutzer-Menü → 🔀 Regeln → ganz oben der Kasten 🚫 Blockierte Absender zeigt alle Blockierungen; mit ↩ Aufheben kommen künftige Mails wieder an. Eine einzelne Mail rettest du, indem du sie im Papierkorb-Ordner öffnest und zurück in den Posteingang verschiebst (solange die 7 Tage noch nicht um sind).
Die Auto-Leeren-Zeiten (Spam & Papierkorb: aus / sofort / nach 7 / 30 Tagen) findest du unter 👤 → ✎ Konto bearbeiten.
Methode in Einstellungen → E-Mail-Benachrichtigungen wählen (Web Benutzer-Menü → 🔔 / App Einstellungen). Der Server bedient alle drei parallel — z. B. FCM aufs Haupt-Handy, ntfy auf ein entgoogeltes.
| Methode | Latenz | Dauer-Benachrichtigung | Voraussetzung |
|---|---|---|---|
| FCM (Google-Push) | sofort | keine | Firebase-Projekt + Google-Play-Dienste |
| ntfy | sofort | keine (die ntfy-App hält eine Verbindung für alle Apps) | ntfy-Container + ntfy-App |
| Hintergrund-Prüfung | ~1 Min | ja (Android-Pflicht für einen Vordergrund-Dienst) | nichts |
Pro Ordner: unter Ordner auswählen legst du je Konto fest, welche Ordner pushen.
Einrichtung — FCM (Google-Push)
- Kostenloses Firebase-Projekt anlegen: https://console.firebase.google.com.
- Android-App mit Paketname
com.selfmailer.viewerhinzufügen →google-services.jsonladen (kommt in den App-Build). - Projekteinstellungen → Dienstkonten → Neuen privaten Schlüssel generieren → JSON laden.
- Diese JSON auf den Server als
…/selfmailer/data/fcm-service-account.jsonlegen undSELFMAILER_FCM_CREDENTIALS=/data/fcm-service-account.jsonsetzen → Container neu starten. - In der App: Benachrichtigungen an → Google-Push (FCM) → Ordner wählen → mit Test-Push prüfen.
Der Push-Inhalt ist bewusst minimal („2 neue E-Mails"); die Details holt die App direkt von deinem Server — Google sieht keine Mail-Inhalte.
Einrichtung — ntfy (self-hosted)
- ntfy-Container starten (z. B.
binwiederhier/ntfy serve, Host-Port8095→ Container80,NTFY_BASE_URL=http://<host>:8095). - ntfy-App installieren, Server eintragen, das in SelfMailer gezeigte Thema abonnieren.
- In SelfMailer: Benachrichtigungen an → ntfy → URL eintragen → Speichern & aktivieren → Ordner wählen.
Jeder offene Client (Web-Tab, App im Vordergrund) hält eine dünne SSE-Verbindung (/api/v1/events/stream). Macht irgendwer eine Aktion — oder trifft neue Mail ein — schickt der Server ein winziges Event und die anderen Clients frischen den betroffenen Ordner auf. Kein Polling-Sturm, kein Google, quasi sofort.
cp .env.example .env
# Secret erzeugen und in .env eintragen (SELFMAILER_SECRET):
python -c "import secrets; print(secrets.token_hex(32))"
docker compose up -dhttp://<host>:8090 öffnen → Erst-Setup legt den Admin an → danach Mailkonten hinzufügen.
Template über
https://raw.githubusercontent.com/s3lfcod3r/selfmailer/main/unraid/selfmailer.xml
hinzufügen oder unter Docker → Add Container → Template importieren. Master Secret setzen, Rest auf Standard.
Master Secret:
SELFMAILER_SECRETnach dem ersten Start nie ändern — er verschlüsselt die gespeicherten Postfach-Passwörter. Ändern macht sie unlesbar.Rechte: der Container läuft non-root (uid
99/ gid100=nobody:users). Gehört eine Datei root, einmal korrigieren:chown -R 99:100 /mnt/user/appdata/selfmailer(oder Tools → Docker Safe New Permissions).
| Variable | Pflicht | Default | Zweck |
|---|---|---|---|
SELFMAILER_SECRET |
✅ | – | JWT-Signatur und At-Rest-Verschlüsselung der Postfach-Passwörter (≥ 32 Zeichen). Nach Setup nicht ändern. |
SELFMAILER_DB_PATH |
– | /data/selfmailer.db |
SQLite-Pfad |
SELFMAILER_FCM_CREDENTIALS |
– | /data/fcm-service-account.json |
Pfad zur Firebase-Service-Account-JSON für Google-Push. Leer/fehlend = FCM aus. |
SELFMAILER_TRANSLATE_URL |
– | – | URL einer self-hosted LibreTranslate-Instanz für die Mail-Übersetzung (z. B. http://192.168.1.10:5000). Leer = Übersetzung aus (Button versteckt). |
SELFMAILER_TRANSLATE_API_KEY |
– | – | Optionaler API-Key, nur falls deine LibreTranslate-Instanz einen verlangt. |
SELFMAILER_ADMIN_TOKEN |
– | – | Wenn gesetzt, verlangt das Erst-Setup diesen Token |
SELFMAILER_BASE_URL |
– | – | Öffentliche Basis-URL (z. B. für Feed-Links) |
SELFMAILER_SYNC_INTERVAL |
– | 300 |
Sekunden zwischen Mail-Hintergrund-Syncs (kleiner = schnellerer Push & Sync) |
SELFMAILER_DAV_SYNC_INTERVAL |
– | 120 |
Sekunden zwischen Kalender-/Kontakt-Syncs (DAV) — getrennt vom Mail-Sync, min. 30 |
SELFMAILER_SYNC_DISABLE |
– | 0 |
Hintergrund-Sync abschalten |
SELFMAILER_IMAP_TIMEOUT |
– | 15 |
IMAP-Socket-Timeout (Sekunden) |
SELFMAILER_DAV_BLOCK_PRIVATE |
– | false |
Private/LAN-Ziele beim DAV-Pull blocken (SSRF-Strikt-Modus) |
SELFMAILER_JWT_ALGORITHM |
– | HS256 |
HS256 / HS384 / HS512 |
SELFMAILER_BACKUP_ENABLED |
– | true |
Nächtliches automatisches DB-Backup (~03:00) |
SELFMAILER_BACKUP_KEEP |
– | 7 |
Anzahl behaltener Backups (ältere werden rotiert; 0 = alle behalten) |
Der Scheduler zieht nachts (~03:00) ein konsistentes Backup der SQLite-DB
(via sqlite3-Online-Backup-API, sicher auch bei laufenden Schreibvorgängen)
nach data/backups/:
data/backups/selfmailer-YYYYMMDD-HHMMSS.db
Es werden nur die letzten SELFMAILER_BACKUP_KEEP Snapshots behalten. Der
Ordner ist git-ignoriert. Wiederherstellen: Container stoppen, die Live-DB
durch ein Backup ersetzen, neu starten:
docker stop selfmailer
cp data/backups/selfmailer-20260101-030000.db data/selfmailer.db
# veraltete WAL/SHM-Dateien entfernen, damit die wiederhergestellte Datei gilt
rm -f data/selfmailer.db-wal data/selfmailer.db-shm
docker start selfmailerEin echter nativer Client (android/SelfMailer.apk) — kein WebView-Wrapper. Gebaut wie Synology MailPlus:
- Hamburger-Schublade → Konto-Wechsler + Sonderordner + Unterordner, mit Ungelesen-Badges
- Untere Navigation: Mail · Kalender · Notizen
- HTML-Mail, Anhänge, Mehrfachauswahl mit Aktionsleiste, Such-Filter, Synology-Stil-Schreiben mit Von-Auswahl
- Biometrie-App-Sperre (Gesicht / Finger / Geräte-PIN), Akzent-Markierung für Ungelesene
- Benachrichtigungen: FCM / ntfy / Hintergrund — deine Wahl, pro Ordner
- Spricht dieselbe
/api/v1wie die Web-UI; Server-URL beim ersten Start
| Bereich | Endpunkte (Auszug) |
|---|---|
| Auth | auth/status, auth/setup, auth/login, auth/login/totp, auth/me, auth/totp/* |
| Konten | accounts (CRUD), accounts/{id}/test |
mail/{id}/folders[/counts], mail/{id}/messages, …/{uid}, …/flags, …/move, …/send, …/transfer, …/batch-* |
|
| Organizer | calendar/events, contacts, notes, tasks |
| DAV / Feeds | dav/accounts, feeds/token, calendar/export.ics, contacts/export.vcf |
| Push | push (ntfy), push/folders (Ordner je Konto), push/device (FCM-Tokens), push/test |
| Live | events/stream (Server-Sent Events) |
| Dashboard | dashboard/summary (gebündelte Ungelesen-Zähler) |
- Login-Passwörter Argon2-gehasht; Postfach- & DAV-Passwörter Fernet-verschlüsselt at-rest (Key aus
SELFMAILER_SECRET) - JWT auf
HS256/384/512festgenagelt; der 2FA-Zwischen-Token gewährt keinen Zugriff - Rate-Limiting auf Login/2FA/Setup; defensive Response-Header; Anti-Enumeration-Dummy-Hash
- SSRF-Schutz beim DAV-Pull (loopback/link-local/Cloud-Metadata immer geblockt)
- Android:
allowBackup=false, JWT im Android-Keystore, gehärteter Mail-HTML-WebView, FileProvider mit Path-Traversal-Check - Bewusste LAN-Trade-offs (dokumentiert): Klartext-HTTP erlaubt (TLS extern terminiert),
SELFMAILER_SECRETist zugleich der Verschlüsselungs-Seed. Geprüft mit dem ECC-Security-Reviewer.
| Teil | Technik |
|---|---|
| Backend | FastAPI · SQLModel · SQLite (WAL) · httpx · aiosmtplib · imap-tools · PyJWT |
| Web | React · Vite · TypeScript · EventSource (SSE) |
| App | Kotlin · Jetpack Compose · OkHttp · WorkManager · Firebase Messaging · BiometricPrompt |
| Push | FCM (Google) · ntfy (self-hosted) |
| Deploy | Docker (Multi-Stage, non-root) · GHCR · Unraid |
# Backend
cd backend && pip install -r requirements.txt
export SELFMAILER_SECRET=$(python -c "import secrets; print(secrets.token_hex(32))")
uvicorn app.main:app --reload --port 8090
# Web (zweites Terminal)
cd frontend && npm install && npm run dev # http://localhost:5173, proxyt /api → :8090Interaktive API-Doku: http://localhost:8090/docs
Tests ausführen: cd backend && pip install -r requirements.txt -r requirements-dev.txt && pytest --cov=app
(pytest + TestClient gegen eine Temp-SQLite — aktuell ~48 % Coverage; die IMAP/SMTP/DAV-Pfade brauchen Mocking, um höher zu kommen.)
- Mail (IMAP/POP3/SMTP) · gestapelte Konten · Ordnerbaum + Ungelesen-Zähler
- Kalender · Kontakte · Notizen · Aufgaben · CalDAV/CardDAV + ICS/vCard-Feeds
- 2FA / TOTP · Filterregeln · Konto-übergreifend übertragen · Mehrfachauswahl · Such-Filter
- Native Android-App (Synology-Stil, Biometrie-Sperre)
- Benachrichtigungen: FCM + ntfy + Hintergrund, pro Konto & Ordner
- Live-Sync (SSE) zwischen Web-Clients und der Android-App (Vordergrund)
- Kalender-Monatsgitter in der App
- HTTPS-Reverse-Proxy-Anleitung
- OAuth für Gmail / Outlook
- Tests Richtung 80 % Coverage
SelfMailer · part of the Self family · made with 📬 for self-hosting