SelfMailer
Docker app from S3lfcod3r's Repository
Overview
Readme
View on GitHub
Self-hosted, multi-user e-mail client â with calendar, contacts, notes, a native Android app and real-time sync.
Selbstgehosteter Mehrbenutzer-Mailclient â mit Kalender, Kontakten, Notizen, nativer Android-App und Echtzeit-Sync.
Version
License
Backend
Web
App
Ko-fi
đŹđ§ English · đ©đȘ Deutsch
đ° What's new / Neuigkeiten â CHANGELOG.md
đŹđ§ English
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 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.
đ Table of contents
Features · Blocking senders · Architecture · Notifications · Live sync · Quick start · Unraid · Configuration · Backups · Android app · API · Security · Tech stack · Development · Roadmap
âš Features
đŹ Mail
- Multiple IMAP / 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
- Mailbox cleanup: find similar mail by sender, domain, subject, keyword or label, preview, then move in bulk; saved search views
- Block sender in one click â mail goes to trash (existing + future); a blocked-senders list lets you undo any block. Rules also match the reply-to address and the newsletter ID (List-Id) and show a hit counter
- 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
- New mail right away via IMAP IDLE: the server reports new mail itself (2-minute polling stays as a safety net); the text of new mail is prefetched, so it opens instantly
đĄïž Spam & virus protection (Settings â Security, everything off until you switch it on)
- Blocks for all accounts â a sender blocked in one account is removed from all your accounts
- One-click unsubscribe for newsletters (RFC 8058) â with a warning when the sender is not verified
- Virus scan of attachments with your own ClamAV â new mail is checked before it shows up or pushes; infected mail goes to spam, infected downloads are blocked
- Spam filter with rspamd â rates new mail, moves spam away and learns from your âSpamâ button
đ Organizer
- Calendar â local events, month/agenda views, birthdays from contacts
- Contacts â rich address book (phones, address, organisation, website, birthday); import from / export to the phone, optional two-way sync
- 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 and the mail cache Fernet-encrypted at rest
- i18n DE/EN, light/dark themes + custom accent colours
- Single container â FastAPI + SQLite, no external database
đ« Blocking senders
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).
Still getting mail from a blocked sender? Spammers keep changing their address. Under âïž Settings â Security â Spam & virus protection you can:
- let every block apply to all your accounts,
- and under đ Rules block by reply-to address or newsletter ID (List-Id) â features spammers change less often than the sender.
Each rule shows how often it matched â so you can see whether a block still works. For real newsletters, the Unsubscribe button (same settings page) is the cleaner way out; for unknown senders, blocking is safer, because unsubscribing confirms that your address exists.
đĄ Tip: auto-empty timings (spam & trash: off / immediately / after 7 / 30 days) live under đ€ â â Edit account.
đ§ Architecture
ââââââââââââââââ âââââââââââââââââ
â 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 SMTP CalDAV / CardDAV FCM ntfy
(read mail) (send mail) (cal & contacts) (Google push) (self-hosted)
đ Notifications
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.
đ Live sync
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.
đ Quick start (Docker)
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 -d
Open http://<host>:8090 â the first-run setup creates the admin account â then add your mail accounts.
For access from outside your LAN, put a reverse proxy in front: HTTPS guide.
đŠ Unraid
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).
âïž Configuration
Environment variables, all with the prefix SELFMAILER_.
| 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 |
Firebase service-account JSON for Google push. Empty/missing = FCM off. |
SELFMAILER_TRANSLATE_URL |
â | â | Self-hosted LibreTranslate URL for in-app translation (e.g. http://192.168.1.10:5000). Empty = 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 |
â | 120 |
Seconds between background mail syncs (lower = faster push & sync) |
SELFMAILER_IDLE |
â | 1 |
IMAP IDLE on the inbox: new mail is reported by the server right away (one extra connection per account); 0 = off |
SELFMAILER_PREFETCH_NEW |
â | 20 |
Prefetch the full text of up to N new inbox mails per run (†1 MB each, not marked as read); 0 = off |
SELFMAILER_CLAMAV |
â | â | ClamAV (clamd) host:port for scanning attachments, e.g. 192.168.1.10:3310. LAN only â clamd has no password. Users switch it on under Settings â Security. |
SELFMAILER_RSPAMD_URL |
â | â | rspamd scanner (port 11333) for the spam filter. Users switch it on under Settings â Security. |
SELFMAILER_RSPAMD_CONTROLLER_URL / SELFMAILER_RSPAMD_PASSWORD |
â | â | Optional: rspamd controller (port 11334) + password so the "Spam" button teaches rspamd. |
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 (0 = keep all) |
đŸ Backups
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.
Restore â stop the container, replace the live DB with a snapshot, 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 selfmailer
đ± Android app
A 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
- Drafts are kept (encrypted locally) even after app-lock or restart
- Contacts: import from / export to the phone, optional two-way sync
- 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
đ API overview
Base path /api/v1, authentication via JWT Bearer. Interactive docs: http://<host>:8090/docs.
| 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) |
đĄïž Security
- Login passwords Argon2-hashed; mailbox & DAV passwords Fernet-encrypted at rest (key from
SELFMAILER_SECRET) - Data at rest: the mail cache (subject, sender, preview, full text) and scheduled mails are encrypted; database and backups readable by the container user only
- IMAP certificates verified, optionally pinned per account (fingerprint) for self-signed LAN servers
- Revocable sessions: logout, password/2FA changes and admin locks revoke tokens, SSE access and push registrations; CSRF protection for cookie sessions
- JWT pinned to
HS256/384/512; the 2FA intermediate token grants no access - Rate limiting on login/2FA/setup; defensive response headers; API responses
Cache-Control: no-store; anti-enumeration dummy hash - SSRF guard with IP pinning on DAV pull, ntfy, translation and unsubscribe (loopback/link-local/cloud-metadata always blocked; unsubscribe only to public addresses); DAV credentials stay bound to their origin
- Container: non-root (uid 99), app code read-only, no Linux capabilities (
no-new-privileges,cap-drop=ALL), base images pinned by digest, Trivy image scan and dependency audits in CI - Virus scan never reports âcleanâ without a real scan â errors and oversized files are marked ânot checkedâ
- Android:
allowBackup=false, token in the Android Keystore, hardened mail-HTML WebView (no JavaScript, no form submission), FileProvider with path-traversal check - Deliberate LAN trade-offs (documented): cleartext HTTP allowed (HTTPS optional in the container or via a reverse proxy),
SELFMAILER_SECRETdoubles as the encryption seed
Security update history â CHANGELOG.md (entries marked đ).
đ§± Tech stack
| 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 |
đ ïž Development
# 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 â :8090
Tests:
cd backend && pip install -r requirements.txt -r requirements-dev.txt && pytest --cov=app
đșïž Roadmap
- Mail (IMAP/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
- Mailbox cleanup · saved views · phone contact import/export/sync
- HTTPS reverse-proxy guide
- OAuth for Gmail / Outlook
- Tests toward 80 % coverage
đ©đȘ Deutsch
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-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-Familie (SelfAuthenticator, SelfArchiver, SelfDashboard âŠ) â gleiches Design-System, gleicher Deploy-Stil (GHCR â Unraid).
Kurz â IMAP/SMTP-Mailclient · Kalender/Kontakte/Notizen · Web + native Android · Live-Sync zwischen GerĂ€ten · 3 Benachrichtigungswege (FCM / ntfy / Hintergrund) · 2FA · ein Container · keine externe DB.
đ Inhaltsverzeichnis
Funktionen · Absender blockieren · Architektur · Benachrichtigungen · Live-Sync · Schnellstart · Unraid · Konfiguration · Backups · Android-App · API · Sicherheit · Tech-Stack · Entwicklung · Roadmap
âš Funktionen
đŹ Mail
- Mehrere IMAP-/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 Absender-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 â bei jedem Hintergrund-Sync automatisch angewandt) und serverseitige Postfach-Migration
- Postfach aufrĂ€umen: Ă€hnliche Mails nach Absender, Domain, Betreff, Stichwort oder Label finden, Vorschau prĂŒfen, dann gesammelt verschieben; gespeicherte Suchansichten
- Absender blockieren per Klick â Mails wandern in den Papierkorb (vorhandene + kĂŒnftige); eine Liste blockierter Absender macht jede Blockierung rĂŒckgĂ€ngig. Regeln greifen auch auf die Antwort-Adresse und die Newsletter-Kennung (List-Id) und zeigen einen TrefferzĂ€hler
- Spam & Papierkorb automatisch leeren je Konto (aus / sofort / nach 7 / 30 Tagen)
- Suche mit Schnellfiltern (ungelesen · Anhang · Stern), Seitenweise Anzeige, Vorschauen
- Lokaler SQLite-Cache, vom Hintergrund-Sync warmgehalten â die OberflĂ€che wartet nie auf einen langsamen Anbieter
- Neue Mails sofort per IMAP IDLE: der Server meldet neue Mails selbst (die Abfrage alle 2 Minuten bleibt als Sicherheitsnetz); der Text neuer Mails wird vorgeladen und öffnet sich ohne Wartezeit
đĄïž Spam- & Virenschutz (Einstellungen â Sicherheit, alles aus, bis du es einschaltest)
- Sperren fĂŒr alle Konten â ein in einem Konto gesperrter Absender verschwindet aus allen deinen Konten
- Abmelden mit einem Klick fĂŒr Newsletter (RFC 8058) â mit Warnung, wenn der Absender nicht geprĂŒft ist
- Virenscan der AnhĂ€nge mit deinem eigenen ClamAV â neue Mails werden geprĂŒft, bevor sie erscheinen oder pushen; infizierte Mails wandern in den Spam-Ordner, infizierte Downloads werden gesperrt
- Spamfilter mit rspamd â bewertet neue Mails, verschiebt Spam und lernt mit deinem âSpam"-Knopf
đ Organizer
- Kalender â lokale Termine, Monats-/Agenda-Ansicht, Geburtstage aus Kontakten
- Kontakte â umfangreiches Adressbuch (Telefon, Adresse, Firma, Website, Geburtstag); Import vom / Export aufs Handy, optional Sync in beide Richtungen
- Notizen & Aufgaben
- CalDAV-/CardDAV-Abgleich mit externen Servern (Nextcloud, Synology âŠ) + abonnierbare ICS-/vCard-Feeds
đ Benachrichtigungen â du wĂ€hlst
- FCM (Google-Push) â sofort, keine Dauer-Benachrichtigung (wie Gmail/Synology)
- ntfy â selbstgehosteter Push, kein Google
- Hintergrund-PrĂŒfung â ~1 Min, keine Einrichtung nötig
- Pro Konto, pro Ordner â du wĂ€hlst genau, welche PostfĂ€cher benachrichtigen
- Einstellbar in Web-OberflĂ€che 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
- Mehrbenutzer mit Admin-/Nutzer-Rollen; der Admin kann Konten vorkonfigurieren
- 2FA / TOTP mit Backup-Codes; biometrische App-Sperre unter Android
- Konto-Zugangsdaten und Mail-Cache Fernet-verschlĂŒsselt gespeichert
- Deutsch/Englisch, helles/dunkles Theme + eigene Akzentfarben
- Ein Container â FastAPI + SQLite, keine externe Datenbank
đ« Absender blockieren
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 đ€ BenutzermenĂŒ â đ Regeln â der Kasten đ« Blockierte Absender ganz oben zeigt alle Blockierungen; mit â© Aufheben kommen kĂŒnftige Mails wieder an. Eine einzelne Mail rettest du, indem du sie im Ordner Papierkorb öffnest und zurĂŒck in den Posteingang verschiebst (innerhalb der 7 Tage).
Kommen trotzdem Mails vom gesperrten Absender? Spammer wechseln stĂ€ndig ihre Adresse. Unter âïž Einstellungen â Sicherheit â Spam- & Virenschutz kannst du:
- jede Sperre fĂŒr alle deine Konten gelten lassen,
- und unter đ Regeln nach Antwort-Adresse oder Newsletter-Kennung (List-Id) sperren â Merkmale, die Spammer seltener wechseln als den Absender.
Jede Regel zeigt, wie oft sie zugeschlagen hat â so siehst du, ob eine Sperre noch wirkt. Bei echten Newslettern ist der Abmelden-Knopf (gleiche Einstellungsseite) der sauberere Weg; bei Unbekannten ist Sperren sicherer, denn Abmelden bestĂ€tigt, dass deine Adresse existiert.
đĄ Tipp: die Zeiten fĂŒrs automatische Leeren (Spam & Papierkorb: aus / sofort / nach 7 / 30 Tagen) findest du unter đ€ â â Konto bearbeiten.
đ§ Architektur
ââââââââââââââââ âââââââââââââââââ
â Web-UI â â Android-App â
â React + Vite â â Kotlin/Composeâ
ââââââââŹââââââââ ââââââââŹâââââââââ
ââââââ JSON-API + SSE ââââââ
/api/v1 · JWT (Bearer)
â
ââââââââââŒââââââââââ
â FastAPI-Kern â SQLite (Cache + Konfiguration)
â Hintergrund-Sync â Fernet-verschlĂŒsselte Geheimnisse
â + Event-Bus â In-Memory-Pub/Sub â Live-Sync
ââââââââââŹââââââââââ
ââââââââââââŹâââââââŽââââââââŹâââââââââââââââŹââââââââââââ
IMAP SMTP CalDAV / CardDAV FCM ntfy
(Mail lesen) (Mail senden) (Kal. & Kontakte) (Google-Push) (selbstgehostet)
đ Benachrichtigungen
Methode unter Einstellungen â E-Mail-Benachrichtigungen wĂ€hlen (Web BenutzermenĂŒ â đ / App Einstellungen). Der Server bedient alle drei parallel â z. B. FCM aufs Haupt-Handy, ntfy auf ein Handy ohne Google.
| 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 einen Push auslösen.
Einrichtung â FCM (Google-Push)
- Kostenloses Firebase-Projekt anlegen: https://console.firebase.google.com.
- Android-App hinzufĂŒgen mit Paketname
com.selfmailer.viewerâgoogle-services.jsonherunterladen (kommt in den App-Build). - Projekteinstellungen â Dienstkonten â Neuen privaten SchlĂŒssel generieren â JSON herunterladen.
- Diese JSON auf dem Server als
âŠ/selfmailer/data/fcm-service-account.jsonablegen 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 senden prĂŒfen.
Der Push-Inhalt ist bewusst minimal (â2 neue E-Mailsâ); die App holt die Details direkt von deinem Server â Google sieht keine Mail-Inhalte.
Einrichtung â ntfy (selbstgehostet)
- 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 angezeigte Thema abonnieren.
- In SelfMailer: Benachrichtigungen an â ntfy â URL eintragen â Speichern & aktivieren â Ordner wĂ€hlen.
đ Live-Sync
Jeder offene Client (Web-Tab, App im Vordergrund) hĂ€lt eine schlanke SSE-Verbindung (/api/v1/events/stream). FĂŒhrt jemand eine Aktion aus â oder kommt neue Mail an â schickt der Server ein kleines Event, und die anderen Clients aktualisieren den betroffenen Ordner. Kein Dauer-Polling, kein Google, quasi sofort.
đ Schnellstart (Docker)
cp .env.example .env
# Secret erzeugen und in .env eintragen (SELFMAILER_SECRET):
python -c "import secrets; print(secrets.token_hex(32))"
docker compose up -d
http://<host>:8090 öffnen â die Ersteinrichtung legt das Admin-Konto an â danach deine Mailkonten hinzufĂŒgen.
FĂŒr Zugriff von auĂerhalb des LANs einen Reverse Proxy davorschalten: HTTPS-Anleitung.
đŠ Unraid
Vorlage von
https://raw.githubusercontent.com/s3lfcod3r/selfmailer/main/unraid/selfmailer.xml
hinzufĂŒgen oder unter Docker â Add Container â Template importieren. Master Secret setzen, den Rest auf Standard lassen.
â ïž Master Secret:
SELFMAILER_SECRETnach dem ersten Start nie Ă€ndern â damit werden die gespeicherten Postfach-Passwörter verschlĂŒsselt. Eine Ănderung macht sie unlesbar.â ïž Rechte: der Container lĂ€uft ohne Root (uid
99/ gid100=nobody:users). Gehört eine Datendatei root, einmalig korrigieren:chown -R 99:100 /mnt/user/appdata/selfmailer(oder Tools â Docker Safe New Permissions).
âïž Konfiguration
Umgebungsvariablen, alle mit dem PrÀfix SELFMAILER_.
| Variable | Pflicht | Standard | Zweck |
|---|---|---|---|
SELFMAILER_SECRET |
â | â | JWT-Signatur und VerschlĂŒsselung der gespeicherten Postfach-Passwörter (â„ 32 Zeichen). Nach der Einrichtung nicht Ă€ndern. |
SELFMAILER_DB_PATH |
â | /data/selfmailer.db |
SQLite-Pfad |
SELFMAILER_FCM_CREDENTIALS |
â | /data/fcm-service-account.json |
Firebase-Service-Account-JSON fĂŒr Google-Push. Leer/fehlend = FCM aus. |
SELFMAILER_TRANSLATE_URL |
â | â | URL einer selbstgehosteten LibreTranslate-Instanz fĂŒr die Ăbersetzung in der App (z. B. http://192.168.1.10:5000). Leer = aus (Button ausgeblendet). |
SELFMAILER_TRANSLATE_API_KEY |
â | â | Optionaler API-Key, nur falls deine LibreTranslate-Instanz einen verlangt |
SELFMAILER_ADMIN_TOKEN |
â | â | Wenn gesetzt, verlangt die Ersteinrichtung diesen Token |
SELFMAILER_BASE_URL |
â | â | Ăffentliche Basis-URL (z. B. fĂŒr Feed-Links) |
SELFMAILER_SYNC_INTERVAL |
â | 120 |
Sekunden zwischen Mail-Syncs im Hintergrund (kleiner = schnellerer Push & Sync) |
SELFMAILER_IDLE |
â | 1 |
IMAP IDLE auf dem Posteingang: der Server meldet neue Mails sofort (eine zusÀtzliche Verbindung je Konto); 0 = aus |
SELFMAILER_PREFETCH_NEW |
â | 20 |
Volltext von bis zu N neuen Posteingangs-Mails je Lauf vorladen (je †1 MB, ohne Gelesen-Markierung); 0 = aus |
SELFMAILER_CLAMAV |
â | â | ClamAV (clamd) host:port fĂŒr den Virenscan der AnhĂ€nge, z. B. 192.168.1.10:3310. Nur im Heimnetz â clamd hat kein Passwort. Einschalten unter Einstellungen â Sicherheit. |
SELFMAILER_RSPAMD_URL |
â | â | rspamd-Scanner (Port 11333) fĂŒr den Spamfilter. Einschalten unter Einstellungen â Sicherheit. |
SELFMAILER_RSPAMD_CONTROLLER_URL / SELFMAILER_RSPAMD_PASSWORD |
â | â | Optional: rspamd-Controller (Port 11334) + Passwort, damit der âSpam"-Knopf rspamd anlernt. |
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-Abgleich blockieren (strenger SSRF-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 (0 = alle behalten) |
đŸ Backups
Der Scheduler erstellt nachts ein konsistentes Backup der SQLite-Datenbank (sqlite3-Online-Backup-API, sicher auch wÀhrend SchreibvorgÀngen) in data/backups/:
data/backups/selfmailer-YYYYMMDD-HHMMSS.db
Es werden nur die letzten SELFMAILER_BACKUP_KEEP Backups 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 selfmailer
đ± Android-App
Ein echter nativer Client (android/SelfMailer.apk) â kein WebView-Wrapper. Aufgebaut wie Synology MailPlus:
- SeitenmenĂŒ â Kontowechsel + Sonderordner + Unterordner, mit Ungelesen-Badges
- Untere Navigation: Mail · Kalender · Notizen
- HTML-Mail, AnhÀnge, Mehrfachauswahl mit Aktionsleiste, Suchfilter, Verfassen im Synology-Stil mit Absender-Auswahl
- EntwĂŒrfe bleiben erhalten (lokal verschlĂŒsselt), auch nach App-Sperre oder Neustart
- Kontakte: Import vom / Export aufs Handy, optional Sync in beide Richtungen
- Biometrische App-Sperre (Gesicht / Fingerabdruck / GerĂ€te-PIN), farbige Markierung fĂŒr Ungelesenes
- Benachrichtigungen: FCM / ntfy / Hintergrund â deine Wahl, pro Ordner
- Nutzt dieselbe
/api/v1wie die Web-OberflÀche; Server-URL beim ersten Start eingeben
đ API-Ăbersicht
Basispfad /api/v1, Anmeldung per JWT Bearer. Interaktive Doku: http://<host>:8090/docs.
| 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) |
đĄïž Sicherheit
- Login-Passwörter mit Argon2 gehasht; Postfach- & DAV-Passwörter Fernet-verschlĂŒsselt gespeichert (SchlĂŒssel aus
SELFMAILER_SECRET) - Daten im Ruhezustand: Mail-Cache (Betreff, Absender, Vorschau, Volltext) und geplante Mails verschlĂŒsselt; Datenbank und Backups nur fĂŒr den Container-Nutzer lesbar
- IMAP-Zertifikate werden geprĂŒft, fĂŒr selbstsignierte LAN-Server optional pro Konto per Fingerprint festgelegt
- Widerrufbare Sitzungen: Logout, Passwort-/2FA-Ănderungen und Admin-Sperren widerrufen Tokens, SSE-Zugriff und Push-Registrierungen; CSRF-Schutz fĂŒr Cookie-Sitzungen
- JWT fest auf
HS256/384/512; der 2FA-Zwischentoken gewÀhrt keinen Zugriff - Rate-Limiting bei Login/2FA/Einrichtung; defensive Response-Header; API-Antworten mit
Cache-Control: no-store; Schutz gegen Nutzer-AusspĂ€hung (Dummy-Hash) - SSRF-Schutz mit IP-Pinning bei DAV-Abgleich, ntfy, Ăbersetzung und Abmelden (Loopback/Link-Local/Cloud-Metadaten immer blockiert; Abmelden nur an öffentliche Adressen); DAV-Zugangsdaten bleiben am Origin
- Container: ohne Root (uid 99), App-Code schreibgeschĂŒtzt, keine Linux-Sonderrechte (
no-new-privileges,cap-drop=ALL), Basis-Images per Digest festgelegt, Trivy-Image-Scan und AbhĂ€ngigkeits-Audits in der CI - Virenscan meldet nie âsauber" ohne echten Scan â Fehler und zu groĂe Dateien gelten als ânicht geprĂŒft"
- Android:
allowBackup=false, Token im Android-Keystore, gehĂ€rtete Mail-HTML-WebView (kein JavaScript, keine Formulare), FileProvider mit Path-Traversal-PrĂŒfung - Bewusste LAN-Kompromisse (dokumentiert): unverschlĂŒsseltes HTTP erlaubt (HTTPS optional im Container oder ĂŒber einen Reverse-Proxy),
SELFMAILER_SECRETist zugleich der VerschlĂŒsselungs-Seed
Verlauf der Sicherheitsupdates â CHANGELOG.md (EintrĂ€ge mit đ).
đ§± Tech-Stack
| 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 (selbstgehostet) |
| Deploy | Docker (Multi-Stage, ohne Root) · GHCR · Unraid |
đ ïž Entwicklung
# 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, leitet /api â :8090 weiter
Tests:
cd backend && pip install -r requirements.txt -r requirements-dev.txt && pytest --cov=app
đșïž Roadmap
- Mail (IMAP/SMTP) · gestapelte Konten · Ordnerbaum + Ungelesen-ZÀhler
- Kalender · Kontakte · Notizen · Aufgaben · CalDAV/CardDAV + ICS/vCard-Feeds
- 2FA / TOTP · Filterregeln · kontoĂŒbergreifend ĂŒbertragen · Mehrfachauswahl · Suchfilter
- Native Android-App (Synology-Stil, biometrische Sperre)
- Benachrichtigungen: FCM + ntfy + Hintergrund, pro Konto & Ordner
- Live-Sync (SSE) zwischen Web-Clients und der Android-App (Vordergrund)
- Kalender-Monatsansicht in der App
- Postfach aufrÀumen · gespeicherte Ansichten · Handykontakte importieren/exportieren/abgleichen
- HTTPS-Reverse-Proxy-Anleitung
- OAuth fĂŒr Gmail / Outlook
- Tests Richtung 80 % Abdeckung
â Support / UnterstĂŒtzen
đŹđ§ This project is free for private use and built in my spare time. If it saves you time, a coffee keeps the next update coming. No pressure, a â on the repo helps too!
đ©đȘ Dieses Projekt ist fĂŒr private Nutzung kostenlos und entsteht in meiner Freizeit. Wenn es dir Zeit spart, hĂ€lt ein Kaffee das nĂ€chste Update am Laufen. Kein Muss, ein â auf dem Repo hilft auch!
âïž Lizenz / License
đ©đȘ Alle Rechte vorbehalten. © 2026 s3lfcod3r / SelfCoder
Du darfst die Software privat und nicht-kommerziell installieren und benutzen (z. B. im Homelab, ĂŒber Docker oder Unraid).
Ohne schriftliche Erlaubnis verboten: kommerzielle Nutzung · Kopieren · VerÀndern · Weitergeben · Einbauen in eigene Projekte (auch Closed Source) · Verkaufen.
Details: LICENSE
đŹđ§ All rights reserved. © 2026 s3lfcod3r / SelfCoder
You may install and use the software privately and non-commercially (e.g. in your home lab, via Docker or Unraid).
Not permitted without written permission: commercial use · copying · modifying · redistributing · incorporating into other projects (including closed source) · selling.
Details: LICENSE
SelfMailer · part of the Self family · Teil der Self-Familie
made with đŹ for self-hosting
Requirements
Categories
Related apps
Explore more like this
Explore allDetails
ghcr.io/s3lfcod3r/selfmailer:latestRuntime arguments
- Web UI
http://[IP]:[PORT:8090]/- Network
bridge- Shell
bash- Privileged
- false
- Extra Params
--restart=unless-stopped --memory=1g --security-opt=no-new-privileges --cap-drop=ALL
Template configuration
Path where the SQLite database is stored.
- Target
- /data
- Default
- /mnt/user/appdata/selfmailer/data
- Value
- /mnt/user/appdata/selfmailer/data
Port for the SelfMailer web interface and API.
- Target
- 8090
- Default
- 8090
- Value
- 8090
Optional. Only used when TLS cert and key are set below. HTTP on 8090 keeps working.
- Target
- 8443
Optional built-in HTTPS: path INSIDE the container to the PEM certificate (incl. chain), e.g. /data/certs/fullchain.pem. Set together with the key. Leave empty for plain HTTP or when using a reverse proxy.
- Target
- SELFMAILER_TLS_CERT
Optional built-in HTTPS: path INSIDE the container to the PEM private key, e.g. /data/certs/privkey.pem.
- Target
- SELFMAILER_TLS_KEY
auto (default) = secure cookie only over HTTPS, so HTTP in the LAN and HTTPS work side by side. true = always (HTTPS only). false = never.
- Target
- SELFMAILER_COOKIE_SECURE
- Default
- auto
- Value
- auto
Optional. IP(s) of your reverse proxy (e.g. Nginx Proxy Manager), comma-separated. Needed behind a proxy for the real client IP (login rate limit) and HTTPS detection. Leave empty without a proxy.
- Target
- SELFMAILER_TRUSTED_PROXIES
Optional. Seconds, 0 = off. Only sent on HTTPS responses.
- Target
- SELFMAILER_HSTS_MAX_AGE
- Default
- 0
- Value
- 0
REQUIRED. Long random secret. Used for JWT signing AND at-rest encryption of stored mailbox passwords. Changing it makes saved account passwords unreadable.
- Target
- SELFMAILER_SECRET
Optional. If set, the first-run admin setup requires this token.
- Target
- SELFMAILER_ADMIN_TOKEN
Optional public base URL, e.g. http://192.168.1.69:8090
- Target
- SELFMAILER_BASE_URL
Optional. Folder INSIDE the container for the nightly DB backups. Empty = /data/backups. Recommended: add an extra Path mapping (e.g. container path /backups -> a folder on another disk) and enter /backups here. Do not share the data or backup folder via SMB.
- Target
- SELFMAILER_BACKUP_DIR
Path to SQLite database. Usually no need to change.
- Target
- SELFMAILER_DB_PATH
- Default
- /data/selfmailer.db
- Value
- /data/selfmailer.db
Optional. Path to a Firebase service-account JSON for Google push (FCM). Drop the file into the data folder. Leave/remove to disable FCM.
- Target
- SELFMAILER_FCM_CREDENTIALS
- Default
- /data/fcm-service-account.json
- Value
- /data/fcm-service-account.json
Optional. URL of a self-hosted LibreTranslate instance for in-app e-mail translation, e.g. http://192.168.1.10:5000 (use the Unraid host IP + the LibreTranslate port, default 5000). Empty = translation feature disabled (the translate button stays hidden).
- Target
- SELFMAILER_TRANSLATE_URL
Optional. ClamAV (clamd) host:port for scanning attachments, e.g. 192.168.1.10:3310 (ClamAV container on Unraid). LAN only - clamd has no password. Each user switches the virus scan on under Settings - Security.
- Target
- SELFMAILER_CLAMAV
Optional. rspamd scanner for the spam filter, e.g. http://192.168.1.10:11333. Each user switches the spam filter on under Settings - Security.
- Target
- SELFMAILER_RSPAMD_URL
Optional. rspamd controller (port 11334) so the Spam button teaches rspamd, e.g. http://192.168.1.10:11334.
- Target
- SELFMAILER_RSPAMD_CONTROLLER_URL
Optional. Password of the rspamd controller (for learning).
- Target
- SELFMAILER_RSPAMD_PASSWORD
Optional. Only needed if your LibreTranslate instance requires an API key.
- Target
- SELFMAILER_TRANSLATE_API_KEY