Omahub
← All plugins
T

Mail Inbox

by themo

Unread mail across all your mailboxes in the Omarchy bar. Plain IMAP, so Gmail (app password) and any other account work through one code path. Click a message to read it in full and reply without leaving the bar.

Security review

Review recommended · 9 findings

Deterministic scan — not a security guarantee

Medium
Risk level
Medium
Analyzed commit
b025cb4
Scanned
4 weeks ago
  • medium package_manager …/workflows/ci.yml:40

    System package manager operation.

    apt-get update
  • medium package_manager …/workflows/ci.yml:41

    System package manager operation.

    apt-get install -y --no-install-recommends shellcheck qt6-declarative-dev-tools
  • medium package_manager …/workflows/ci.yml:38

    System-wide Python package installation (not --user).

    pip install --upgrade pip
  • medium package_manager …/workflows/ci.yml:39

    System-wide Python package installation (not --user).

    pip install pytest pytest-cov ruff
  • Command runs with sudo, elevating the process beyond the plugin environment.

    sudo apt-get update
  • Command runs with sudo, elevating the process beyond the plugin environment.

    sudo apt-get install -y --no-install-recommends shellcheck qt6-declarative-dev-tools
  • Docs external_hosts CONTRIBUTING.md:58

    Downloads or connects to an external HTTP(S) host.

    git clone https://github.com/<your-username>/omarchy-mail-inbox.git
  • Docs external_hosts CONTRIBUTING.md:98

    Downloads or connects to an external HTTP(S) host.

    git clone https://github.com/Mario-Mohar/omarchy-mail-inbox.git
  • Docs package_manager CONTRIBUTING.md:119

    System-wide Python package installation (not --user).

    pip install pytest ruff

Automated analysis only — not a security guarantee.

AI advisory review

No obvious issues detected

Language-model assessment · ~deepseek/deepseek-v4-flash-latest — advisory only

Low
AI risk level
Low
Recommendation
install
Model
~deepseek/deepseek-v4-flash-latest
Analyzed commit
b025cb4
Reviewed
4 weeks ago

The plugin is a well-engineered mail widget that reads IMAP and sends SMTP using only the Python standard library and the system keyring. The deterministic findings are all in CI configuration and contributor documentation, not in runtime code, and the runtime code shows deliberate security hardening (keyring-based passwords, bounded outputs, O_NOFOLLOW config reads, no command-line secrets). No obfuscation, persistence, or destructive behavior was found.

  • The plugin handles email credentials and can send mail, but this is the intended functionality and is implemented with keyring storage and stdin-based payloads to avoid exposing secrets.
  • The optional install script only creates symlinks in ~/.local/bin and the uninstall only removes its own symlinks; no system-wide changes.
  • The deterministic scan flagged external hosts and package-manager commands, but these appear only in CONTRIBUTING.md and the CI workflow, not in code executed on the user's machine.
How this check works

This review combines the deterministic scan (the rule-based results above) with an independent look at the plugin's code by a language model. The model reads a trimmed sample of the repository's files, the manifest, and the README, then gives a plain-language risk level and a recommendation: install (no notable danger), review (look closer first), or avoid (clearly dangerous).

It runs on the same analyzed commit as the deterministic scan and is strictly advisory — it is not a security guarantee and never blocks a plugin by itself. A human moderator still reviews plugins before they are listed.

AI advisory only — automated analysis, not a security guarantee.

Install
$ omarchy plugin add https://github.com/Mario-Mohar/omarchy-mail-inbox --enable
Productivity #bar #quickshell

Mail Inbox — Omarchy bar widget

codecov

One bar icon for all your mailboxes: the total unread count. Click it and the panel lists your mailboxes on the left with their individual counts — pick one to see its unread mail on the right. Adding a tenth address costs no bar space.

Click a message to read it in full and answer it without leaving the bar.

Straight IMAP, so Gmail (via app password) and any other account go through one code path. All mailboxes are polled in parallel; one broken account shows an error next to its name and leaves the others alone.

<p align="center"> <img src="preview.png" alt="The panel: a heading reading Mail, 3 unread across 3 mailboxes, a list of mailboxes on the left with their individual counts, and the selected mailbox's unread message on the right with sender, subject and time" width="660"> </p>

Install

omarchy plugin add https://github.com/Mario-Mohar/omarchy-mail-inbox --enable

Then add the widget to the bar through the Omarchy shell settings, or with omarchy plugin enable themo.mail-inbox right.

Optional, to get the two account commands onto your PATH:

~/.config/omarchy/plugins/themo.mail-inbox/install

The widget does not need that step — it calls the scripts by absolute path.

Requirements: Python 3 (standard library only, no extra packages) and secret-tool from libsecret for the keyring.

Setup

~/.config/omarchy/plugins/themo.mail-inbox/bin/mail-inbox-account add

The wizard asks for the sending side too, so replying works right after setup. Picking a preset settles it without a single question; entering a server by hand asks three, and answering n skips them if you only want to read. mail-inbox-account test <id> prints whether sending is configured.

Server details are written to ~/.config/omarchy/mail-inbox/accounts.json (mode 600). The password is stored in the login keyring under the service omarchy-mail-inbox and is never written to that file or passed on a command line.

For Gmail, create an app password at https://myaccount.google.com/apppasswords (needs 2-step verification enabled). Your normal Google password will not work over IMAP.

One "Mail Inbox" widget in the bar covers every account — no per-account setup in the widget itself. New mailboxes appear as soon as accounts.json changes; the widget watches the file.

