# mail-overview: notify on mail arrival New mail should announce itself. Today nothing does: `mailsync.sh` syncs and writes a status file, `waybar-mail.sh` updates a number in the bar, and the drawer shows detail only when opened. A message that lands while the user is looking at something else is silent. This adds one notification per account per arriving batch, from a new script in this component. ## What it is not It is not the quickshell notification daemon that would replace dunst. That remains deferred and is independent of this: notifications here are sent over the freedesktop DBus spec, so they work with dunst today and keep working unchanged if the daemon is ever swapped in. Nothing here should wait for it. ## Shape `mail-overview/mail-notify.sh`, bash, sibling to `waybar-mail.sh`, GPLv2 header like every other source file here. Started from `autostart.lua`, runs for the whole session. resolve db path from `notmuch config get database.path` guard: db dir missing -> complain on stderr, exit 1 seed state silently (no startup notification) while inotifywait -qq -e close_write,moved_to "$db"; do sleep 0.3 notify_new done complain, exit 1 The watch idiom is copied from `waybar-mail.sh` rather than shared. It is about six lines, and two copies of six lines beat an abstraction spanning a bar module and a notifier, which have different owners and different lifetimes. **Why a separate process rather than extending `waybar-mail.sh`.** That script already has the arrival edge, and reusing it would be the shortest diff. It was rejected because waybar owns that process: a waybar restart or a `hyprctl reload` would stop mail notifications with nothing reporting it. A notifier that silently stops is worse than one that costs a second inotify watch. Running it inside quickshell was also rejected: `FileView` watches files, not directories, and a watch on a file inside the Xapian directory dies on commit (the trap already recorded in AGENTS.md), so it would need a `Process` running `inotifywait` anyway. ## What counts as new A notmuch commit is not the same as new mail. Reading a message in qtmaildir drops its `unread` tag and commits; so does tagging. Arrival is detected with notmuch's own revision counter. notmuch count --lastmod 'tag:unread and tag:inbox' prints three tab-separated fields: count, database UUID, revision. The revision is field 3. Verified on notmuch 0.39. Per tick, if the revision has not advanced, there is nothing to do. Otherwise, per account: notmuch count "tag:unread and tag:inbox and tag:account- and lastmod:.." notmuch search --format=json --limit=3 --sort=newest-first "" Two calls, the same count-plus-preview pair `Accounts.qml` already makes, and only for accounts whose query is non-empty. The count gives the true N for "+N more"; the search gives the rows. `search --format=json` returns `authors` and `subject` directly, which is all the body needs. **Alternatives rejected.** A per-account count delta is simpler but cannot name senders, which was the point of the feature. A timestamp watermark using `date:@..` is quietly broken: `date:` is the message's Date header, so backdated mail never notifies and future-dated mail notifies forever. ## State `~/.local/state/mail-notify.lastmod`, alongside `mail-watcher.heartbeat` and `mailsync.log`. Written by atomic replace (tmpfile then rename), the same idiom the heartbeat uses. It stores the database UUID as well as the revision. notmuch revisions are only comparable within one database: a rebuilt database restarts the counter, and a stored revision from the old one would then be meaningless. A UUID mismatch is treated exactly like a missing file. **Missing, unparseable, or UUID-mismatched state seeds silently:** record the current revision, notify nothing. Without this, a first run has no floor, `lastmod:0..` matches every unread inbox message ever, and startup is a wall of popups. Measured here: 101 unread messages across five accounts. The same applies to a restart mid-session. Mail that arrived while the script was down is never notified. This is the right trade: the waybar count is still correct and the drawer still shows the mail, so nothing is lost except a popup that would have been stale anyway. The new revision is written **after** every account has been processed, so a failure mid-loop leaves `prev` unchanged and the next tick retries rather than dropping a batch silently. ## Accounts Parsed from `qtmaildir.conf`, the same file the drawer parses, so adding an account in qtmaildir makes it notify with no edit here. Two details carry over from `Accounts.qml` and are not optional: - **The key runs to the closing bracket, not to the first dot.** Real keys contain dots: a section like `[account.provider-first.last]` maps to the notmuch tag `account-provider-first.last`. Splitting on the first dot yields a tag that matches nothing, and an account that never notifies. - **Walk lines; never match "everything up to the next `[`".** Several accounts have folders named like `[Gmail]/Bozze`, which ends a section body before its `label` and makes the account display its raw key. The `label` is what the notification summary shows. ## The notification One per account with new mail: dunstify -a mail-overview -u normal -t 10000 \ -h string:x-dunst-stack-tag:mail- \ -A default,open \ "