Skip to main content

hermes-inbox-organizer

Autonomous Gmail triage as an in-process Hermes plugin — it sorts, labels, and archives your mail and drafts replies in your voice, self-hosted, with no third-party SaaS in the path of your inbox.

CI PyPI Python 3.11+ License: MIT


What it does

A Hermes agent (NousResearch/hermes-agent) loads this plugin in-process and it runs a continual Gmail triage daemon: each new message is classified by a hybrid pre-classifier + OpenRouter LLM pipeline and gets a colored Fyxer-style numbered Gmail label (1: To Respond8: Marketing). Only 1: To Respond and 2: FYI stay in the inbox; the rest skip-inbox and archive. For 1: To Respond, the plugin asks Hermes to compose a reply draft in your voice (drafts only, never sent). When you reply, the thread moves to 7: Actioned; when you send and are waiting, 6: Awaiting Reply.

No separate service and no public webhook — it's one plugin that loads with the agent. It can also add an optional Inbox Organizer tab to Hermes's own web dashboard for connecting/removing accounts (docs/dashboard.md).

Capabilities

  • Multi-account Gmail (connect/disconnect by chatting with Hermes, or from an optional web dashboard tabdocs/dashboard.md)
  • Hybrid triage: deterministic header/sender rules + LLM fallback
  • Hermes-drafted replies for 1: To Respond
  • Sent-handling (Actioned / Awaiting Reply)
  • Draft reinforcement loop — learns from draft→sent deltas: distils per-sender voice notes + global do/don't lessons + gold-example replies into a separate auditable layer that feeds future drafting briefs (in-context learning-from-edits; no model fine-tuning)
  • On-demand unread rollup tool ("what needs me across my inboxes?")
  • Agent tools: inbox_create_draft, inbox_list_accounts, inbox_list_emails, inbox_get_email, inbox_get_thread, inbox_unread_rollup, inbox_connect_account / inbox_complete_connection / inbox_disconnect_account, inbox_draft_feedback_status, inbox_forget_lesson, inbox_clear_learned_notes

Quick Start

You need a Hermes deployment you control and a Google Cloud project (a one-time setup in your browser). Then:

# 1) Install (pip — this plugin needs the Gmail/Pub/Sub deps)
pip install "hermes-inbox-organizer[live]"
hermes plugins enable inbox_organizer

# 2) One-time Google Cloud setup (OAuth client + Pub/Sub)
#    → docs/google-bootstrap.md  (~10 min, in your browser)

# 3) Configure — the wizard writes the config files + keys
#    (auto-generates the encryption key; never prints secrets)
hermes-inbox-organizer setup
hermes-inbox-organizer status      # what's configured + capabilities

# 4) Restart Hermes, then in chat: "connect a Gmail account"

Already run a Hermes agent? Hand it HERMES_SELF_INSTALL.md and it'll do all of this for you. The full detail and the by-hand path are under Install.

How it works

  • Load: register(ctx) registers the agent tools + a pre_llm_call nudge hook and starts the daemon. A gateway:startup hook starts the daemon at boot (so it doesn't wait for the first agent turn).
  • Sync: Gmail watch() → Pub/Sub streaming pull (outbound connection — no webhook/tunnel) → drain history from a stored cursor → classify + label → wake Hermes to draft. A polling reconciler re-drains on a timer in case a push is dropped.
  • State: SQLite (state.db) in the Hermes data volume — history cursors, draft idempotency, classified messages, thread state, draft outcomes + learned lessons (schema v3, migrates in place on deploy). OAuth tokens are AES-256-GCM encrypted at rest.
  • Cost: the cheap local pre-classifier handles most mail; the LLM fallback (a small OpenRouter model) runs only when needed, so per-message cost is a fraction of a cent.

Install

The Quick Start is the short version; this is the full detail, including the by-hand config path if you'd rather not use the setup wizard.

This is a pip / entry-point Hermes plugin — it ships the hermes_agent.plugins entry point and your Hermes agent loads it in-process. It is not installed with hermes plugins install <repo>: that command git-clones a directory plugin and never installs Python dependencies, which this plugin needs (Gmail, Pub/Sub, OpenRouter). Use pip. For the discovery/enable model see the Hermes plugin docs.

You need a Hermes deployment you control. The three steps below are: set up Google Cloud, install + enable the plugin, then drop in its config.

1. Google Cloud

The plugin watches Gmail over Pub/Sub via an outbound streaming pull (no webhook, no public endpoint), so it needs a Google Cloud project with the Gmail + Pub/Sub APIs, an OAuth client, and a topic + pull subscription.

  1. Projectconsole.cloud.google.com → project selector → New Project (e.g. hermes-inbox).
  2. Enable APIs — APIs & Services → Library → enable Gmail API (gmail.googleapis.com) and Cloud Pub/Sub API (pubsub.googleapis.com).
  3. OAuth consent screen — APIs & Services → OAuth consent screen → External. Add scopes gmail.modify, gmail.send, userinfo.email, userinfo.profile; add your Gmail as a Test User; then Publish App (Production audience) to avoid the 7-day refresh-token expiry (docs/oauth-modes.md).
  4. OAuth client — Credentials → Create Credentials → OAuth client ID → Web application. For the Authorized redirect URI use a static page that just displays the code for you to paste back — host oauth-callback/ yourself or reuse https://inbox-organizer.northbound.run/ (it holds no secrets; the OAuth client itself must still be your own). Save the client id/secret for step 3.
  5. Pub/Sub — Pub/Sub → Topics → Create Topic (e.g. gmail-notifications); on that topic, Create Subscription with delivery type Pull (e.g. gmail-inbox-organizer-pull).
  6. Let Gmail publish — on the topic → Permissions → Add Principal gmail-api-push@system.gserviceaccount.com, role Pub/Sub Publisher.
  7. Subscriber key — IAM & Admin → Service Accounts → create one (e.g. hermes-inbox-pubsub), grant roles/pubsub.subscriber on the subscription (not project-wide), and download a JSON key.

(Screens and detail: docs/google-bootstrap.md.)

2. Install the plugin

Install into the same Python environment as your hermes CLI:

pip install "hermes-inbox-organizer[live]"
# or from source: pip install "hermes-inbox-organizer[live] @ git+https://github.com/Northbound-Run/hermes-inbox-organizer"

[live] pulls the Gmail/Pub/Sub/OpenRouter deps. Hermes auto-discovers the plugin via its entry point; enable it (pip plugins are opt-in):

hermes plugins enable inbox_organizer    # or add `inbox_organizer` to plugins.enabled in ~/.hermes/config.yaml

3. Config + secrets

The plugin reads its secrets as files from a config dir (INBOX_CONFIG_DIR, default /opt/data/config) and writes its SQLite DB + encrypted tokens to a data dir (INBOX_DATA_DIR, default /opt/data/inbox-organizer). Point both env vars at real paths if those defaults don't fit your host.

Easiest — the setup wizard. It prompts for the values from step 1, generates the encryption key, and writes everything into INBOX_CONFIG_DIR (mode 0600):

hermes-inbox-organizer setup     # or: python -m hermes_inbox_organizer setup
hermes-inbox-organizer status    # verify what's configured + capabilities

The wizard writes env-only values (the classifier API key + endpoint, optional HERMES_API_URL) to INBOX_CONFIG_DIR/inbox.env for you to wire into the Hermes environment (e.g. a compose env_file:).

Or by hand — install these files into INBOX_CONFIG_DIR:

File Contents
inbox-oauth-client.json { "client_id", "client_secret", "redirect_uri", "owner_matrix_ids": ["@you:your-homeserver"] } (from step 1.4)
inbox-pubsub.json { "project", "topic": "projects/<id>/topics/<topic>", "subscription" } (from step 1.5)
inbox-pubsub-sa.json the pubsub.subscriber service-account key (step 1.7)
inbox-encryption-key 32 hex bytes — openssl rand -hex 32 (AES-GCM for tokens at rest)

owner_matrix_ids is the owner allowlist for the connect/disconnect tools — gateway sender ids, not Matrix-only despite the name (use whatever source.user_id your channel reports). You can instead set it via INBOX_OWNER_MATRIX_IDS (comma-separated), which takes precedence. The gate is fail-closed: with neither set, even the owner can't connect a mailbox. Proactive module pushes (2FA, shipping) go to your Hermes /sethome home channel on whatever platform you use.

And in the Hermes environment:

  • Classifier LLM — the classifier speaks the OpenAI API, so it works with any compatible endpoint. By default it uses OpenRouter via OPENROUTER_API_KEY (openrouter.ai/keys; most Hermes setups already have one). For any other endpoint — a local vLLM/Ollama/LM Studio server, or another gateway — set INBOX_CLASSIFIER_API_KEY and INBOX_CLASSIFIER_BASE_URL instead, and optionally INBOX_CLASSIFIER_MODEL (default google/gemini-2.5-flash-lite). INBOX_CLASSIFIER_API_KEY takes precedence over OPENROUTER_API_KEY.
  • HERMES_API_URL (optional) — the Hermes OpenAI-compatible endpoint, needed only for the drafted replies; triage + labeling run without it.

Restart Hermes. To start the daemon at boot (instead of on the first agent turn), copy the gateway:startup hook from deploy/hooks/inbox-organizer-boot/ into ~/.hermes/hooks/inbox-organizer-boot/.

4. Connect a Gmail account

Two ways — same copy-paste OAuth either way:

  • Chat with Hermes"Connect a Gmail account." The agent returns a Google consent link; approve it, paste the code from the callback page back, and the account is hot-added (owner-gated). Repeat for additional mailboxes.
  • Dashboard tab — open Hermes's web dashboard and use the Inbox Organizer tab to connect/remove accounts with buttons instead of tools. If the tab isn't there yet, run hermes inbox-organizer install-dashboard once and restart hermes dashboard. See docs/dashboard.md.

Verify

hermes-inbox-organizer status                # config + capabilities at a glance
hermes plugins list                          # inbox_organizer → enabled
HERMES_PLUGINS_DEBUG=1 hermes plugins list   # verbose discovery if it doesn't show up

In a running session, /plugins lists it as loaded. Ask the agent to run inbox_list_accounts, then send yourself a test email — it should get a numbered label within seconds (push) or a few minutes (the polling reconciler).

Deploying into a baked container image (the daemon up at boot, secrets in a read-only mount)? That's how this repo's own stack runs — see docs/setup.md for the Dockerfile + entrypoint wiring.

Updating

pip install --upgrade "hermes-inbox-organizer[live]"

Then restart Hermes (or /reset in a CLI session) so the updated tools + schemas reload. The SQLite schema migrates in place on load, so cursors, tokens, and learned drafting state carry over; run hermes-inbox-organizer status to confirm.

Layout

  • hermes_inbox_organizer/ — the plugin package (the module), including its bundled dashboard/ web-UI plugin; with tests/, deploy/ (the gateway:startup boot hook), and pyproject.toml at the repo root.
  • oauth-callback/ — the static OAuth callback page (Cloudflare Pages) used by chat onboarding.
  • docs/ — setup, Google Cloud, OAuth, sync, security, and dashboard notes.

Develop

uv venv && uv pip install -e ".[dev]"
.venv/bin/python -m pytest -q     # tests run fully offline (network/Google/agent are seamed)
ruff check .                      # lint
mypy                              # type-check

This plugin follows the shared Hermes Plugin Standard (packaging, tooling, CI, release, and security conventions used across our Hermes plugins). See CONTRIBUTING.md before opening a PR.

License

MIT — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hermes_inbox_organizer-0.2.0.tar.gz (196.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hermes_inbox_organizer-0.2.0-py3-none-any.whl (146.8 kB view details)

Uploaded Python 3

File details

Details for the file hermes_inbox_organizer-0.2.0.tar.gz.

File metadata

  • Download URL: hermes_inbox_organizer-0.2.0.tar.gz
  • Upload date:
  • Size: 196.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for hermes_inbox_organizer-0.2.0.tar.gz
Algorithm Hash digest
SHA256 58206c618dbed59036513e69fba892e6def532759069bc758632a45ec1874245
MD5 a7b6313c557b7794cd81633c06ab9e00
BLAKE2b-256 9261458c98825c35aedd792b002a128e5b351c26dea9751886435342d02fce84

See more details on using hashes here.

Provenance

The following attestation bundles were made for hermes_inbox_organizer-0.2.0.tar.gz:

Publisher: release.yml on Northbound-Run/hermes-inbox-organizer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file hermes_inbox_organizer-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for hermes_inbox_organizer-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3da1709238cacbee6a50f814c1671336ddf56a0bc722f4be149b1be634f1e443
MD5 7d76ef9443f657f759f90dfab800e13d
BLAKE2b-256 e1b1e5a74503db1aa826eda24c72f690795796a1748879b0d4d5b2b266519796

See more details on using hashes here.

Provenance

The following attestation bundles were made for hermes_inbox_organizer-0.2.0-py3-none-any.whl:

Publisher: release.yml on Northbound-Run/hermes-inbox-organizer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page