Reading and replying

Clicking a message opens it in the right-hand pane: full text, sender, date and any attachment names, with a reply box underneath. Ctrl+Enter sends, Escape goes back to the list (and only closes the panel once the message is closed, so a stray Escape cannot lose a half-written reply).

Reading never touches the mailbox. \Seen is set only by the Mark read button, so the bar counter cannot drop just because you glanced at something. Sending a reply flags the original \Answered and files a copy in the account's Sent folder.

Replies go out as text/plain with the original quoted below, and carry In-Reply-To / References, so they thread properly in the recipient's client. Reply all appears only when the message actually has other recipients.

Only the first 256 KB of a message body are fetched — MIME puts the text parts before the attachments, so this costs nothing in practice and keeps a mail with a 20 MB attachment from stalling the panel. When it does bite, the panel says the message was truncated rather than pretending it showed everything.

Account fields

Field Needed for Example
host, port, user, folder reading imap.gmail.com, 993
smtpHost, smtpPort, smtpMode replying smtp.gmail.com, 465, ssl
from replying you@example.org
fromName optional display name Your Name
sentFolder filing sent replies Sent, [Gmail]/Gesendet
appendSent set false for Gmail Gmail files its own copy

smtpMode is ssl (implicit TLS, usually port 465) or starttls (usually 587); the wizard derives it from the port. These fields are filled in for you by mail-inbox-account add — the table is for editing an account that already exists, or for adding sending to one set up before version 3.3.0.

Commands

mail-inbox-setup                  # fill in missing passwords for all mailboxes
mail-inbox-account add            # add a mailbox, interactively
mail-inbox-account list           # show configured ids
mail-inbox-account test <id>      # poll once and print the result
mail-inbox-account remove <id>    # drop it and clear the keyring entry

mail-check --all --limit 12       # unread state of every mailbox, as JSON
mail-read --account <id> --uid N  # one message in full, as JSON
mail-mark --account <id> --uid N --flag seen [--remove]
mail-send < request.json          # the reply request arrives on stdin

mail-send reads its request from stdin rather than argv: the reply body would otherwise be readable in /proc by every process on the machine, and the rule that keeps passwords off the command line applies just as much to what you wrote.

Opening it without the mouse

The panel registers an IPC target, so it can be opened from a key binding or a script:

omarchy shell themo.mail-inbox toggle    # also: open, close, show, hide
omarchy shell themo.mail-inbox refresh   # poll every mailbox now

Widget settings

Setting Default Meaning
Refresh interval 300 s how often the mailboxes are polled
Messages in panel 12 how many unread messages per mailbox are listed
Hide when no unread mail off remove the icon while everything is read

Middle-click or scroll on the icon forces an immediate check. In the panel, Up/Down switch mailboxes and Enter re-checks.

Removing it

~/.config/omarchy/plugins/themo.mail-inbox/uninstall   # undo the PATH links
omarchy plugin remove themo.mail-inbox

Two things deliberately survive that, because they are yours and not the plugin's:

  • ~/.config/omarchy/mail-inbox/ — your mailbox list and its backups.
  • The keyring entries under the service omarchy-mail-inbox. List them with secret-tool search service omarchy-mail-inbox, remove one with secret-tool clear service omarchy-mail-inbox account <id>.

What it touches

Worth knowing before you install any plugin that reads your mail:

  • Network: IMAP and SMTP to the servers you configure, nothing else. No telemetry, no third-party service.
  • Credentials: read from the login keyring at the moment they are needed and passed to Python through the environment, never as a command-line argument and never written to disk.
  • Your mailbox: read-only except for two explicit actions — the Mark read button setting \Seen, and sending a reply, which flags the original \Answered and appends a copy to your Sent folder.
  • Files: ~/.config/omarchy/mail-inbox/accounts.json (mode 600) and its backups. Nothing outside that folder, apart from the optional PATH links.

Limits

Everything the widget reads comes either from a file on disk or off the network, so both sides are bounded rather than trusted:

accounts.json opened with O_NOFOLLOW; regular file, your ownership and mode 600 are checked on the same descriptor that is read from, not on the path. At most 256 KB, 50 accounts, 40 fields each.
Config writes created with O_EXCL at mode 600 and fsynced before the atomic replace, so there is no window in which the file exists with a wider mode. The directory must be yours and not group- or world-writable.
IMAP search the UID list is counted in one pass and only a bounded tail is kept, so a mailbox with a very large unread count costs the same memory as a small one.
Helper output capped at both ends: the scripts bound every string and list they emit, and the widget discards a response larger than 512 KB instead of parsing it.
Stuck helpers each poll, read and send has a 90 second watchdog that terminates it.
Displayed text every label renders as plain text, and strings that reach the shared bar tooltip are length-limited with angle brackets removed. Mail decides what is in a subject line.

Notes

Messages are addressed by UID together with the mailbox's UIDVALIDITY, never by sequence number. A poll landing between the click and the send therefore cannot redirect a reply onto a different message; if the server rebuilds the mailbox, the action is refused instead.

A failed poll keeps the last known counts on screen and marks the affected mailbox with ! in the panel list, rather than silently showing zero. The tooltip on the bar icon lists every mailbox with its count or its error.

Contributing

Bug reports, feature requests and pull requests are all welcome — finding something that is broken and writing it down is a real contribution, and the most useful one.

CONTRIBUTING.md has the details: what makes a report useful, how to send a fix through a fork, and what happens after you submit.