Skill: aggrrrh/imap-client (version 0.1.3)

--- name: imap-client description: 'Read, search, and download email over IMAP from the command line using the `myl` CLI client. Use this skill whenever the user wants to interact with their mailbox from a terminal — checking the inbox, listing or searching messages, reading a specific email, opening HTML or raw source, or saving attachments. Trigger on any of these cues even when `myl` is not named explicitly — "check my email", "look in my inbox", "search my mail for X", "find the email from Y", "download the attachment", "is there an email about Z", "read the latest message", "show me unread", "connect to my IMAP server", "imap.gmail.com", "imap.yandex.com", "imap.yandex.ru", "imap.mail.ru", "imap.fastmail.com", "Yandex Mail", "Mail.ru", "Gmail IMAP", "проверить почту", "новые письма", "найти письмо", and similar. Also trigger when the user asks to script or automate any of the above. Do not trigger for outgoing mail (sending, SMTP, drafting) — `myl` is read-only — or for desktop/GUI mail clients.' license: MIT homepage: https://github.com/codd-tech/imap-client metadata: {"openclaw":{"emoji":"📬","requires":{"bins":["myl"],"env":["IMAP_USER","IMAP_PASSWORD"]},"primaryEnv":"IMAP_PASSWORD","install":[{"id":"pipx","kind":"pipx","package":"myl","bins":["myl"],"label":"Install myl via pipx"}]}} --- # imap-client Read mailboxes over IMAP from the terminal using `myl`, a small Python CLI client. Maintained and distributed by [codd-tech](https://github.com/codd-tech/imap-client). Designed to drop into [OpenClaw](https://openclaw.ai) and any other AgentSkills-compatible runtime (Claude Code, generic). `myl` is read-only and intentionally minimal: it lists, searches, and fetches messages and attachments. It does not send mail, manage folders, or modify state beyond optionally marking messages as seen. ## How credentials reach this skill This is the most important section. **You do not type passwords on the command line.** Credentials live in environment variables that the runtime injects per agent run. The skill reads them and assembles the right `myl` flags through the wrapper at `{baseDir}/scripts/imap.sh`. The variables the wrapper expects: | Variable | Required | Purpose | |---|---|---| | `IMAP_USER` | yes | Login (usually full email address) | | `IMAP_PASSWORD` | yes | App-specific password (see `references/authentication.md`) | | `IMAP_PROVIDER` | no | One of `auto` (default), `gmail`, `yandex`, `mailru`, `manual` | | `IMAP_SERVER` | only with `manual` | IMAP host | | `IMAP_PORT` | no | Defaults to 993 | | `IMAP_STARTTLS` | no | `1` to add `--starttls` (use only with port 143) | **Set them once, use them every session.** How depends on the runtime — `references/authentication.md` covers OpenClaw's `skills.entries.imap-client.env`, generic shell `export`, and a `~/.config/imap-client/credentials` fallback file. Do not invent your own scheme; use one of those three. If the wrapper detects `IMAP_USER` or `IMAP_PASSWORD` is missing, it prints the setup instructions and exits without contacting any server. That's the signal to stop and walk the user through credential setup before retrying. ## Workflow at a glance 1. **Check that `myl` is installed.** OpenClaw gates this skill on `requires.bins: ["myl"]`, so it shouldn't load without it. For non-OpenClaw runtimes, run `bash {baseDir}/scripts/check_myl.sh`. If missing, follow `references/installation.md`. 2. **Confirm credentials are configured.** Run `bash {baseDir}/scripts/imap.sh --count 1 >/dev/null` once. Success means the env vars are wired and the connection works. Failure means walk the user through `references/authentication.md`. 3. **Run the requested operation** through the wrapper. Listing, searching, fetching by ID, getting HTML, saving raw `.eml`, or pulling an attachment. 4. **Summarise the result.** Don't dump full raw email bodies into the chat unless the user asked. Every `myl` example in this skill goes through `{baseDir}/scripts/imap.sh`, which expands env vars into the right `myl` flags. You do not need to remember `--google` vs `--auto` vs `--server`/`--port`; the wrapper picks based on `IMAP_PROVIDER`. ## When to read what | Task involves… | Read | |---|---| | Detecting or installing `myl`, OpenClaw `requires.bins` gating | `references/installation.md` | | Setting up credentials, choosing connection mode, app passwords for Gmail / Yandex / Mail.ru / iCloud / Fastmail | `references/authentication.md` | | Any specific CLI flag, listing, searching, fetching, attachments, provider-specific folder names | `references/operations.md` | | Multi-step recipes (e.g. "find the invoice from Acme last month and save the PDF") | `references/recipes.md` | | Errors like SSL failures, "command not found", autodiscovery failing, "AUTHENTICATIONFAILED", env vars not visible to the wrapper | `references/troubleshooting.md` | ## Principles ### 1. Credentials must not appear in agent-generated commands or logs **Do not** generate commands like `myl --password hunter2` or `myl --password "$IMAP_PASSWORD"` directly — the first hardcodes the secret in shell history and conversation logs; the second exposes it there too and adds no benefit over the wrapper. Always use the wrapper: ```bash bash {baseDir}/scripts/imap.sh --count 5 ``` The wrapper reads credentials from env vars and passes them to `myl` via `--username`/`--password` flags. This means the password **is** briefly visible in `/proc/<pid>/cmdline` and `ps` output to other processes on the same host while `myl` runs — the same exposure as running `myl` directly with env-var expansion. The wrapper's benefit is narrower: the password value never appears in commands the agent generates, in shell history, or in conversation logs. On shared or multi-user machines this argv exposure should be understood as a residual risk. ### 2. Default to small result sets When the user's intent is exploratory ("any new mail?"), pass `--count 5` or `--count 10`. Only fetch larger windows on explicit request. This keeps output readable and avoids dumping sensitive content the user didn't ask to see. ### 3. Don't mark as seen by accident `--mark-seen` mutates state on the server. Only pass it when the user explicitly asked to mark messages read. Listing or reading without this flag is non-destructive. ### 4. Render long bodies to a file, summarise in chat When the user fetches a long message or HTML email, save the raw output to a file (e.g. `/tmp/email-<id>.eml` or `.html`) and give the user a 2–4 sentence summary plus the file path. Do not paste a 500-line HTML body into the conversation. ### 5. Search syntax is server-side IMAP, not Gmail's web UI `--search "important"` issues an IMAP `SEARCH` command. It does not understand Gmail's `from:`, `has:attachment`, or `label:` operators. For complex filtering, fetch a reasonable window with `--count` and filter the listing locally. See `references/operations.md` for what IMAP `SEARCH` supports. ### 6. Never echo, summarise, or persist the password When summarising what you did, refer to the credential as `IMAP_PASSWORD` or "the password from your OpenClaw config", never the literal value. If the user pastes a password into chat by mistake, treat it as compromised: tell them to rotate it and update their config. Do not write it to any artifact. ## Quick decision tree ``` User asked something email-related from the CLI │ ├─ Is `myl` installed and on PATH? ── No ──► references/installation.md │ │ │ Yes │ ▼ ├─ Does the wrapper smoke-test pass? │ bash {baseDir}/scripts/imap.sh --count 1 >/dev/null │ │ No ──► references/authentication.md │ Yes │ ▼ ├─ What does the user want? │ ├─ Browse / list ──► imap.sh --count N [--folder F] │ ├─ Search ──► imap.sh --search "TERM" [--count N] │ ├─ Read one message ──► imap.sh "$MAILID" │ ├─ Read HTML version ──► imap.sh --html "$MAILID" → save to file │ ├─ Save raw .eml ──► imap.sh --raw "$MAILID" > file.eml │ ├─ Get attachment ──► imap.sh "$MAILID" "$ATT_NAME" > file │ └─ Anything multi-step ──► references/recipes.md │ └─ Errors? ─────────────────────► references/troubleshooting.md ``` ## Output style After running the wrapper, present results in this shape: - **One-line status** of what just ran (e.g. "Listed the 10 most recent messages in INBOX"). - **A compact table or bullet list** of message metadata (date, from, subject, ID). - **Any file paths** where larger output was saved. - **Suggested next actions** (e.g. "Want me to open #4582 or save its attachments?"). Keep it scannable.

