0
0
Fork
You've already forked sharedinbox
0
SharedInbox: a Mail User Agent supporting IMAP and JMAP https://sharedinbox.de/
  • Dart 77%
  • HTML 13.4%
  • Go 2.7%
  • Shell 2.3%
  • Python 2.1%
  • Other 2.4%
Thomas Güttler 0892e2f152
Some checks failed
CI / Full Project Check (push) Has been cancelled
ci: support draft fallback when deploying to a draft Google Play app
# Committing as: info@thomas-guettler.de 
2026年06月12日 23:40:03 +02:00
.devcontainer ci: automate dev container build via devcontainer.json + workflow ( #553 ) 2026年06月09日 21:31:45 +02:00
.forgejo ci: automate dev container build via devcontainer.json + workflow ( #553 ) 2026年06月09日 21:31:45 +02:00
.github/workflows ci: copy forgejo workflows to github workflows for Gitea support 2026年06月12日 19:05:27 +02:00
android chore(deps): update plugin com.android.application to v9 ( #465 ) 2026年06月06日 09:04:32 +02:00
assets
ci fix(ci): stop gradle daemon between flutter build apk and assembleAndroidTest ( #554 ) 2026年06月10日 13:13:28 +02:00
codeberg-runner
drift_schemas
hooks
integration_test feat: configurable menu bar position for mailbox view ( #298 ) ( #303 ) 2026年05月27日 22:07:12 +02:00
lib refactor(ui): unify email-list code across folder, combined inbox, search 2026年06月10日 12:59:08 +00:00
linux feat: use icon.svg as app icon for Android and Linux ( #459 ) 2026年06月06日 05:32:29 +02:00
playstore playstore icons 2026年06月04日 14:26:10 +02:00
scripts ci: support draft fallback when deploying to a draft Google Play app 2026年06月12日 23:40:03 +02:00
server/bugreport test: add automerge comment in bugreport/main.go 2026年06月12日 17:02:28 +00:00
stalwart-dev fix: enforce non-root execution in Taskfile and shell scripts ( #433 ) 2026年06月05日 18:41:36 +02:00
test test(email-list): drop redundant accountId arg to satisfy --fatal-infos 2026年06月10日 13:45:10 +00:00
website fix: add explicit note that app settings are never uploaded ( #280 ) ( #281 ) 2026年05月27日 08:25:20 +02:00
.daggerignore fix(ci): add .task/ and .fvm/ to .daggerignore to skip walk 2026年05月20日 13:52:19 +02:00
.env.example
.envrc
.fvmrc chore: drop nix and migrate to container-based development 2026年06月08日 22:34:48 +02:00
.gitignore ignore kubeconfig. 2026年06月08日 17:05:10 +02:00
.pre-commit-config.yaml chore: drop nix and migrate to container-based development 2026年06月08日 22:34:48 +02:00
AGENTS.md docs: update AGENTS.md for new agentloop defaults (merge prompt + label rename) ( #471 ) 2026年06月07日 04:38:19 +02:00
analysis_options.yaml
before-playstore.md
DAGGER.md fix: add Renovate custom managers for Dagger version in Dockerfile and DAGGER.md ( #365 ) 2026年06月03日 19:25:25 +02:00
DB-SYNC.md
deploy.sh fix: enforce non-root execution in Taskfile and shell scripts ( #433 ) 2026年06月05日 18:41:36 +02:00
deploy_cron.py refactor(deploy_cron): trigger Forgejo Actions workflow via fgj instead of deploying locally 2026年05月23日 17:42:20 +02:00
DEVELOPMENT.md test: add agentloop code test comment to DEVELOPMENT.md ( #336 ) 2026年06月01日 21:47:44 +02:00
Dockerfile.dev chore: drop nix and migrate to container-based development 2026年06月08日 22:34:48 +02:00
done.md feat: reimplement user preferences, archive, configurable navigation ( #315 ) ( #324 ) 2026年05月29日 19:08:12 +02:00
go.mod chore: move go.mod to repository root 2026年06月05日 15:11:30 +02:00
icon.png feat: use icon.svg as app icon for Android and Linux ( #459 ) 2026年06月06日 05:32:29 +02:00
icon.svg icon.svg 2026年06月04日 11:12:07 +02:00
LATER.md
next.md
plan.log
pubspec.lock chore(deps): bump go_router, file_picker, flutter_local_notifications ( #532 ) 2026年06月07日 19:45:22 +02:00
pubspec.yaml chore(deps): bump go_router, file_picker, flutter_local_notifications ( #532 ) 2026年06月07日 19:45:22 +02:00
README.md clean up in README 2026年06月05日 08:21:13 +02:00
renovate.json feat: track Flutter version in Renovate via Docker datasource ( #452 ) 2026年06月05日 22:42:47 +02:00
secrets.enc.yaml chore: migrate CI secrets from Forgejo to SOPS ( #354 ) 2026年06月03日 06:37:07 +02:00
shared-pub-cache-setup.md
SYNC.md
Taskfile.yml feat(playstore): also publish AAB to closed-testing (alpha) track ( #546 ) 2026年06月08日 18:55:58 +02:00

SharedInbox License: MIT

IMAP/SMTP email client written in Flutter.

Targets Android, iOS, and Desktop (Linux done; macOS, Windows, Android, iOS scaffolded). Supports multiple accounts — each synced independently via IMAP IDLE.

Design philosophy: offline-first

IMAP/SMTP server
 ↓
 AccountSyncManager ←→ Drift (SQLite, local DB)
 (IMAP IDLE per account) ↓
 UI (reads only from DB)

The UI never touches the network. The sync engine runs in the background and writes to a local Drift database. Screens observe reactive streams from that DB.

Platform support

Platform Status
Linux desktop Working (task run)
Android APK builds (task build-android)
macOS desktop Scaffolded
Windows desktop Scaffolded
iOS Scaffolded

Key packages

Package Role
enough_mail IMAP / SMTP / MIME
drift Local SQLite ORM (offline-first store)
flutter_riverpod State management / DI
go_router Navigation
flutter_secure_storage Password storage

For users

Run the app, tap +, and enter your IMAP/SMTP server details. The app syncs your INBOX in the background using IMAP IDLE and works offline — the network is only needed during initial sync and when sending mail.


For developers

Prerequisites

Nix with flakes enabled and direnv.

# One-time: allow direnv to load the Nix dev shell
direnv allow
# One-time: install the pinned Flutter version (fvm is provided by Nix)
fvm install

direnv loads the Nix flake automatically — it provides go-task, fvm, Android SDK, Stalwart, and Linux build tools. Flutter itself is managed by FVM (pinned in .fvmrc) rather than Nix, which avoids glibc compatibility issues on non-NixOS hosts. task check also runs fvm install automatically if Flutter is missing.

First-time setup

# Generate the Drift database layer (required before first build)
task codegen
# Verify everything compiles and tests pass
task check

Daily workflow

task analyze # flutter analyze (uses analysis_options.yaml)
task test # pure-Dart unit tests + coverage gate (≥85%)
task test-widget # widget tests — headless, no device needed
task test-flutter # full Flutter test suite (unit + widget + integration)
task integration # IMAP/SMTP integration tests via local Stalwart server
task build-linux # flutter build linux --debug (compile check)
task run # flutter run -d linux
task analyze-fix # dart fix --apply

task check runs analyze + test + test-widget + build-linux + integration in parallel — use it before every commit.

Running the app on desktop in mobile screen resolution

Start the app on the Linux desktop target:

task run # or: flutter run -d linux

After the window opens, resize it to a phone-like size. Typical reference dimensions:

Device profile Width ×ばつ Height
Compact phone (e.g. Pixel 6a) 360 ×ばつ 800
Large phone (e.g. iPhone 14 Pro) 393 ×ばつ 852
Tall phone (e.g. Samsung S24) 360 ×ばつ 780

Drag the window border to those dimensions, or use your window manager's "set window size" feature. The Flutter layout engine responds to the window size exactly as it would on a real device — breakpoints, overflow, and scrolling behave identically. Hot-reload (r in the terminal) preserves the window size between reloads.

Building and installing an Android APK

Build a release APK with:

task build-android # or: flutter build apk --release

The signed APK is written to:

build/app/outputs/flutter-apk/app-release.apk

Install via ADB (USB cable or Wi-Fi ADB, device must have "Install from unknown sources" enabled):

adb install build/app/outputs/flutter-apk/app-release.apk

Install by side-loading (no cable):

  1. Copy app-release.apk to the device (e.g. via USB file transfer, cloud storage, or adb push).
  2. Open a file manager on the device, tap the .apk file, and confirm the install prompt.

Tip — split APKs for smaller size: flutter build apk --split-per-abi produces three smaller APKs (one per CPU architecture). Install the one matching the device: app-arm64-v8a-release.apk covers almost all modern Android phones.

Widget tests

test/widget/ contains Flutter widget tests for every screen. They run headlessly — no display server, no device, no database, no network. Each test pumps the screen into a virtual render canvas and uses in-memory fakes for the Riverpod repository providers.

Run them locally:

task test-widget # or: flutter test test/widget/

They also run in CI on every push (see the Widget tests step in .forgejo/workflows/ci.yml).

After changing the DB schema

Edit lib/data/db/database.dart, then:

task codegen # regenerates lib/data/db/database.g.dart

database.g.dart is git-ignored; every developer must regenerate it after cloning or pulling schema changes.

Integration tests

task integration

Starts a local Stalwart mail server on random ports, runs the tests in test/integration/, then stops it. No manual setup needed — Stalwart is provided by the Nix flake.

Adding a screen

  1. Create lib/ui/screens/my_screen.dart — extend ConsumerWidget.
  2. Add a GoRoute in lib/ui/router.dart.
  3. Read from Riverpod providers in lib/di.dart; never call the network directly from UI.

Project layout

lib/
 core/
 models/ — plain Dart data classes (Account, Email, Mailbox, ...)
 repositories/ — abstract interfaces
 sync/ — AccountSyncManager (IMAP IDLE + backoff)
 utils/ — htmlToPlain, fmtSize (pure functions, unit-tested)
 data/
 db/ — Drift schema + generated code
 imap/ — connectImap / connectSmtp helpers
 repositories/ — concrete implementations
 ui/
 screens/ — one file per screen
 router.dart — go_router route tree
 di.dart — Riverpod providers
 main.dart — entry point
packages/
 enough_mail/ — vendored IMAP/SMTP library (editable)
stalwart-dev/ — local mail server config + start/test scripts
test/
 unit/ — pure-Dart unit tests (no device)
 widget/ — Flutter widget tests (headless, no device)
 integration/ — IMAP/SMTP tests against local Stalwart

Working features

  • Multiple accounts — add any number of IMAP/SMTP accounts; each syncs independently
  • IMAP IDLE — background sync with push-like latency; exponential backoff (5 s → 5 min) on error
  • Mailbox list — shows all folders with unread / total counts
  • Email list — sender, subject, date; bold for unread; manual sync button
  • Email detail — renders plain text; falls back to HTML→plain conversion; marks as read on open; shows attachment names and sizes
  • Reply / Reply all — pre-fills To, Subject (Re:), Cc from original
  • Compose — To, Cc, Subject, Body fields; sends via SMTP
  • Flag / unflag — star button in detail view; amber star indicator in list; synced to server
  • Move to folder — bottom-sheet folder picker; moves on server via IMAP MOVE
  • Attachment indicators — paperclip icon in email list; filename + size in detail
  • Delete email — removes from server (IMAP expunge) and local DB
  • Settings — list and remove accounts
  • Search — IMAP server-side search (subject + body); results shown inline, no navigation change
  • Offline-first — all reads come from local Drift/SQLite DB; network only for sync and send