diff options
| -rw-r--r-- | docs/superpowers/specs/2026-09-13-mail-arrival-notifications-design.md | 46 | ||||
| -rw-r--r-- | mail-overview/README.md | 56 |
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 |
