notmuch-browser 0.2.0rc1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- notmuch_browser-0.2.0rc1/.gitignore +10 -0
- notmuch_browser-0.2.0rc1/LICENSE +21 -0
- notmuch_browser-0.2.0rc1/PKG-INFO +106 -0
- notmuch_browser-0.2.0rc1/README.md +85 -0
- notmuch_browser-0.2.0rc1/hatch_build.py +17 -0
- notmuch_browser-0.2.0rc1/package-lock.json +7749 -0
- notmuch_browser-0.2.0rc1/package.json +27 -0
- notmuch_browser-0.2.0rc1/pyproject.toml +66 -0
- notmuch_browser-0.2.0rc1/scripts/check_dist.py +40 -0
- notmuch_browser-0.2.0rc1/scripts/fixture_db.py +50 -0
- notmuch_browser-0.2.0rc1/scripts/release.sh +14 -0
- notmuch_browser-0.2.0rc1/scripts/shots.mjs +235 -0
- notmuch_browser-0.2.0rc1/src/notmuch_browser/__init__.py +3 -0
- notmuch_browser-0.2.0rc1/src/notmuch_browser/__main__.py +67 -0
- notmuch_browser-0.2.0rc1/src/notmuch_browser/app.py +320 -0
- notmuch_browser-0.2.0rc1/src/notmuch_browser/notmuch.py +269 -0
- notmuch_browser-0.2.0rc1/src/notmuch_browser/render.py +201 -0
- notmuch_browser-0.2.0rc1/src/notmuch_browser/static/assets/boot.72d11448.js +1 -0
- notmuch_browser-0.2.0rc1/src/notmuch_browser/static/assets/main.26bc01b9.css +1319 -0
- notmuch_browser-0.2.0rc1/src/notmuch_browser/static/assets/main.684e6e09.js +1 -0
- notmuch_browser-0.2.0rc1/src/notmuch_browser/static/index.html +1 -0
- notmuch_browser-0.2.0rc1/tests/conftest.py +160 -0
- notmuch_browser-0.2.0rc1/tests/test_app.py +295 -0
- notmuch_browser-0.2.0rc1/tests/test_notmuch.py +169 -0
- notmuch_browser-0.2.0rc1/tests/test_render.py +129 -0
- notmuch_browser-0.2.0rc1/web/src/api.js +61 -0
- notmuch_browser-0.2.0rc1/web/src/app.js +273 -0
- notmuch_browser-0.2.0rc1/web/src/boot.js +14 -0
- notmuch_browser-0.2.0rc1/web/src/dom.js +44 -0
- notmuch_browser-0.2.0rc1/web/src/format.js +113 -0
- notmuch_browser-0.2.0rc1/web/src/hue.js +55 -0
- notmuch_browser-0.2.0rc1/web/src/icons.js +59 -0
- notmuch_browser-0.2.0rc1/web/src/index.html +13 -0
- notmuch_browser-0.2.0rc1/web/src/index.js +13 -0
- notmuch_browser-0.2.0rc1/web/src/reader.js +281 -0
- notmuch_browser-0.2.0rc1/web/src/router.js +23 -0
- notmuch_browser-0.2.0rc1/web/src/sidebar.js +84 -0
- notmuch_browser-0.2.0rc1/web/src/styles/base.css +109 -0
- notmuch_browser-0.2.0rc1/web/src/styles/components.css +831 -0
- notmuch_browser-0.2.0rc1/web/src/styles/layout.css +214 -0
- notmuch_browser-0.2.0rc1/web/src/styles/tokens.css +161 -0
- notmuch_browser-0.2.0rc1/web/src/theme.js +53 -0
- notmuch_browser-0.2.0rc1/web/src/threadlist.js +169 -0
- notmuch_browser-0.2.0rc1/web/src/toast.js +45 -0
- notmuch_browser-0.2.0rc1/web/src/topbar.js +124 -0
- notmuch_browser-0.2.0rc1/webpack.config.js +35 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Guilhem Lavaux
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: notmuch-browser
|
|
3
|
+
Version: 0.2.0rc1
|
|
4
|
+
Summary: A small local web UI to browse and tag a notmuch mail database
|
|
5
|
+
Author: Guilhem Lavaux
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: email,maildir,notmuch,web
|
|
9
|
+
Classifier: Environment :: Web Environment
|
|
10
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Topic :: Communications :: Email
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Requires-Dist: aiohttp<4,>=3.9
|
|
19
|
+
Requires-Dist: nh3<0.4,>=0.3.7
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# notmuch-browser
|
|
23
|
+
|
|
24
|
+
A small web UI to search, read and tag the mail in a [notmuch](https://notmuchmail.org) database. It runs on your own machine and talks to the `notmuch` command line. Your mail never leaves the computer it is on.
|
|
25
|
+
|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
You need Python 3.11 or newer and the `notmuch` command (0.36 or newer, developed against 0.40). You do not need Node.
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
uv tool install notmuch-browser
|
|
34
|
+
notmuch-browser
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Or run it once without installing:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
uvx notmuch-browser
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The server listens on `http://127.0.0.1:8765/` and opens your browser. Options:
|
|
44
|
+
|
|
45
|
+
| Option | Meaning |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `--port N` | listen on another port (default 8765) |
|
|
48
|
+
| `--notmuch-config FILE` | use a specific notmuch configuration file |
|
|
49
|
+
| `--no-browser` | do not open a browser window |
|
|
50
|
+
|
|
51
|
+
## Using it
|
|
52
|
+
|
|
53
|
+
Type a notmuch query in the search field and press Enter. The help button next to the field lists the common prefixes, and `man notmuch-search-terms` has the full syntax. The left rail holds saved searches (Inbox, Unread, Flagged, All mail) and every tag, with the number of unread threads loading in the background.
|
|
54
|
+
|
|
55
|
+
| Key | Action |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `/` | focus the search field |
|
|
58
|
+
| `j` / `k` | next and previous thread |
|
|
59
|
+
| `f` | flag or unflag the open thread |
|
|
60
|
+
| `t` | add a tag to the open thread |
|
|
61
|
+
|
|
62
|
+
Tag changes show an Undo button. Undo restores exactly the messages that the change touched, so a thread that was only partly flagged returns to its earlier state.
|
|
63
|
+
|
|
64
|
+
HTML mail is cleaned and shown in a sandboxed frame. Scripts, forms, styles and remote images are removed, so tracking pixels do not load. Inline images that belong to the message are shown. Attachments download as files.
|
|
65
|
+
|
|
66
|
+
The light and dark themes follow your system. The button in the top bar overrides it and the choice is remembered in the browser.
|
|
67
|
+
|
|
68
|
+
## What it does to your mail
|
|
69
|
+
|
|
70
|
+
Reading changes nothing. The only write is `notmuch tag`. It never indexes, sends, composes or deletes.
|
|
71
|
+
|
|
72
|
+
Read this if you use `maildir.synchronize_flags=true` (the notmuch default): changing the `unread`, `flagged`, `replied`, `draft` or `passed` tags renames the message files in your Maildir, because notmuch keeps the Maildir flags in step. If an IMAP sync tool also works on that Maildir, avoid tagging while it runs. If another program holds the database write lock, for example `notmuch new`, a tag change fails after a few seconds with a clear "database busy" message and changes nothing.
|
|
73
|
+
|
|
74
|
+
## Security model
|
|
75
|
+
|
|
76
|
+
- The server binds `127.0.0.1` only. There is no option to change that and there is no login.
|
|
77
|
+
- Requests with an unexpected `Host` header are refused, which stops DNS rebinding. Writes also need a matching `Origin` and a JSON content type, and cross-site requests are refused.
|
|
78
|
+
- The one cross-site request that is served is the image load of a message's inline `cid:` image from the sandboxed mail frame, because a browser labels that frame's requests cross-site. A foreign web page could use it only to learn that an image part exists and how large it is, and only if it already knew the message id.
|
|
79
|
+
- Attachments are always sent as downloads with a restrictive content security policy.
|
|
80
|
+
- On a shared machine other local users can reach the port. Do not run it there.
|
|
81
|
+
|
|
82
|
+
## Development
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
uv sync
|
|
86
|
+
npm ci
|
|
87
|
+
npm run build # compiles web/src into src/notmuch_browser/static
|
|
88
|
+
uv run pytest # tests use a throwaway database, never your real one
|
|
89
|
+
uv run notmuch-browser --notmuch-config "$(uv run python scripts/fixture_db.py /tmp/nb-fixture)"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`npm run dev` rebuilds on change. The Python server serves the built files, so reload the page.
|
|
93
|
+
|
|
94
|
+
`node scripts/shots.mjs <notmuch-config>` drives the real UI with Playwright against a fixture database, checks behaviour and writes screenshots.
|
|
95
|
+
|
|
96
|
+
### Releasing
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
scripts/release.sh
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
builds the bundle, then the wheel and sdist, and checks their contents. The bundle is not stored in git, so a wheel built without `npm run build` would have no UI. The build refuses to run in that case. The script does not upload anything. Publish with `uv publish` when you are ready.
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# notmuch-browser
|
|
2
|
+
|
|
3
|
+
A small web UI to search, read and tag the mail in a [notmuch](https://notmuchmail.org) database. It runs on your own machine and talks to the `notmuch` command line. Your mail never leaves the computer it is on.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
You need Python 3.11 or newer and the `notmuch` command (0.36 or newer, developed against 0.40). You do not need Node.
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
uv tool install notmuch-browser
|
|
13
|
+
notmuch-browser
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Or run it once without installing:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
uvx notmuch-browser
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The server listens on `http://127.0.0.1:8765/` and opens your browser. Options:
|
|
23
|
+
|
|
24
|
+
| Option | Meaning |
|
|
25
|
+
|---|---|
|
|
26
|
+
| `--port N` | listen on another port (default 8765) |
|
|
27
|
+
| `--notmuch-config FILE` | use a specific notmuch configuration file |
|
|
28
|
+
| `--no-browser` | do not open a browser window |
|
|
29
|
+
|
|
30
|
+
## Using it
|
|
31
|
+
|
|
32
|
+
Type a notmuch query in the search field and press Enter. The help button next to the field lists the common prefixes, and `man notmuch-search-terms` has the full syntax. The left rail holds saved searches (Inbox, Unread, Flagged, All mail) and every tag, with the number of unread threads loading in the background.
|
|
33
|
+
|
|
34
|
+
| Key | Action |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `/` | focus the search field |
|
|
37
|
+
| `j` / `k` | next and previous thread |
|
|
38
|
+
| `f` | flag or unflag the open thread |
|
|
39
|
+
| `t` | add a tag to the open thread |
|
|
40
|
+
|
|
41
|
+
Tag changes show an Undo button. Undo restores exactly the messages that the change touched, so a thread that was only partly flagged returns to its earlier state.
|
|
42
|
+
|
|
43
|
+
HTML mail is cleaned and shown in a sandboxed frame. Scripts, forms, styles and remote images are removed, so tracking pixels do not load. Inline images that belong to the message are shown. Attachments download as files.
|
|
44
|
+
|
|
45
|
+
The light and dark themes follow your system. The button in the top bar overrides it and the choice is remembered in the browser.
|
|
46
|
+
|
|
47
|
+
## What it does to your mail
|
|
48
|
+
|
|
49
|
+
Reading changes nothing. The only write is `notmuch tag`. It never indexes, sends, composes or deletes.
|
|
50
|
+
|
|
51
|
+
Read this if you use `maildir.synchronize_flags=true` (the notmuch default): changing the `unread`, `flagged`, `replied`, `draft` or `passed` tags renames the message files in your Maildir, because notmuch keeps the Maildir flags in step. If an IMAP sync tool also works on that Maildir, avoid tagging while it runs. If another program holds the database write lock, for example `notmuch new`, a tag change fails after a few seconds with a clear "database busy" message and changes nothing.
|
|
52
|
+
|
|
53
|
+
## Security model
|
|
54
|
+
|
|
55
|
+
- The server binds `127.0.0.1` only. There is no option to change that and there is no login.
|
|
56
|
+
- Requests with an unexpected `Host` header are refused, which stops DNS rebinding. Writes also need a matching `Origin` and a JSON content type, and cross-site requests are refused.
|
|
57
|
+
- The one cross-site request that is served is the image load of a message's inline `cid:` image from the sandboxed mail frame, because a browser labels that frame's requests cross-site. A foreign web page could use it only to learn that an image part exists and how large it is, and only if it already knew the message id.
|
|
58
|
+
- Attachments are always sent as downloads with a restrictive content security policy.
|
|
59
|
+
- On a shared machine other local users can reach the port. Do not run it there.
|
|
60
|
+
|
|
61
|
+
## Development
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
uv sync
|
|
65
|
+
npm ci
|
|
66
|
+
npm run build # compiles web/src into src/notmuch_browser/static
|
|
67
|
+
uv run pytest # tests use a throwaway database, never your real one
|
|
68
|
+
uv run notmuch-browser --notmuch-config "$(uv run python scripts/fixture_db.py /tmp/nb-fixture)"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`npm run dev` rebuilds on change. The Python server serves the built files, so reload the page.
|
|
72
|
+
|
|
73
|
+
`node scripts/shots.mjs <notmuch-config>` drives the real UI with Playwright against a fixture database, checks behaviour and writes screenshots.
|
|
74
|
+
|
|
75
|
+
### Releasing
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
scripts/release.sh
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
builds the bundle, then the wheel and sdist, and checks their contents. The bundle is not stored in git, so a wheel built without `npm run build` would have no UI. The build refuses to run in that case. The script does not upload anything. Publish with `uv publish` when you are ready.
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
|
|
85
|
+
MIT
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Hatch build hook: a package without the compiled UI must never be built."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class StaticBundleHook(BuildHookInterface):
|
|
9
|
+
def initialize(self, version, build_data):
|
|
10
|
+
if version == "editable":
|
|
11
|
+
return
|
|
12
|
+
index = Path(self.root) / "src" / "notmuch_browser" / "static" / "index.html"
|
|
13
|
+
if not index.is_file():
|
|
14
|
+
raise RuntimeError(
|
|
15
|
+
f"{index} is missing. Run `npm ci && npm run build` before building "
|
|
16
|
+
"the package (see scripts/release.sh)."
|
|
17
|
+
)
|