Yandex Mail plugin for Hermes Agent: search, read, flag, move, and delete messages over IMAP.
Project description
hermes-yandex-mail
Let your Hermes Agent work through your Yandex inbox. "What came in overnight?" — "Read me the one from the bank." — "File everything from GitHub into Archive and mark it read." The agent works on your real mailbox, over IMAP, with no third-party service in the middle.
- 📬 Six tools, one toolset — list folders with unread counts, search, read (body plus attachment inventory), flag, move, delete.
- 🔒 You choose what it may touch — restrict it to specific folders, and to
specific actions (
read,read,write, …). A disallowed action is not in the toolset at all, so the model cannot be talked into calling it. - 🛟 Nothing is lost — a copy exists before an original is removed, deletion means Trash unless you insist otherwise, and an expunge only ever names the UIDs it was given.
- 👓 Reading does not mark as read — the agent peeks;
\Seenchanges only when you ask. - 🪶 No runtime dependencies — IMAP and MIME parsing come from Python's own standard library.
- 🔑 App password, not your account password — scoped to mail, revocable in one click.
Tested against Hermes 0.19.x, Python 3.11–3.13.
Quick start
# 1. Install into Hermes (alternatively: pip install hermes-yandex-mail)
hermes plugins install akinfold/hermes-yandex-mail --enable
# 2. Add your credentials — the app password comes from
# https://id.yandex.ru/security/app-passwords (scope: "Почта" / Mail)
printf 'YANDEX_MAIL_LOGIN=%s\nYANDEX_MAIL_APP_PASSWORD=%s\n' \
'you@yandex.ru' 'your-app-password' >> ~/.hermes/.env
Then enable it in ~/.hermes/config.yaml (third-party plugins are off by default):
plugins:
enabled: [yandex_mail]
Before the first run, switch IMAP on for the mailbox — see Enabling IMAP. Yandex refuses to log in otherwise, and the error looks exactly like a wrong password.
That's it. Ask the agent "anything unread in my inbox?" and it will tell you.
Not ready to hand over write access? Add
YANDEX_MAIL_ACTIONS=readand it can only look — see Restricting what the agent can do.
The tools
Up to six standalone tools, in the yandex_mail toolset:
| Tool | Purpose |
|---|---|
yandex_mail_list_folders |
List folders with their role (inbox, sent, trash, junk, drafts, archive) and total/unread counts. |
yandex_mail_search_messages |
Search a folder by sender, recipient, subject, full text, date range, unread or flagged state; returns subject, addresses, date, size, flags, and the uid. |
yandex_mail_read_message |
Read one message: headers, text body (HTML-only mail is converted to text), and the attachment list. Peeks by default. |
yandex_mail_mark_message |
Mark messages read/unread and flagged/unflagged. |
yandex_mail_move_message |
Move messages to another folder. |
yandex_mail_delete_message |
Delete messages — to Trash by default, permanently on request. |
Yandex Mail has no public REST API, so this plugin speaks IMAP
(imap.yandex.ru:993) directly — the same protocol Yandex documents for mail
clients. Nothing is proxied through anyone else's servers.
Messages are addressed by folder + uid. UIDs are unique within a folder and
change when a message moves, so pass back the folder each result reports. Several
UIDs can be given at once, comma-separated: "101,102".
This plugin does not send mail. IMAP reads and organises an existing mailbox; sending is SMTP, which is deliberately out of scope — the agent can triage your inbox but cannot mail anyone on your behalf.
Configuration
| Env var | Required | Default | Meaning |
|---|---|---|---|
YANDEX_MAIL_LOGIN |
yes | — | Yandex login / email. |
YANDEX_MAIL_APP_PASSWORD |
yes | — | App password with the Mail scope — an account password will not work. |
YANDEX_MAIL_IMAP_HOST |
no | imap.yandex.ru |
Override for a Yandex 360 domain or for testing. |
YANDEX_MAIL_IMAP_PORT |
no | 993 |
IMAP over TLS. |
YANDEX_MAIL_FOLDERS |
no | (all) | Comma-separated allow-list of folders, e.g. INBOX,Sent. The first is the default folder. |
YANDEX_MAIL_ACTIONS |
no | (all) | Comma-separated allow-list of actions the agent may perform — see below. |
Credentials are read from the environment first, then from ~/.hermes/.env, so
they work in gateway and subprocess runs. Secret values are never logged.
Dates are ISO 8601 (2026-07-25). Folder names may be in any language — the
Cyrillic names Yandex gives Russian accounts (Отправленные, Спам) are encoded
and decoded for you.
Restricting what the agent can do
YANDEX_MAIL_ACTIONS decides which of the six tools are registered at all. A
disallowed action is not merely refused at call time: the tool never appears in
the agent's toolset, so it cannot be invoked, and the model is not tempted to try.
Accepted values, comma-separated and case-insensitive — individual actions
(list_folders, search_messages, read_message, mark_message,
move_message, delete_message), full tool names
(yandex_mail_delete_message), or the shorthands:
| Shorthand | Expands to |
|---|---|
read |
list_folders, search_messages, read_message |
write |
mark_message, move_message |
delete |
delete_message |
all |
everything (the default) |
# Read the mail, change nothing:
YANDEX_MAIL_ACTIONS=read
# Full triage, but the agent can never delete anything:
YANDEX_MAIL_ACTIONS=read,write
# Just enough to report what is unread:
YANDEX_MAIL_ACTIONS=list_folders,search_messages
Leave it unset for all six tools. A name that matches nothing is ignored, so a typo can only ever withhold a tool, never grant one — and a value that names nothing recognisable therefore registers nothing at all. The list is applied when the plugin loads: restart Hermes after changing it.
Pair it with YANDEX_MAIL_FOLDERS to fence off the rest of the mailbox: with
YANDEX_MAIL_FOLDERS=INBOX, every other folder is invisible and unusable — as a
source and as a move destination.
Enabling IMAP in Yandex Mail
A mailbox that has never been used with a mail client does not accept IMAP
connections until you switch the protocol on. Until you do, every login fails
with [AUTHENTICATIONFAILED] ... invalid credentials or IMAP is disabled, which
reads exactly like a wrong password.
- Open https://mail.yandex.ru/#setup/client (⚙ Настройки → Почтовые программы).
- Tick «С сервера imap.yandex.ru по протоколу IMAP» — "allow access to the mailbox over IMAP".
- Save. It takes effect within seconds.
In a Yandex 360 organisation an administrator may have to allow mail clients for the whole domain first.
Getting the app password
IMAP does not accept your normal account password.
- Open https://id.yandex.ru/security/app-passwords.
- Add a password with the Почта (IMAP, SMTP) / Mail scope. A password created for another service — a CalDAV one, for instance — will not work here.
- Copy it into
YANDEX_MAIL_APP_PASSWORD. It is shown only once, and you can revoke it at any time without touching your account password.
If a tool answers "Authentication failed", it is one of these two things: IMAP is off, or the app password lacks the Mail scope.
Good to know
- A just-arrived message may not match a subject search for a second or two.
Yandex indexes
SEARCH SUBJECTasynchronously; searching bytext, or simply asking again a moment later, finds it. - UIDs change when a message moves. After a move, search the destination folder if you need the new identifier.
- Attachments are listed, not downloaded — name, MIME type, and size. The body is capped (20 000 characters by default) and says when it was truncated.
Installing the plugin into Hermes
Option A — from Git (recommended)
hermes plugins install akinfold/hermes-yandex-mail --enable
Option B — pip
pip install hermes-yandex-mail
Hermes discovers it through the hermes_agent.plugins entry point; add
yandex_mail to plugins.enabled.
Option C — drop-in directory
Unzip the release archive into ~/.hermes/plugins/ so you end up with
~/.hermes/plugins/yandex_mail/plugin.yaml, then enable it the same way.
Development
python -m venv .venv && source .venv/bin/activate
pip install -e '.[dev]'
ruff check . && ruff format --check .
pytest # unit tests, no network
Running the live E2E tests
The e2e-marked tests hit a real Yandex mailbox and are deselected by default.
They upload one throwaway message with a unique marker via IMAP APPEND, then
search, read, flag, move, and erase it — so a successful run leaves nothing
behind, and nothing is ever sent to anyone. Use a dedicated test mailbox all
the same.
Locally
YANDEX_MAIL_LOGIN=you@yandex.ru \
YANDEX_MAIL_APP_PASSWORD=xxxx \
pytest -m e2e
Or keep both out of the command line, in ~/.yandex-mail-login and
~/.yandex-mail-app-password, and just run pytest -m e2e — see
tests/e2e/conftest.py.
On GitHub Actions
The E2E (live) workflow is manual (workflow_dispatch). It reads
YANDEX_MAIL_LOGIN and YANDEX_MAIL_APP_PASSWORD from a GitHub Environment
named yandex-mail-e2e.
Contributing
Issues and PRs are welcome — see CONTRIBUTING.md for the layout, the plugin contract rules worth knowing, and the release process.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hermes_yandex_mail-0.1.0.tar.gz.
File metadata
- Download URL: hermes_yandex_mail-0.1.0.tar.gz
- Upload date:
- Size: 38.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
80dc5505846a69dd597e0e7dae4e62335f8a97ae31ea7c0d1239a7d0578b9a01
|
|
| MD5 |
4729ca4c5bb2e0553f0ce7b2ba29d4da
|
|
| BLAKE2b-256 |
eb0829798985eaf2b233d824f592ed3f675f8e3fa2877539d4a9c0a2500672b5
|
Provenance
The following attestation bundles were made for hermes_yandex_mail-0.1.0.tar.gz:
Publisher:
release-publish.yml on akinfold/hermes-yandex-mail
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hermes_yandex_mail-0.1.0.tar.gz -
Subject digest:
80dc5505846a69dd597e0e7dae4e62335f8a97ae31ea7c0d1239a7d0578b9a01 - Sigstore transparency entry: 2255238057
- Sigstore integration time:
-
Permalink:
akinfold/hermes-yandex-mail@81361356b63f074abe540c2888d877b5013c5b3c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/akinfold
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-publish.yml@81361356b63f074abe540c2888d877b5013c5b3c -
Trigger Event:
push
-
Statement type:
File details
Details for the file hermes_yandex_mail-0.1.0-py3-none-any.whl.
File metadata
- Download URL: hermes_yandex_mail-0.1.0-py3-none-any.whl
- Upload date:
- Size: 26.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6d90b48cd4ab3d2d7d84c9a1c5613f0404cb9448b5a528737e1640c2a6e83189
|
|
| MD5 |
a02cb000b12c6d0be6caa2124fab4c8c
|
|
| BLAKE2b-256 |
2c98026ad0f40d86f53701d5f426a4c3a2467fc32807f9eb97d4a508bfefa845
|
Provenance
The following attestation bundles were made for hermes_yandex_mail-0.1.0-py3-none-any.whl:
Publisher:
release-publish.yml on akinfold/hermes-yandex-mail
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hermes_yandex_mail-0.1.0-py3-none-any.whl -
Subject digest:
6d90b48cd4ab3d2d7d84c9a1c5613f0404cb9448b5a528737e1640c2a6e83189 - Sigstore transparency entry: 2255238065
- Sigstore integration time:
-
Permalink:
akinfold/hermes-yandex-mail@81361356b63f074abe540c2888d877b5013c5b3c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/akinfold
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-publish.yml@81361356b63f074abe540c2888d877b5013c5b3c -
Trigger Event:
push
-
Statement type: