aboutsummaryrefslogtreecommitdiffstats
path: root/README.md
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-03 10:56:58 +0200
committerDanilo M. <danix@danix.xyz>2026-08-04 12:53:12 +0200
commit8b507431ef55bb48b5bf55af70167af50d60910d (patch)
tree1917f9a60fb846d2c281beb6a822750183e5c73b /README.md
parent55e2440d62243bd80003b2fc98540633c7f57cc8 (diff)
downloadqtmaildir-8b507431ef55bb48b5bf55af70167af50d60910d.tar.gz
qtmaildir-8b507431ef55bb48b5bf55af70167af50d60910d.zip
docs: add README, changelog and release procedurev0.1.0
README covers what the project deliberately does not do (no POP, IMAP or SMTP, no send in v1, no confirmation dialogs), the verified dependency versions, build and test commands, the full config format with the reasons behind its two surprises, the keybinding table, and the security posture of the message view. Two facts in it were checked rather than assumed: the `spam` and `load_remote` actions are registered but have no default binding, so they are documented as unreachable until bound; and the attachment path guard does exist as described. The Release build was also verified to pass all 11 test binaries, which matters because Q_ASSERT compiles out there and one of the cid: invariants leans on an assertion in debug. CHANGELOG.md follows Keep a Changelog and records 0.1.0 along with the current known limitations. docs/RELEASING.md records what semver means for a mail client (the config format and action names are the public interface) and the steps, including that the version is bumped in exactly one place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'README.md')
-rw-r--r--README.md198
1 files changed, 198 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4acd215
--- /dev/null
+++ b/README.md
@@ -0,0 +1,198 @@
+# qtmaildir
+
+A Qt6 desktop mail client for reading and organizing a local,
+notmuch-indexed Maildir. A GUI counterpart to neomutt for the parts of mail
+handling that are easier with a mouse and a real HTML renderer.
+
+## What it does not do
+
+This is the important half of the description.
+
+qtmaildir does **no network protocol work at all**. There is no POP, no
+IMAP, no SMTP, and no sync implementation. Mail arrives in your Maildir by
+whatever you already use (mbsync, offlineimap, fetchmail) and is indexed by
+`notmuch new`. qtmaildir reads the result.
+
+Fetching is delegated to a command you configure. Running your existing
+script means joining the `flock` that already serializes it against cron; a
+built-in implementation would sit outside that lock and could run two
+`mbsync` processes over one Maildir, which corrupts UID state.
+
+Sending is not implemented. Compose, reply, forward and send are planned for
+v2 and need a companion send script that does not exist yet.
+
+There is also no dry-run and no "are you sure?" on destructive actions. The
+answer for a human at a GUI is undo, which is implemented, and which is
+strictly better than a dialog that gets dismissed reflexively.
+
+## Requirements
+
+Verified against these versions on Slackware -current:
+
+| Component | Version | Notes |
+|---|---|---|
+| Qt6 | 6.11.1 | Widgets and WebEngine. On Slackware both ship in the monolithic `qt6` package; there is no separate `qt6-webengine`. |
+| libnotmuch | 0.39 (libnotmuch 5.6) | Installs no `notmuch.pc`, so CMake locates it with `find_path`/`find_library` rather than pkg-config. |
+| GMime | 3.2.15 | Does ship `gmime-3.0.pc`. |
+| CMake | 4.3.4 | 3.21 or newer. |
+| GCC | 15.3.0 | C++17. |
+
+You also need a notmuch database that is already set up and working from the
+command line. qtmaildir does not create or configure one.
+
+## Building
+
+```bash
+cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
+cmake --build build
+./build/src/qtmaildir
+```
+
+Running the tests:
+
+```bash
+ctest --test-dir build --output-on-failure
+```
+
+A single test binary, for a tighter loop:
+
+```bash
+./build/tests/test_keymap
+```
+
+## Configuration
+
+`~/.config/qtmaildir/qtmaildir.conf`, INI format.
+
+The Maildir path is deliberately **not** configurable. notmuch already
+stores it as `database.path` and libnotmuch reads it; duplicating it here
+would create two sources of truth and let the GUI index a different tree
+than the CLI. The only escape hatch is pointing at an alternate notmuch
+config, so notmuch remains the authority either way.
+
+Per-account subdirectories *are* configured, because notmuch does not model
+accounts at all: it sees one flat tree. An account is a path prefix plus an
+identity.
+
+```ini
+[general]
+; Optional. Omit to let notmuch resolve its own config, which is what keeps
+; the GUI and the CLI pointed at one database.
+; notmuch_config = /home/you/.notmuch-config
+
+[sync]
+; Optional. Omit and the Sync button disables itself with a tooltip.
+command = /home/you/bin/mailsync.sh
+
+; Section names use a dot, not a slash: QSettings treats "/" as its own
+; group separator, so [account/work] would be parsed as a nested group.
+[account.work]
+name = Your Name
+address = you@example.org
+maildir = work-mail ; relative to notmuch's database.path
+drafts = Drafts ; recorded for v2; unused today
+
+[account.personal]
+name = Your Name
+address = you@example.net
+maildir = personal
+drafts = Drafts
+
+[queries]
+Inbox = tag:inbox
+Unread = tag:unread
+Flagged = tag:flagged
+
+[keys]
+j = next_thread
+k = prev_thread
+Return = open_thread
+a = archive
+d = delete
+N = toggle_unread
+F = flag
+/ = focus_query
+h = toggle_html
+u = undo
+G = sync
+Ctrl+Q = quit
+```
+
+Saved-query buttons appear in alphabetical order rather than file order:
+QSettings returns keys sorted, and preserving file order would mean
+hand-rolling an INI parser.
+
+## Keybindings
+
+Defaults, all rebindable through `[keys]`:
+
+| Key | Action | Does |
+|---|---|---|
+| `j` | `next_thread` | Select the next thread |
+| `k` | `prev_thread` | Select the previous thread |
+| `Return` | `open_thread` | Focus the thread list |
+| `a` | `archive` | Remove `inbox` from every selected thread |
+| `d` | `delete` | Add `deleted` |
+| `N` | `toggle_unread` | Toggle `unread` |
+| `F` | `flag` | Add `flagged` |
+| `/` | `focus_query` | Focus and select the query bar |
+| `h` | `toggle_html` | Switch the thread between HTML and plain text |
+| `u` | `undo` | Undo the last tag change |
+| `G` | `sync` | Run the configured sync command |
+| `Ctrl+Q` | `quit` | Quit |
+
+Two further actions exist but have **no default binding**, so they are
+unreachable until you bind them: `spam` (adds `spam`, removes `inbox`) and
+`load_remote` (the keyboard equivalent of the "Load remote content"
+button).
+
+An unknown action name in `[keys]` produces a warning at startup rather than
+binding silently, so a typo is visible.
+
+Tag actions apply to **every selected thread**, not only the focused one.
+
+## Security posture of the message view
+
+A mail client rendering HTML is a browser engine pointed at input from
+strangers, and it is treated that way.
+
+- A dedicated off-the-record `QWebEngineProfile`: no cookies, no cache,
+ nothing persisted.
+- JavaScript disabled. `LocalContentCanAccessRemoteUrls` and
+ `LocalContentCanAccessFileUrls` both false. Plugins and fullscreen off.
+- A request interceptor that **blocks everything by default**. Remote
+ images, stylesheets and fonts are stopped before a connection opens,
+ which defeats tracking pixels and read receipts.
+- When something was blocked the header shows "Remote content blocked" with
+ a **Load remote content** button. The grant applies to that one render and
+ is never remembered. Switching threads discards both the grant and
+ anything already fetched under it.
+- Link clicks are intercepted and handed to the system browser, so a message
+ can never replace the pane with a page of its own choosing.
+- A whole thread renders as one document rather than one web view per
+ message, which would spawn a Chromium render process per message.
+ Sharing one document means `cid:` references could collide between
+ messages, so every reference is namespaced per message; two newsletters
+ using `cid:logo@example.org` resolve to their own images.
+- Attachment filenames are treated as untrusted input. A name from a MIME
+ header is reduced to its basename and the result is resolved against the
+ chosen directory; anything escaping that directory is refused.
+
+## Documentation
+
+- `CHANGELOG.md` - what changed in each release.
+- `docs/RELEASING.md` - versioning policy and the release steps.
+- `docs/manual-verification.md` - results of the manual checklist run
+ against a real mailbox, including the defects it caught.
+
+## License
+
+GPL-2.0-only. See `LICENSE` for the full text.
+
+## Development Approach
+
+This project is developed using AI-assisted tools. Code is generated with the help of AI based on human-provided specifications, design decisions, and iterative feedback.
+
+All contributions are reviewed, tested, and curated by the maintainer before being included in the codebase. AI is used as a productivity and exploration tool, while human oversight remains central to all decisions.
+
+The goal is to combine the flexibility of AI-assisted development with standard open-source practices such as transparency, review, and accountability.