aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-13 18:06:28 +0200
committerDanilo M. <danix@danix.xyz>2026-09-13 18:06:28 +0200
commit2eb373726a0abe07741138616a9e2bc70a2f753b (patch)
tree6a41e27e2971edb74bc9f33d2ef024c63d0b019b
parentf438dd7c8c98699966d3da1cfafd95d299f6b613 (diff)
downloadquickshell-2eb373726a0abe07741138616a9e2bc70a2f753b.tar.gz
quickshell-2eb373726a0abe07741138616a9e2bc70a2f753b.zip
docs(mail-overview): document the arrival notifier
Why a revision counter rather than a count delta or a date watermark, why the state file carries a UUID, and why an unseeded run stays quiet. Also records the final notification presentation: the split heading, the absolute-path icon, and the show_indicators setting that keeps the action indicator off.
-rw-r--r--docs/superpowers/specs/2026-09-13-mail-arrival-notifications-design.md46
-rw-r--r--mail-overview/README.md56
2 files changed, 82 insertions, 20 deletions
diff --git a/docs/superpowers/specs/2026-09-13-mail-arrival-notifications-design.md b/docs/superpowers/specs/2026-09-13-mail-arrival-notifications-design.md
index 0b4db9b..93dc13c 100644
--- a/docs/superpowers/specs/2026-09-13-mail-arrival-notifications-design.md
+++ b/docs/superpowers/specs/2026-09-13-mail-arrival-notifications-design.md
@@ -120,39 +120,45 @@ The `label` is what the notification summary shows.
One per account with new mail:
- dunstify -a mail-overview -u normal -t 10000 \
+ dunstify -a "New Mail" -u normal -t 10000 -b \
+ -i "$MAIL_ICON" \
-h string:x-dunst-stack-tag:mail-<key> \
-A default,open \
- "<label> · N new" "<body>"
+ "<label> (<count>)" "<body>"
-Body: up to 3 rows of author and subject, then `+N more` when N > 3. Three
-matches the drawer's own `--limit=3`, and bounds the height so a 20-message
-mailing list burst does not become a wall.
+The heading is split to match the running dunst `format`, which renders `%a`
+(the app name) bold on the top line and `%s` (the summary) italic below it:
+the app name is `New Mail`, the summary is `<label> (<count>)`.
+
+Body: up to 3 bullet rows of author and subject, each on its own line with a
+blank line between, then `+N more` when N > 3. Three matches the drawer's own
+`--limit=3`, and bounds the height so a 20-message mailing list burst does not
+become a wall.
+
+`MAIL_ICON` is an absolute path, not an icon name. The running dunst resolves
+a themed icon name only through the `icon_path` in `dunstrc`, which holds no
+mail icon, so a name renders nothing; an absolute path under the active theme
+resolves, as the machine's other notifiers already do. It points at
+`${XDG_DATA_HOME:-$HOME/.local/share}/icons/MB-Blueberry-Suru-GLOW/actions/24/mail-unread-multiple.svg`.
+
+Because the notification carries `-A`, dunst prepends an `(A)` action
+indicator when `show_indicators` is on, so `dunstrc` sets
+`show_indicators = no`.
Normal urgency and an explicit 10s timeout. Deliberately not `-u critical`:
-on most dunst configurations critical notifications never expire, which would
-leave mail popups stuck on screen.
+on most dunst configurations critical notifications never expire, which
+would leave mail popups stuck on screen.
**Stack tag per account** means a second batch for the same account replaces
the first rather than stacking, which is what "one notification per account"
has to mean when mail keeps arriving.
**Click opens qtmaildir.** `-A default,open` plus `-b` makes dunstify block
-until the notification is dismissed or clicked and print the action key, so
+until the notification is dismissed or actioned and print the action key, so
each notification is launched in a backgrounded subshell that waits and runs
`~/bin/qtmaildir` on `default`. Without backgrounding, the loop would stall
-for the full timeout on every account.
-
-The click cannot open the *account* that was clicked: `qtmaildir` accepts no
-command line arguments, and `startup_account` in its config is a static
-setting rather than a flag. This is the same limitation the README already
-records for the drawer's thread rows, and it is accepted for the same reason.
-
-`dunstify` rather than `notify-send` because actions need it. Verified
-present, and dunst 1.12.2 lists both `actions` and `x-dunst-stack-tag` in
-`--capabilities`. If `dunstify` is absent the script falls back to
-`notify-send` with no click action, rather than failing: a notification
-without a click is still worth having.
+for the full timeout on every account. With the running dunst's
+`mouse_middle_click = do_action`, it is a middle click that invokes it.
## Failure handling
diff --git a/mail-overview/README.md b/mail-overview/README.md
index 68ad051..6e34140 100644
--- a/mail-overview/README.md
+++ b/mail-overview/README.md
@@ -120,6 +120,62 @@ Gmail API with stored credentials, covered three of the five accounts, and
opened Thunderbird. Everything it fetched over the network was already in the
local index.
+## Notifications on arrival
+
+`mail-notify.sh` sends one notification per account when mail lands, with the
+newest three senders and subjects and a `+N more` line when the batch is
+bigger. Each message is a bullet on its own line with a blank line between,
+under a bold `New Mail` heading with the account and count as the second line.
+Started from `autostart.lua`, it runs for the whole session.
+
+ /home/you/Programming/GIT/quickshell/mail-overview/mail-notify.sh
+
+It watches the same xapian directory the waybar module does, for the same
+reason and with the same 0.3s debounce. It is a **separate process rather
+than part of `waybar-mail.sh`**, which already has that edge: waybar owns
+that script's process, so a bar restart would stop notifications with nothing
+reporting it.
+
+**A commit is not the same as new mail.** Reading a message in qtmaildir drops
+its `unread` tag and commits; so does tagging. Arrival is found with notmuch's
+revision counter instead:
+
+ notmuch count --lastmod 'tag:unread and tag:inbox'
+
+which prints count, database UUID and revision, tab separated. Each account is
+then asked what it gained in `lastmod:<prev>..<cur>`. A count delta was
+rejected because it cannot name senders, and a `date:` watermark because
+`date:` is the message's own Date header: backdated mail would never notify
+and future-dated mail would notify forever.
+
+Position is kept in `~/.local/state/mail-notify.lastmod`, written by atomic
+replace, holding the **UUID as well as the revision**. Revisions only compare
+within one database, so a rebuild restarts the counter and a stored revision
+from the old one means nothing.
+
+**Missing, corrupt or mismatched state seeds silently**, recording the current
+revision and notifying nothing. Without that floor, `lastmod:0..` matches every
+unread message ever: 101 of them here, which is a wall of popups at every
+login. The same applies to a restart, so mail that arrived while it was down is
+never notified. The waybar count is still right and the drawer still shows it,
+so nothing is lost but a stale popup.
+
+The notification is sent with dunstify. The icon is passed as an **absolute
+path** to the active icon theme's `mail-unread-multiple`, because this dunst
+resolves an icon *name* only through the `icon_path` in `dunstrc`, and that
+path holds no mail icon, so a name renders no icon at all. The click action
+(`-A default,open`) makes dunst prepend an `(A)` action indicator when
+`show_indicators` is on, so `dunstrc` sets `show_indicators = no`. A middle
+click (dunst's `do_action`) opens qtmaildir, but not the account it belongs
+to: `qtmaildir` takes no arguments and `startup_account` is a static setting
+rather than a flag, the same limitation the thread rows have.
+
+`test-mail-notify.sh` is the check. It sources the script as a library and
+asserts on the functions that only move text around, so it needs no mail and no
+notmuch:
+
+ ./test-mail-notify.sh
+
## Geometry, and the one property that differs from the other panels
The window is a fullscreen overlay with a dimmed backdrop and the drawer itself