Skip to main content

mailo

Tells you when your AI agent gets stuck, dies, or finishes.

Agents and long jobs run while nobody is watching. Things happen that you need to know about and no way to find out — and the one case that matters most, an agent frozen waiting for approval, cannot announce itself. mailo hooks into Claude Code, catches that moment, and pushes it to your phone.

  • No daemon, no server. One executable and one config file
  • Zero dependencies (standard library only), Python ≥ 3.11
  • Two channels: Discord webhook (immediacy) and SMTP mail (a record)

Status

v0.1.0 — it works. Not published to PyPI yet. The hook input fields follow the reference documentation and have not been measured against a live session yet (docs/mailo/07-open-questions.md Q5).

uv tool install mailo-cli    # or: pipx install mailo-cli
mailo init

The distribution is called mailo-cli because PyPI rejects mailo as too similar to an existing project. Everything else — the command, the module, the config paths — is mailo.

Design docs/mailo/ — event model, hook wiring, config, onboarding, channels
Reference docs/channels/, docs/pypi/ — from the official docs
Open questions docs/mailo/07-open-questions.md — delayed blocked, SessionEnd, hook measurement
Tests python -m unittest discover -s tests (73 tests, no dependencies)

CLI

mailo init                          create the config (~/.config/mailo/config.toml)
mailo config set smtp.username ...  set one key, the way git config does
mailo config list / get / unset / edit
mailo config export                 move the config to another machine in one line
mailo test [-c CHANNEL]             verify the config and send a test
mailo send -e EVENT -s SUBJECT [-b BODY]
mailo run [--name NAME] -- CMD...   wrap a command — exit code, duration, log tail
mailo install-hooks / uninstall-hooks

There are three ways in. The first time, init takes everything you collected in one paste, in any order. After that config set changes one key at a time. When you add a machine, config export moves the whole thing in one line. Secrets are never accepted as command-line arguments. (docs/mailo/05-onboarding.md)

Four events

Event When Cost of missing it
blocked Stopped, waiting for approval or input The highest. Thirty seconds of attention would clear it; thirty minutes are lost instead
failed Died (OOM, rate limit, non-zero exit) High. You find out in the morning that a three-hour run died
done Finished normally Low. You just cannot queue the next thing
custom Called directly from a script

Routing is per event. blocked to Discord, done to mail, failed to both — two independent paths, so one can die and the notification still arrives.

Design principles

  1. The only failure mode of a notifier is being noisy. That is why the Stop hook, which fires every turn, is not used, and why there is a cooldown and a daily cap. (docs/mailo/01-events.md §3.1)
  2. The default leaks nothing. Bodies carry metadata only — no conversation text, no full paths. include_message_text defaults to false. (docs/mailo/04-configuration.md §6.2)
  3. The hook path never disturbs the session. Always exit 0, always async: true. (docs/mailo/03-hooks.md §5.1)
  4. No plaintext SMTP, and no way to disable TLS verification. Not even as an option. (docs/mailo/06-channels.md §7.1)

Known weakness

The immediacy of blocked rests on Discord alone. Mail cannot deliver immediacy, so it is not an alternative. Discord is a chat app, which means push delivery depends on the user's per-channel notification settings — something we cannot enforce. The only mitigation is guidance: make a dedicated channel for mailo, set it to "All Messages", and keep mobile push on. (docs/mailo/04-configuration.md §6.4)

Layout

src/mailo/
  cli.py         command parsing and implementations
  schema.py      the config key schema — one table shared by init, config set, and load
  config.py      reading and writing, mode 600 enforcement, machine-to-machine transfer
  tomlwrite.py   in-place TOML editing (stdlib has no writer, and comments must survive)
  onboard.py     discovery, order-free paste parsing, domain presets
  hooks.py       hook entry point (always exit 0) and settings.json merging
  notify.py      routing, cooldown, daily cap, retry classification
  runner.py      mailo run — command wrapping
  channels/      discord (urllib) and smtp (smtplib)

Licence

MIT.

Download files

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

Source Distribution

mailo_cli-0.1.0.tar.gz (75.8 kB view details)

Uploaded Source

Built Distribution

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

mailo_cli-0.1.0-py3-none-any.whl (46.2 kB view details)

Uploaded Python 3

File details

Details for the file mailo_cli-0.1.0.tar.gz.

File metadata

  • Download URL: mailo_cli-0.1.0.tar.gz
  • Upload date:
  • Size: 75.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for mailo_cli-0.1.0.tar.gz
Algorithm Hash digest
SHA256 640362514a93c0c8d080907d4285b2098d884b1ec1115220dbacf026ffe1d055
MD5 edb24c869d03fb8001478be72d17573e
BLAKE2b-256 ad4d46663619c0c971df67a9513bc503b3fc33f315ce47a927154e1542e31aac

See more details on using hashes here.

Provenance

The following attestation bundles were made for mailo_cli-0.1.0.tar.gz:

Publisher: release.yml on 1935138/mailo

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

File details

Details for the file mailo_cli-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: mailo_cli-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 46.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for mailo_cli-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1f9f4d58656e22b10c5b6feb27a5fb2a8f412f8c5d8685dc7af62d428ee98433
MD5 41b4089c7010b2f60fda3bdb01feb7a2
BLAKE2b-256 84b52b39dd7962b542ce1b484d4ddb379047f9b91553183b3a0b9971be57b019

See more details on using hashes here.

Provenance

The following attestation bundles were made for mailo_cli-0.1.0-py3-none-any.whl:

Publisher: release.yml on 1935138/mailo

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