path: README.md
content: # imap-client

[![Install via ClawHub](https://img.shields.io/badge/install-clawhub-2563eb?style=flat-square)](https://clawhub.ai/aggrrrh/imap-client)
[![License: MIT](https://img.shields.io/github/license/codd-tech/imap-client?style=flat-square)](./LICENSE)
[![Maintained by codd.tech](https://img.shields.io/badge/maintained%20by-codd.tech-f97316?style=flat-square)](https://codd.tech)
[![Stars](https://img.shields.io/github/stars/codd-tech/imap-client?style=flat-square)](https://github.com/codd-tech/imap-client/stargazers)

An agent skill for [OpenClaw](https://openclaw.ai), Claude Code, and other AgentSkills-compatible runtimes. Lets the agent read, search, and download email over IMAP from the command line via the [`myl`](https://github.com/pschmitt/myl) CLI client.

`myl` is a small read-only IMAP client. This skill teaches the agent **when** to reach for it, **how** to install it, **how to source credentials safely** from the runtime's environment-injection mechanism, and **which** flags to use for common tasks — without ever asking for the password mid-session.

## What this skill enables

Once installed and configured once, the agent will recognise prompts like:

- *"check my inbox"*
- *"any new email from Acme today?"*
- *"find the email with the AWS invoice and save the PDF"*
- *"show me the HTML version of the newsletter from yesterday"*
- *"download all unread messages as `.eml` files"*
- *"проверь почту на Яндексе"*
- *"найди письмо от налоговой"*

…and translate them into safe `myl` invocations through the wrapper at `scripts/imap.sh`, summarising the result back in chat.

## Provider support

First-class support, with `IMAP_PROVIDER` shortcuts:

- **Gmail** / Google Workspace (`IMAP_PROVIDER=gmail`)
- **Yandex Mail** — `@yandex.ru`, `@yandex.com`, Yandex 360 custom domains (`IMAP_PROVIDER=yandex`)
- **Mail.ru** — `@mail.ru`, `@bk.ru`, `@inbox.ru`, `@list.ru` (`IMAP_PROVIDER=mailru`)

Plus autodiscovery for most other providers (Fastmail, iCloud, ISPs) and explicit `manual` mode for self-hosted / corporate servers.

## Quick start

### 1. Install `myl`

```bash
pipx install myl
```

(Or `pip install --user myl`, or `nix run github:pschmitt/myl`.)

### 2. Get an app-specific password from your provider

Account settings → app passwords / external app passwords. Direct links per provider in [`references/authentication.md`](./references/authentication.md).

### 3. Install the skill

**Recommended — via ClawHub:**

```bash
clawhub install imap-client
```

**Or clone directly:**

```bash
# OpenClaw
git clone https://github.com/codd-tech/imap-client ~/.openclaw/skills/imap-client

# Claude Code
git clone https://github.com/codd-tech/imap-client ~/.claude/skills/imap-client

# Generic AgentSkills runtime — drop the folder anywhere the runtime scans for SKILL.md
```

Restart the session. OpenClaw picks the skill up automatically. The skill declares `requires.bins: ["myl"]` so it filters itself out if `myl` isn't on `PATH` — you'll never get a half-broken state.

### 4. Configure credentials — pick the method for your runtime

**OpenClaw** (recommended for OpenClaw users) — edit `~/.openclaw/openclaw.json`:

```json
{
  "skills": {
    "entries": {
      "imap-client": {
        "enabled": true,
        "env": {
          "IMAP_USER": "[REDACTED_SECRET]",
          "IMAP_PASSWORD": "app-specific-password-here",
          "IMAP_PROVIDER": "yandex"
        }
      }
    }
  }
}
```

```bash
chmod 600 ~/.openclaw/openclaw.json
```

OpenClaw injects these into `process.env` per agent run. You configure once; every subsequent session has them.

**Claude Code / generic shell** — export in `~/.bashrc` / `~/.zshrc`:

```bash
export IMAP_USER='[REDACTED_SECRET]'
export IMAP_PASSWORD='app-specific-password-here'
export IMAP_PROVIDER='yandex'
```

**Headless / cron / fallback** — create `~/.config/imap-client/credentials`:

```bash
mkdir -p ~/.config/imap-client
cat > ~/.config/imap-client/credentials <<'EOF'
IMAP_USER='[REDACTED_SECRET]'
IMAP_PASSWORD='app-specific-password-here'
IMAP_PROVIDER='yandex'
EOF
chmod 600 ~/.config/imap-client/credentials
```

The wrapper refuses to source this file if its permissions are not `600` or `400`.

### 5. Verify

Ask the agent something like *"check the latest five emails"* — or run the wrapper directly:

```bash
bash ~/.openclaw/skills/imap-client/scripts/imap.sh --count 5
```

## Repo layout

```
imap-client/
├── SKILL.md                       # Frontmatter + workflow + principles
├── README.md                      # This file
├── LICENSE                        # MIT
├── .gitignore
├── references/
│   ├── installation.md            # pipx / pip / nix / source install paths + sandbox
│   ├── authentication.md          # OpenClaw env injection + fallbacks; provider table
│   ├── operations.md              # Full myl flag reference + folder names per provider
│   ├── recipes.md                 # 11 multi-step workflows, including Yandex/Mail.ru
│   └── troubleshooting.md         # Symptom-first error guide
└── scripts/
    ├── imap.sh                    # Credential-aware wrapper around myl
    └── check_myl.sh               # Fast install detection
```

## Security model

The skill is opinionated about credentials. The short version:

- Passwords never appear as literals in commands the agent generates.
- The runtime's environment-injection mechanism (OpenClaw `skills.entries.<key>.env`, or shell `export`, or the `chmod 600` creds file) is the source of truth.
- The `imap.sh` wrapper reads env vars and constructs `myl` flags internally — the password is in the wrapper's process scope, never in the agent's command history.
- App-specific passwords from each provider are the default recommendation; the skill explains where to generate them (Gmail, Yandex, Mail.ru, iCloud, Fastmail, Yahoo).
- The agent is instructed not to echo, summarise, or persist the password anywhere.
- For OpenClaw users, `apiKey` with a `SecretRef` (`{ source, provider, id }`) keeps the literal password out of `openclaw.json` entirely.

See [`references/authentication.md`](./references/authentication.md) for the full ruleset.

## Limitations of `myl` itself

`myl` is intentionally minimal. The skill will tell the user explicitly when their request is out of scope and suggest alternatives:

| Want to… | Use instead |
|---|---|
| Send mail | `msmtp`, `mutt`, scripted SMTP |
| Move / delete / label | `imap-tools`, the provider's web UI |
| Sync to local maildir | `mbsync` (`isync`), `offlineimap`, `getmail` |
| OAuth2 to Gmail / Outlook | Proton Bridge, `mbsync` + XOAUTH2, or app password fallback |

## Contributing

Bug reports and PRs welcome. The skill itself is markdown plus two shell scripts — easy to read, easy to fork.

When proposing changes, prefer:

- additions to `references/` over additions to `SKILL.md` (keep the always-loaded part lean)
- examples that don't paste credentials anywhere
- behaviour changes that fail safely if the user's `myl` version is older than the skill assumes

## Credits

- [`myl`](https://github.com/pschmitt/myl) by Philipp Schmitt — the underlying CLI this skill wraps.
- [OpenClaw](https://openclaw.ai) for the AgentSkills runtime, env injection mechanism, and skills format documented at https://docs.openclaw.ai/tools/skills.
- Anthropic's [Skills documentation](https://docs.claude.com/en/docs/build-with-claude/skills) — structure and best-practice patterns.

## About the maintainer

Built and maintained by **[codd.tech](https://codd.tech)** — a boutique technical consultancy specialising in AI agent infrastructure, distributed systems, and platform engineering. We help product teams ship reliable agentic workflows in production: integration design, custom skill packs, OpenClaw / Claude / MCP deployments, and DevOps audits.

Have a use case for agent-driven email automation, or want a custom skill built for your stack? **[Get in touch](https://codd.tech)**.

## License

MIT — see [`LICENSE`](./LICENSE).


path: references/authentication.md
content: # Authentication & Connection

This is the most security-sensitive part of the skill. Read it before configuring credentials anywhere.

## The model in one paragraph

Credentials live as **environment variables** that the runtime injects per agent run. The wrapper at `scripts/imap.sh` reads those env vars, picks the right `myl` connection flags, and never echoes the password. **You configure once, you read mail forever.**

## Setting up credentials — pick one method

### Method A — OpenClaw (recommended for OpenClaw users)

OpenClaw injects `skills.entries.<key>.env` into `process.env` for the duration of each agent turn, then restores the original environment. This is documented behaviour: see https://docs.openclaw.ai/tools/skills under "Environment injection (per agent run)".

Edit `~/.openclaw/openclaw.json` and add an entry under `skills.entries`:

```json
{
  "skills": {
    "entries": {
      "imap-client": {
        "enabled": true,
        "env": {
          "IMAP_USER": "[REDACTED_SECRET]",
          "IMAP_PASSWORD": "app-specific-password-here",
          "IMAP_PROVIDER": "auto"
        }
      }
    }
  }
}
```

Then restart the agent session (or wait for the skills watcher to pick it up if `skills.load.watch` is enabled). The next time the agent runs the skill, `IMAP_USER` and `IMAP_PASSWORD` are already in the environment.

**Permissions matter.** `~/.openclaw/openclaw.json` should not be world-readable:

```bash
chmod 600 ~/.openclaw/openclaw.json
```

**Use `apiKey` with a SecretRef for stronger isolation.** OpenClaw supports pulling the password from a separate source rather than inlining it as plaintext in the JSON:

```json
{
  "skills": {
    "entries": {
      "imap-client": {
        "enabled": true,
        "apiKey": { "source": "env", "provider": "default", "id": "MY_IMAP_PASSWORD" },
        "env": {
          "IMAP_USER": "[REDACTED_SECRET]",
          "IMAP_PROVIDER": "auto"
        }
      }
    }
  }
}
```

The `apiKey` field maps to whatever env var name is declared in `metadata.openclaw.primaryEnv` of `SKILL.md` — for this skill that's `IMAP_PASSWORD`. So OpenClaw reads `MY_IMAP_PASSWORD` from your shell env (or another secret backend) and exposes it as `IMAP_PASSWORD` to the wrapper. The literal password never appears in `openclaw.json`.

### Method B — Generic shell `export`

For Claude Code and other AgentSkills runtimes that don't have OpenClaw's injection mechanism, just export the variables in the shell that launches the agent:

```bash
export IMAP_USER='[REDACTED_SECRET]'
export IMAP_PASSWORD='app-specific-password-here'
export IMAP_PROVIDER='auto'
```

Put this in `~/.bashrc` / `~/.zshrc` if you want it to persist. The downside compared to Method A is that the variables are global to that shell, not scoped to the agent run. The upside is no extra config file.

### Method C — Credentials file fallback

When neither Method A nor B is convenient (e.g. cron jobs, headless workflows, CI), drop a credentials file at `~/.config/imap-client/credentials`:

```bash
mkdir -p ~/.config/imap-client
cat > ~/.config/imap-client/credentials <<'EOF'
IMAP_USER='[REDACTED_SECRET]'
IMAP_PASSWORD='app-specific-password-here'
IMAP_PROVIDER='auto'
EOF
chmod 600 ~/.config/imap-client/credentials
```

The wrapper sources this file when `IMAP_USER`/`IMAP_PASSWORD` are not in the env, **but only if permissions are 600 or 400**. World-readable creds files are ignored with a warning.

To use a different path, set `IMAP_CREDENTIALS_FILE` in env.

## Per-account: switching mailboxes

For multiple accounts, declare each one as a separate OpenClaw skill entry under a different agent (per-agent skill allowlists make this clean), or scope the env per shell:

```bash
# Work
( export IMAP_USER='[REDACTED_SECRET]' \
         IMAP_PASSWORD="$WORK_APP_PASSWORD" \
         IMAP_PROVIDER='auto' ; \
  bash scripts/imap.sh --count 5 )

# Personal
( export IMAP_USER='[REDACTED_SECRET]' \
         IMAP_PASSWORD="$YANDEX_APP_PASSWORD" \
         IMAP_PROVIDER='yandex' ; \
  bash scripts/imap.sh --count 5 )
```

The parentheses create a subshell so the exports don't pollute your main session.

## `IMAP_PROVIDER` — what to set

| Value | Effect | When to use |
|---|---|---|
| `auto` (default) | `myl --auto` — autodiscovery from username domain | Most modern providers (Fastmail, iCloud, ISPs) |
| `gmail` | `myl --google` — hardcoded Gmail IMAP | `@gmail.com` / Google Workspace accounts |
| `yandex` | `--server imap.yandex.com --port 993` | Yandex Mail (`@yandex.ru`, `@yandex.com`, custom domains) |
| `mailru` | `--server imap.mail.ru --port 993` | Mail.ru (`@mail.ru`, `@bk.ru`, `@inbox.ru`, `@list.ru`) |
| `manual` | `--server $IMAP_SERVER --port $IMAP_PORT [--starttls]` | Self-hosted, corporate, or anything autodiscovery doesn't recognise |

## App passwords — the credential the user actually needs

Almost no major provider accepts the account password over IMAP anymore when 2FA is enabled. They require an **app-specific password** generated from the account settings.

| Provider | Where to generate | Notes |
|---|---|---|
| **Gmail / Google Workspace** | https://myaccount.google.com/apppasswords | Requires 2-Step Verification on. If the link 404s, your account or organisation has app passwords disabled — switch provider or ask admin. |
| **Yandex Mail** | https://id.yandex.ru/security/app-passwords | Pick "Mail (IMAP/POP3, SMTP)". Works for `@yandex.ru`, `@yandex.com`, and custom domains hosted on Yandex 360. |
| **Mail.ru** | Account → "Пароли для внешних приложений" / "Passwords for external applications" | Same password works for `@mail.ru`, `@bk.ru`, `@inbox.ru`, `@list.ru`. |
| **Fastmail** | Settings → Privacy & Security → App Passwords | Can scope to "IMAP only" for least privilege. |
| **iCloud** | https://appleid.apple.com → Sign-In and Security → App-Specific Passwords | Username is your Apple ID email, even if you use an `@icloud.com` alias. |
| **Yahoo** | Account Security → Generate app password | |
| **Proton Mail** | Requires Proton Bridge running locally | Use `IMAP_PROVIDER=manual`, `IMAP_SERVER=127.0.0.1`, `IMAP_PORT=1143`. |
| **Outlook.com** | Microsoft has been deprecating basic auth | If app passwords are disabled, use `mbsync` with XOAUTH2 instead. `myl` does not do OAuth2. |

When the IMAP server returns `AUTHENTICATIONFAILED` and the username is one of the providers above, the cause is almost always that the user is trying their account password instead of an app password. Direct them to the relevant URL and explain why.

## Yandex specifics

Yandex Mail is widely used in Russian-speaking contexts and has a few quirks worth knowing:

- **Two server hostnames exist.** `imap.yandex.com` and `imap.yandex.ru` both work; `IMAP_PROVIDER=yandex` defaults to `.com` which serves both account types correctly. To force `.ru`, set `IMAP_SERVER=imap.yandex.ru`.
- **Mailbox features must be enabled in Yandex web UI.** Settings → "Почтовые программы" → tick "С сервера imap.yandex.ru по протоколу IMAP". Without this, IMAP login fails with `AUTHENTICATIONFAILED` even with the right app password.
- **Yandex 360 / business accounts (`@your-company.ru` hosted on Yandex)** use the same `imap.yandex.com:993` endpoint and the same app password mechanism.
- **Folder names are localised.** See `references/operations.md` for the Russian folder name table.

## Mail.ru specifics

- **One app password covers the whole domain group** — the same credential works against `@mail.ru`, `@bk.ru`, `@inbox.ru`, `@list.ru` if you own multiple addresses on the platform.
- **IMAP must be explicitly enabled in account settings.** Mail.ru settings → "Все настройки" → "Почтовые программы" → enable IMAP. Same gotcha as Yandex.

## What to do if the user pastes a password into chat

Treat it as compromised. The password may now sit in:

- the chat transcript / conversation log
- any analytics or telemetry the runtime captured
- the user's clipboard history

**Tell the user explicitly:** "That password is now in the chat history. Rotate it (regenerate the app password from the provider) and put the new one into your `~/.openclaw/openclaw.json` config — not into chat." Then walk them through Method A above.

Do not echo the password back, do not write it to any file, do not include it in a summary, and do not silently reuse it for the rest of the session.

## Smoke test

Once credentials are configured, confirm the connection works with a minimal call:

```bash
bash scripts/imap.sh --count 1 >/dev/null && echo "connection OK"
```

If this prints `connection OK`, every other operation in `references/operations.md` will work the same way.


path: references/installation.md
content: # Installation

`myl` is a Python package. There are several ways to install it; pick the first one that fits the user's environment.

## How OpenClaw handles this

This skill declares `requires.bins: ["myl"]` in `metadata.openclaw`. OpenClaw checks for `myl` on `PATH` at skill load time and **silently filters this skill out** if it's missing, preventing the agent from invoking it without a working binary.

It also declares an `install` block:

```json
{ "id": "pipx", "kind": "pipx", "package": "myl", "bins": ["myl"] }
```

In OpenClaw's macOS Skills UI this surfaces a one-click install button. From the CLI the user installs `myl` themselves with one of the methods below — the install block is hint metadata, not an automated runtime installer.

For non-OpenClaw runtimes (Claude Code, generic), there's no automatic gating. Run `bash scripts/check_myl.sh` first to confirm `myl` is present.

## Detect what's already there

Always check before installing:

```bash
bash scripts/check_myl.sh
```

Or inline:

```bash
if command -v myl >/dev/null 2>&1; then
  echo "myl is installed: $(command -v myl) ($(myl --version 2>/dev/null || echo 'version unknown'))"
else
  echo "myl is not on PATH"
fi
```

If `myl` is present, skip the rest of this file and move on to `authentication.md`.

## Install paths, in order of preference

### `pipx` — recommended

Isolates `myl` and its dependencies from system Python. This is what the upstream README recommends and what the skill's `install` metadata suggests.

```bash
# Install pipx itself if missing (Debian/Ubuntu)
sudo apt update && sudo apt install -y pipx
pipx ensurepath

# Install myl
pipx install myl
```

On macOS:

```bash
brew install pipx
pipx ensurepath
pipx install myl
```

After `pipx ensurepath`, the user may need to restart their shell or `source ~/.bashrc` / `source ~/.zshrc` for `myl` to appear on `PATH`.

### `pip --user` — fallback when `pipx` isn't available

```bash
pip install --user myl
```

The binary lands in `~/.local/bin`, which must be on `PATH`. If not:

```bash
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
```

### Nix flake — for Nix users

```bash
# One-shot run without installing
nix run github:pschmitt/myl -- --help

# Or add to a flake
```

### From source

```bash
git clone https://github.com/pschmitt/myl.git
cd myl
pipx install .
```

## Sandboxed agent runs

If the agent runs inside a Docker sandbox (OpenClaw `agents.defaults.sandbox.docker`), `myl` must be installed **inside the container** as well — the host bin doesn't satisfy the in-sandbox requirement. Add to `setupCommand`:

```json
{
  "agents": {
    "defaults": {
      "sandbox": {
        "docker": {
          "setupCommand": "pipx install myl || pip install --user --break-system-packages myl"
        }
      }
    }
  }
}
```

Sandbox installs also need network egress, a writable root FS, and root user inside the container. See OpenClaw's sandboxing docs for details.

## Confirm before installing

`myl` is a third-party Python package (GPL-3.0, by Philipp Schmitt). Before running an install command, tell the user what's about to happen:

> "I'll install `myl` via `pipx install myl`. This is a third-party CLI from https://github.com/pschmitt/myl. OK to proceed?"

Skip this confirmation only if the user has already explicitly approved installing tools in this session.

## Verify the install worked

```bash
command -v myl && myl --help | head -20
```

If `command -v myl` succeeds but `myl --help` fails, there's likely a Python interpreter or dependency mismatch. See `troubleshooting.md`.

## Updating

```bash
pipx upgrade myl       # if installed via pipx
pip install --user --upgrade myl   # if installed via pip --user
```


path: references/operations.md
content: # Operations reference

All examples use the wrapper at `scripts/imap.sh`. The wrapper reads `IMAP_USER` / `IMAP_PASSWORD` / `IMAP_PROVIDER` from the environment (set up via `references/authentication.md`) and forwards everything else to `myl`.

If the user has aliased the wrapper, replace `bash scripts/imap.sh` with the alias name. A typical setup:

```bash
alias imap='bash ~/.openclaw/skills/imap-client/scripts/imap.sh'
```

…and then `imap --count 5` is enough.

## Listing messages

```bash
# Most recent N messages in INBOX (default folder)
bash scripts/imap.sh --count 10

# Specific folder
bash scripts/imap.sh --folder "INBOX/Archive" --count 20

# When --folder is omitted, myl operates on INBOX. To discover what folders
# exist, run a list against INBOX first; some myl versions surface folder
# listings in the output, others require the `--list-folders` flag if
# present in your version.
```

### Folder names by provider

Folder naming differs per server. When the user names a folder casually ("sent items", "spam", "корзина"), translate to the IMAP path the server expects.

| Provider | Inbox | Sent | Drafts | Archive | Trash | Spam |
|---|---|---|---|---|---|---|
| Gmail | `INBOX` | `[Gmail]/Sent Mail` | `[Gmail]/Drafts` | `[Gmail]/All Mail` | `[Gmail]/Trash` | `[Gmail]/Spam` |
| Yandex | `INBOX` | `Sent` (alias for `Отправленные`) | `Drafts` | n/a | `Trash` | `Spam` |
| Mail.ru | `INBOX` | `Sent` | `Drafts` | `Archive` | `Trash` | `Spam` |
| Fastmail | `INBOX` | `Sent` | `Drafts` | `Archive` | `Trash` | `Junk Mail` |
| iCloud | `INBOX` | `Sent Messages` | `Drafts` | `Archive` | `Deleted Messages` | `Junk` |
| Generic Dovecot | `INBOX` | `Sent` | `Drafts` | (configurable) | `Trash` | `Junk` |

Notes on Russian providers:

- **Yandex** publishes both English (`Sent`, `Drafts`, `Trash`) and Russian (`Отправленные`, `Черновики`, `Удалённые`) folder names; the English ones are aliases that always work, so prefer those.
- **Mail.ru** uses English internal folder names over IMAP, even when the web UI shows Russian labels.
- If a custom folder uses Cyrillic, it may be encoded in modified UTF-7 over IMAP. Pass the **exact string `myl` shows in listings**, not what you see in the web client.

If the first folder guess fails, list the available folders by running with no `--folder` and inspect the output; do not guess repeatedly.

## Searching

`myl --search` issues an IMAP `SEARCH` command. The argument is interpreted by the server, not by `myl`.

```bash
# Match against subject + body (default for most servers)
bash scripts/imap.sh --search "invoice"

# Combine with folder + count
bash scripts/imap.sh --folder "INBOX" --search "Acme" --count 50
```

### What IMAP SEARCH actually supports

Standard IMAP `SEARCH` keys (RFC 3501). Widely supported:

| Key | Meaning | Example |
|---|---|---|
| `FROM "x"` | sender contains x | `FROM "[REDACTED_SECRET]"` |
| `TO "x"` | recipient contains x | `TO "[REDACTED_SECRET]"` |
| `SUBJECT "x"` | subject contains x | `SUBJECT "invoice"` |
| `BODY "x"` | body contains x | `BODY "API key"` |
| `TEXT "x"` | header or body contains x | `TEXT "kubernetes"` |
| `SINCE 1-Jan-2026` | received on or after date | `SINCE 1-Apr-2026` |
| `BEFORE 1-Apr-2026` | received before date | `BEFORE 1-May-2026` |
| `UNSEEN` | not yet marked read | `UNSEEN` |
| `SEEN` | already read | |
| `FLAGGED` | starred / flagged | |
| `LARGER 10000000` | size in bytes | `LARGER 5000000` |

In practice, the safe approaches are:

1. Pass a single keyword or short phrase: `--search "invoice"`. The server treats this as `TEXT "invoice"`.
2. For complex filters, fetch a reasonable window and post-process locally with `grep` / `awk` / a small Python helper.

What `myl --search` does **not** understand:

- Gmail's web UI operators: `from:`, `has:attachment`, `label:`, `older_than:` — these are Gmail-specific and not part of IMAP SEARCH.
- Yandex's web search syntax (e.g. `from:`, `subject:`) — also web-only.
- Boolean operators like `AND` / `OR` / `NOT` directly in the string. IMAP supports them but with different syntax.

### Cyrillic search terms

Yandex and Mail.ru both support searching for Cyrillic text via IMAP, but the term must be sent in the right charset. `myl` typically forwards the argument as-is and lets the server figure it out:

```bash
bash scripts/imap.sh --search "счёт" --count 20
```

If the server returns no results for a Cyrillic term you know exists, retry with the Latin transliteration of the company name (since most senders include both in subject lines).

## Reading a specific message

Each listing shows a per-folder message ID. Fetch it by passing the ID as a positional argument:

```bash
bash scripts/imap.sh "$MAILID"           # plain text body
bash scripts/imap.sh --html "$MAILID"    # HTML body (if present)
bash scripts/imap.sh --raw "$MAILID"     # full raw RFC 5322 source, including headers
```

For HTML and raw, redirect to a file rather than dumping into the terminal:

```bash
bash scripts/imap.sh --html "$MAILID" > "/tmp/mail-${MAILID}.html"
bash scripts/imap.sh --raw  "$MAILID" > "/tmp/mail-${MAILID}.eml"
```

Tell the user the file path and offer to open or summarise it.

## Marking as seen

```bash
bash scripts/imap.sh --mark-seen --count 10
```

Mutates server state. Only use when the user explicitly asked to mark messages read. Never combine `--mark-seen` with a search / list that the user is just exploring.

## Attachments

```bash
# 1. Open the message — myl shows attachment names in the message detail
bash scripts/imap.sh "$MAILID"

# 2. Fetch a specific attachment by name
bash scripts/imap.sh "$MAILID" "invoice-2026-04.pdf" > ~/Downloads/invoice-2026-04.pdf
```

The second positional argument after `$MAILID` is the attachment filename. Output goes to stdout, so always redirect to a file.

If the attachment name has spaces or special characters, quote it:

```bash
bash scripts/imap.sh "$MAILID" "Q1 report.pdf" > "$HOME/Downloads/Q1 report.pdf"
```

Cyrillic attachment names work but the underlying IMAP encoding (RFC 2047 / RFC 2231) varies. If the literal Cyrillic name doesn't match, retry with the encoded form `myl` showed in the message detail.

## Full flag reference

From the upstream README (`https://github.com/pschmitt/myl`). All of these forward through the wrapper:

| Flag | Purpose |
|---|---|
| `--server HOST` | IMAP server hostname (set via `IMAP_SERVER` env) |
| `--port N` | IMAP server port (set via `IMAP_PORT` env; default 993) |
| `--starttls` | Upgrade plain connection to TLS (set via `IMAP_STARTTLS=1`) |
| `--auto` | Autodiscover server + port (set via `IMAP_PROVIDER=auto`) |
| `--google` | Hardcode Gmail's IMAP settings (set via `IMAP_PROVIDER=gmail`) |
| `--username USER` | Login username (set via `IMAP_USER`) |
| `--password PASS` | Login password (set via `IMAP_PASSWORD`) |
| `--folder NAME` | IMAP folder to operate on (default `INBOX`) |
| `--count N` | Number of messages to fetch in listings |
| `--search QUERY` | Server-side IMAP SEARCH |
| `--mark-seen` | Mark fetched messages as seen (mutates server state) |
| `--html` | When fetching a message, output the HTML body |
| `--raw` | When fetching a message, output the raw RFC 5322 source |
| `--help` | Print help |

For the authoritative list on the user's installed version:

```bash
myl --help
```

## What `myl` does not do

If the user asks for any of these, explain that `myl` is read-only and suggest an alternative:

- **Send mail** — use `msmtp`, `sendmail`, `mutt`, or scripted SMTP via Python's `smtplib` / a small `mailx` wrapper.
- **Move / copy / delete messages** — use `imap-tools` (Python), `imapcli`, or the provider's web UI.
- **Manage folders / labels** — same as above.
- **Sync to local maildir** — use `mbsync` (`isync`), `offlineimap`, or `getmail`.
- **OAuth2 to Gmail / Outlook** — `myl` uses password auth. For OAuth2 the user typically needs Proton Bridge, `mbsync` with an XOAUTH2 helper, or the provider's app-password fallback.


path: references/recipes.md
content: # Recipes

Common multi-step tasks. Each recipe assumes credentials are already configured per `references/authentication.md` (Method A / B / C). Examples invoke `bash scripts/imap.sh`; substitute your alias if you set one up.

## Recipe 1 — Quick inbox check

The user asks: "any new email?" or "what's in my inbox?"

```bash
bash scripts/imap.sh --count 10
```

Then summarise the result as a short table: date, from, subject. Don't paste full bodies.

## Recipe 2 — Find a specific email and read it

The user asks: "find the email from the GitHub team about the security alert."

```bash
# Step 1 — list candidates
bash scripts/imap.sh --search "security alert" --count 20

# Step 2 — once the user picks an ID, or you pick the most plausible candidate,
# read the body
MAILID=12345
bash scripts/imap.sh "$MAILID"
```

If the search returns many results, list the top 5–10 and ask the user which one. If it returns one obvious match, read it directly and summarise.

## Recipe 3 — Save an attachment

The user asks: "download the PDF attached to that invoice email."

```bash
# Step 1 — open the message to see the attachment names
MAILID=12345
bash scripts/imap.sh "$MAILID"

# Step 2 — pull the named attachment
ATT="invoice-2026-04.pdf"
DEST="$HOME/Downloads/$ATT"
bash scripts/imap.sh "$MAILID" "$ATT" > "$DEST"

# Step 3 — verify
ls -lh "$DEST"
file "$DEST"
```

If the user didn't specify a destination, default to `~/Downloads/` (Linux/macOS). Never overwrite an existing file silently — check with `[ -e "$DEST" ]` first and append a numeric suffix if needed.

## Recipe 4 — Render an HTML email locally

The user asks: "show me the actual newsletter, not the plain text."

```bash
MAILID=12345
HTML_OUT="/tmp/mail-${MAILID}.html"
bash scripts/imap.sh --html "$MAILID" > "$HTML_OUT"
echo "Saved HTML to $HTML_OUT"

# Optionally open in a browser
xdg-open "$HTML_OUT"   # Linux
open      "$HTML_OUT"  # macOS
```

In an agent context where there's no browser, summarise the HTML in chat after stripping tags:

```bash
python3 -c "
from html.parser import HTMLParser
import sys
class T(HTMLParser):
    def __init__(self): super().__init__(); self.out=[]
    def handle_data(self,d): self.out.append(d)
p=T(); p.feed(sys.stdin.read()); print(' '.join(''.join(p.out).split())[:2000])
" < "$HTML_OUT"
```

## Recipe 5 — Archive a message as `.eml`

The user asks: "I need that email as a file I can forward / archive / re-import."

```bash
MAILID=12345
EML_OUT="$HOME/Documents/email-${MAILID}.eml"
bash scripts/imap.sh --raw "$MAILID" > "$EML_OUT"
ls -lh "$EML_OUT"
```

`.eml` is the standard portable email format — readable by Outlook, Apple Mail, Thunderbird, and most other clients.

## Recipe 6 — Periodic check (one-shot, not a daemon)

The user asks: "ping my inbox every 5 minutes for an hour."

```bash
for i in $(seq 1 12); do
  echo "--- $(date) ---"
  bash scripts/imap.sh --count 5
  sleep 300
done
```

For real persistent monitoring (cron, systemd timers), prefer `mbsync` + `notmuch` or a dedicated tool. The skill is fine for ad-hoc ticks; it's not built to be a long-running mail daemon.

## Recipe 7 — Filter today's mail by sender, locally

`myl --search` is limited to what IMAP SEARCH supports. For more complex filters, fetch a window and post-process:

```bash
bash scripts/imap.sh --search "SINCE $(date +%d-%b-%Y)" --count 100 \
  | grep -i "from.*acme.com"
```

If the server doesn't accept the `SINCE` syntax through `--search`, drop it and filter the full listing locally.

## Recipe 8 — Count unread

```bash
bash scripts/imap.sh --search "UNSEEN" --count 200 \
  | grep -c "^From:" 2>/dev/null \
  || echo "no unread (or grep pattern needs adjusting for this myl version)"
```

The exact pattern to grep depends on `myl`'s output format on the user's version — check `bash scripts/imap.sh --count 1` output once and adjust.

## Recipe 9 — Multi-account: work + Yandex personal

The user has two mailboxes — work on a custom domain, personal on Yandex — and wants to check both.

The right OpenClaw pattern is **two agents, each with its own env**: define separate skill-allowlist agents and assign different `skills.entries.imap-client.env` per agent. But for a quick CLI check inside a single shell, scope the env per subshell so the exports don't pollute the parent:

```bash
# Work
(
  export IMAP_USER='[REDACTED_SECRET]'
  export IMAP_PASSWORD="$WORK_APP_PASSWORD"
  export IMAP_PROVIDER='auto'
  echo "=== work ==="
  bash scripts/imap.sh --count 5
)

# Personal — Yandex
(
  export IMAP_USER='[REDACTED_SECRET]'
  export IMAP_PASSWORD="$YANDEX_APP_PASSWORD"
  export IMAP_PROVIDER='yandex'
  echo "=== yandex ==="
  bash scripts/imap.sh --count 5
)
```

Suggest the user keep the per-account passwords in a password manager and only put them in env vars for the duration of the operation.

## Recipe 10 — Mail.ru: search across the domain group

Mail.ru gives one mailbox access to all four domain aliases (`@mail.ru`, `@bk.ru`, `@inbox.ru`, `@list.ru`) under the same account. So a single `imap-client` config covers all of them:

```bash
export IMAP_USER='[REDACTED_SECRET]'   # primary login
export IMAP_PASSWORD="$MAILRU_APP_PASSWORD"
export IMAP_PROVIDER='mailru'

bash scripts/imap.sh --count 20         # see the latest 20 across all aliases
bash scripts/imap.sh --search "счёт"    # works in Russian
```

Note that Mail.ru's IMAP requires explicit enablement in the web UI (Settings → "Все настройки" → "Почтовые программы" → IMAP). Without that, `AUTHENTICATIONFAILED` even with a correct app password.

## Recipe 11 — One-off ad-hoc account without touching config

Sometimes the user wants to check a colleague's mailbox or a one-off support address without reconfiguring OpenClaw. Use a subshell with inline env, no persistence:

```bash
(
  read -r -p "Username: " IMAP_USER
  read -r -s -p "App password: " IMAP_PASSWORD; echo
  export IMAP_USER IMAP_PASSWORD
  export IMAP_PROVIDER='auto'
  bash scripts/imap.sh --count 5
)
# When the subshell exits, the password disappears with it.
```

The `read -s` flag suppresses echo, so the password doesn't appear on screen or in scrollback. It still ends up in process memory of the subshell — that's unavoidable for any command-line IMAP client.


path: references/troubleshooting.md
content: # Troubleshooting

Symptom-first lookup. When the skill misbehaves, find the closest match below before guessing.

## Wrapper says "missing credentials" but I configured them

The wrapper checks `$IMAP_USER` and `$IMAP_PASSWORD` in process env. If they're missing in the agent's process, OpenClaw's injection didn't fire. Likely causes:

1. **You edited `~/.openclaw/openclaw.json` mid-session.** OpenClaw snapshots eligible skills *when a session starts*. Restart the agent session (or rely on the skills watcher if `skills.load.watch: true`).
2. **The skill key in config doesn't match.** Under `skills.entries`, the key must be `imap-client` exactly (or whatever `metadata.openclaw.skillKey` declares — this skill doesn't override it). Hyphens require quoting in JSON5: `"imap-client": { ... }`.
3. **`skills.entries.imap-client.enabled` is `false`** or the skill is disabled by `skills.allowBundled` allowlist.
4. **The agent has a per-agent skill allowlist that excludes `imap-client`.** Check `agents.list[].skills` in `openclaw.json`.
5. **You're not on OpenClaw.** Methods B (`export`) and C (`~/.config/imap-client/credentials`) are the right paths for Claude Code or generic AgentSkills runtimes. See `authentication.md`.

To verify what env the agent actually sees, run:

```bash
env | grep -E '^IMAP_' || echo "no IMAP_* env vars in this process"
```

If that shows nothing in an OpenClaw agent run, the injection isn't reaching the exec context — file a bug against OpenClaw with `metadata.openclaw` excerpt + your `skills.entries` config (passwords redacted).

## `myl: command not found` (non-OpenClaw)

OpenClaw shouldn't load this skill without `myl` because of `requires.bins: ["myl"]`. If you see this on Claude Code or another runtime:

1. Installed via `pip install --user` but `~/.local/bin` is not in `PATH`. Add it:
   ```bash
   echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc
   ```
2. Installed via `pipx` but `pipx ensurepath` was never run, or the shell wasn't restarted.
3. Installed in a virtualenv that isn't currently activated.
4. Not actually installed. `pip show myl` or `pipx list` to confirm.

## `AUTHENTICATIONFAILED` / `Invalid credentials` / `LOGIN failed`

The username + password combination was rejected. In order of likelihood:

1. **Wrong credential type.** For Gmail, Yandex, Mail.ru, iCloud, Fastmail, Yahoo — the user must use an **app-specific password**, not the account password. See `authentication.md` for the per-provider links.
2. **IMAP not enabled at the provider.** The classic Yandex/Mail.ru gotcha:
   - **Yandex Mail:** web UI → Settings → "Почтовые программы" → tick "С сервера imap.yandex.ru по протоколу IMAP". Without this, login fails even with a correct app password.
   - **Mail.ru:** "Все настройки" → "Почтовые программы" → enable IMAP.
   - **Gmail Workspace:** the org admin sometimes disables IMAP org-wide.
3. **Wrong username form.** Most providers want the full email (`[REDACTED_SECRET]`). Yandex 360 / business accounts: use the full address on the custom domain, not just the local part.
4. **2FA without app password.** Provider has 2FA on but the user generated no app password.
5. **Typo or expired/rotated app password.** Update `IMAP_PASSWORD` in the runtime's config.

## `SSL: CERTIFICATE_VERIFY_FAILED` / `[SSL: WRONG_VERSION_NUMBER]`

TLS handshake problem.

- `WRONG_VERSION_NUMBER` usually means the wrong port + transport combination. Trying STARTTLS against an IMAPS port (993), or implicit TLS against a STARTTLS port (143), produces this. With `IMAP_PROVIDER=manual`:
  - port 993, no `IMAP_STARTTLS` (implicit TLS) — the default
  - port 143 with `IMAP_STARTTLS=1` (explicit upgrade)
- `CERTIFICATE_VERIFY_FAILED` on a self-hosted server usually means the server uses a self-signed cert. There is no documented `--insecure` flag in `myl`. The proper fix is to add the server's CA to the system trust store. Don't work around this for a third party's server without confirming with the user — it's a real warning.

## `imaplib.error: command SEARCH illegal in state AUTH`

`myl` tried to search before selecting a folder. Pass `--folder INBOX` (or whichever folder) explicitly.

## `BAD [CLIENTBUG]` / `Search criteria not supported`

The IMAP search expression is malformed or the server doesn't support that key. Simplify to a single quoted word and re-test:

```bash
bash scripts/imap.sh --search "invoice" --count 20
```

If a single word works, build complexity back up gradually. See the supported-keys table in `operations.md`.

## Autodiscovery (`IMAP_PROVIDER=auto`) fails

`myl --auto` couldn't determine server + port from the username domain. Switch to a specific provider mode or `manual`:

```bash
# In config:
"IMAP_PROVIDER": "yandex"   # or "gmail", "mailru", or "manual" + IMAP_SERVER
```

Common providers with their IMAP endpoints:

| Provider | Server | Port | `IMAP_PROVIDER` |
|---|---|---|---|
| Gmail | imap.gmail.com | 993 | `gmail` |
| Yandex | imap.yandex.com | 993 | `yandex` |
| Mail.ru | imap.mail.ru | 993 | `mailru` |
| iCloud | imap.mail.me.com | 993 | `manual` |
| Fastmail | imap.fastmail.com | 993 | `auto` (works) or `manual` |
| Outlook.com | outlook.office365.com | 993 | basic auth deprecated; check provider |
| Yahoo | imap.mail.yahoo.com | 993 | `manual` |
| Proton (Bridge) | 127.0.0.1 | 1143 | `manual` |

## Connection times out / hangs

- The IMAP port is blocked by a network firewall (common on corporate / hotel WiFi). Test with `nc -vz <host> 993` or `openssl s_client -connect <host>:993`.
- The server is reachable but slow. Add a timeout wrapper:
  ```bash
  timeout 30 bash scripts/imap.sh --count 5
  ```
- The server is geo-blocking. Yandex and Mail.ru sometimes throttle / block traffic from certain regions. Try from a different network or via VPN.

## "Folder not found" / `NO Mailbox does not exist`

Folder names are case-sensitive and provider-specific. Run with no `--folder` and inspect what the listing surfaces. Common gotchas:

- **Gmail:** `[Gmail]/All Mail` — exact, including brackets and space.
- **Yandex:** prefer English aliases `Sent` / `Drafts` / `Trash` over the Russian `Отправленные` / `Черновики` / `Удалённые`. Both work, but Cyrillic is sometimes UTF-7 encoded in IMAP.
- **Mail.ru:** internal IMAP names are English even when the web UI shows Russian.
- **Custom Cyrillic folders:** pass the exact string `myl` shows in listings, not the human-readable name from the web client.

## Large mailbox is slow

`--count 1000` against a large folder is going to be slow. Server response times for IMAP `FETCH` of message metadata scale roughly linearly. Cap requests at 50–100 and paginate if the user needs more.

## Wrapper says perms warning, ignores creds file

The wrapper refuses to source `~/.config/imap-client/credentials` (or `$IMAP_CREDENTIALS_FILE`) unless it's `chmod 600` or `chmod 400`. Fix:

```bash
chmod 600 ~/.config/imap-client/credentials
```

This is intentional. A world-readable creds file would be a bigger security regression than the slight inconvenience.

## `myl --version` works but commands fail mysteriously

Could be a stale install. Reinstall:

```bash
pipx reinstall myl
# or
pip install --user --upgrade --force-reinstall myl
```

If reinstall doesn't fix it, file the bug upstream at https://github.com/pschmitt/myl/issues with the failing command (passwords redacted) and `myl --version`.

## Nothing matches the symptom

Run `myl --help` to confirm the version supports the flag the user expects. Then run the command again with shell tracing on:

```bash
set -x
bash scripts/imap.sh --count 5
set +x
```

If the issue is server-side, raw `openssl s_client` against the IMAP port often surfaces the actual error:

```bash
( set +o history; openssl s_client -connect imap.yandex.com:993 -crlf )
# Then type:  a1 LOGIN [REDACTED_SECRET] app-specific-password
# CTRL+D to disconnect
```

The `set +o history` keeps the password out of the shell history file. The session itself is TLS-encrypted to the server.


path: scripts/check_myl.sh
content: #!/usr/bin/env bash
# check_myl.sh — One-shot detection of `myl` installation.
#
# Exits 0 if myl is installed and runnable, prints the resolved path and
# version. Exits 1 otherwise with a one-line reason.
#
# Used by the imap-client skill to decide whether to run the install workflow
# in non-OpenClaw runtimes. (OpenClaw gates this skill on requires.bins=["myl"]
# and won't even load it without the binary on PATH.)

set -u

if ! command -v myl >/dev/null 2>&1; then
  echo "myl: NOT INSTALLED — not on PATH"
  exit 1
fi

MYL_PATH=$(command -v myl)
MYL_VERSION=$(myl --version 2>/dev/null | head -1 || echo "unknown")

# Sanity-check that --help exits cleanly. Catches half-broken installs where
# the wrapper script exists but the Python module / dependencies are missing.
if ! myl --help >/dev/null 2>&1; then
  echo "myl: BROKEN — found at $MYL_PATH but '--help' fails (likely missing deps)"
  exit 1
fi

echo "myl: OK — $MYL_PATH (version: $MYL_VERSION)"
exit 0


path: scripts/imap.sh
content: #!/usr/bin/env bash
# imap.sh — wrap `myl` with credentials sourced from the environment.
#
# Lookup order for credentials:
#   1. Process env vars (set by OpenClaw skills.entries.imap-client.env, or
#      manually via `export` in the user's shell).
#   2. ~/.config/imap-client/credentials  (must be chmod 600 or 400).
#   3. $IMAP_CREDENTIALS_FILE             (override path; same perms required).
#
# Variables consumed:
#   IMAP_USER          required — login (full email address)
#   IMAP_PASSWORD      required — app-specific password
#   IMAP_PROVIDER      optional — auto (default) | gmail | yandex | mailru | manual
#   IMAP_SERVER        required when IMAP_PROVIDER=manual
#   IMAP_PORT          optional — default 993
#   IMAP_STARTTLS      optional — set to 1 to add --starttls (use only with port 143)
#
# All other arguments are forwarded verbatim to `myl`.

set -euo pipefail

err() { printf '%s\n' "imap.sh: $*" >&2; }

# ---------- 1. Load credentials file if env is incomplete ----------

CRED_FILE="${IMAP_CREDENTIALS_FILE:-$HOME/.config/imap-client/credentials}"

if [[ ( -z "${IMAP_USER:-}" || -z "${IMAP_PASSWORD:-}" ) && -f "$CRED_FILE" ]]; then
  # Refuse to source a world-readable creds file. Permissions check works on
  # both GNU stat (Linux) and BSD stat (macOS).
  perms=$(stat -c '%a' "$CRED_FILE" 2>/dev/null || stat -f '%A' "$CRED_FILE" 2>/dev/null || echo "?")
  case "$perms" in
    600|400)
      # shellcheck disable=SC1090
      . "$CRED_FILE"
      ;;
    *)
      err "WARNING — $CRED_FILE has perms $perms (expected 600 or 400). Ignoring."
      err "Run: chmod 600 \"$CRED_FILE\""
      ;;
  esac
fi

# ---------- 2. Validate ----------

if [[ -z "${IMAP_USER:-}" || -z "${IMAP_PASSWORD:-}" ]]; then
  cat >&2 <<'EOF'
imap.sh: missing credentials.

Set IMAP_USER and IMAP_PASSWORD by ONE of these methods (pick whichever fits
your runtime):

  A. OpenClaw — ~/.openclaw/openclaw.json
     {
       "skills": {
         "entries": {
           "imap-client": {
             "enabled": true,
             "env": {
               "IMAP_USER": "[REDACTED_SECRET]",
               "IMAP_PASSWORD": "app-specific-password",
               "IMAP_PROVIDER": "auto"
             }
           }
         }
       }
     }
     Restart the agent session. OpenClaw injects these into env per turn.

  B. Generic shell — export in your terminal (or in ~/.bashrc / ~/.zshrc):
     export IMAP_USER='[REDACTED_SECRET]'
     export IMAP_PASSWORD='app-specific-password'
     export IMAP_PROVIDER='auto'    # optional

  C. Credentials file — ~/.config/imap-client/credentials  (chmod 600):
     IMAP_USER='[REDACTED_SECRET]'
     IMAP_PASSWORD='app-specific-password'
     IMAP_PROVIDER='auto'           # optional

For provider-specific details (Gmail, Yandex, Mail.ru, iCloud, Fastmail),
see references/authentication.md.
EOF
  exit 2
fi

# ---------- 3. Build connection flags from IMAP_PROVIDER ----------

flags=()

case "${IMAP_PROVIDER:-auto}" in
  gmail|google)
    flags+=(--google)
    ;;

  yandex)
    # Yandex Mail. .com works for both yandex.com and yandex.ru accounts.
    # Override via IMAP_SERVER if you need imap.yandex.ru explicitly.
    flags+=(--server "${IMAP_SERVER:-imap.yandex.com}" --port "${IMAP_PORT:-993}")
    ;;

  mailru|mail.ru)
    flags+=(--server "${IMAP_SERVER:-imap.mail.ru}" --port "${IMAP_PORT:-993}")
    ;;

  manual)
    if [[ -z "${IMAP_SERVER:-}" ]]; then
      err "IMAP_PROVIDER=manual requires IMAP_SERVER to be set."
      exit 2
    fi
    flags+=(--server "$IMAP_SERVER" --port "${IMAP_PORT:-993}")
    [[ "${IMAP_STARTTLS:-0}" == "1" ]] && flags+=(--starttls)
    ;;

  auto|"")
    flags+=(--auto)
    ;;

  *)
    err "Unknown IMAP_PROVIDER='$IMAP_PROVIDER'."
    err "Valid values: auto, gmail, yandex, mailru, manual."
    exit 2
    ;;
esac

flags+=(--username "$IMAP_USER" --password "$IMAP_PASSWORD")

# ---------- 4. Dispatch ----------

if ! command -v myl >/dev/null 2>&1; then
  err "\`myl\` is not on PATH. See references/installation.md."
  exit 127
fi

exec myl "${flags[@]}" "$@"


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)