Skip to main content

sessionmail

Mail for coding agents: message moves messages, pager alerts, memory is the filesystem view - three commands over one maildir.

Quick start

claude plugin marketplace add mbailey/sessionmail
claude plugin install sessionmail
uv tool install sessionmail          # the commands the plugin calls
pager listen                         # blocks until mail arrives, then says so

Both of the first two lines need the repo and the release to be public: see Status before you copy them.

The last line is the point: an agent that runs pager listen in the background can be reached while it is idle, instead of finding out on its next manual check.

Installation

Two installs, and you need both. The plugin brings the skills, the two hooks and the MCP servers; the package brings message, pager and memory, which is what all of those call.

claude plugin install sessionmail    # skills, hooks, MCP entries
uv tool install sessionmail          # the three commands on PATH

With only the plugin, the hooks exit quietly and the MCP servers do not start: you get skills describing commands the machine does not have. The hook says so once per session rather than leaving you to guess.

The notmuch hook is installed by hand. hooks/notmuch-post-new tags mail at index time (+agent, +memory) and re-derives superseded from each message's Supersedes: header, so a correction survives rebuilding the index. It is NOT installed by the plugin or the package. On a host it runs as both post-new and post-insert, and needs two indexed headers:

ln -s <repo>/hooks/notmuch-post-new ~/.mail/.notmuch/hooks/post-new
ln -s <repo>/hooks/notmuch-post-new ~/.mail/.notmuch/hooks/post-insert
notmuch config set index.header.Supersedes Supersedes
notmuch config set index.header.XSupersedes X-Supersedes

Headers added to an existing index need a one-off notmuch reindex '*'.

An optional command after each index run. The hook can run one command of yours after its tagging, for anything that should follow new mail (a dashboard redraw, say). It is off unless a notmuch config key names it:

notmuch config set sessionmail.post_new_command /abs/path/to/command   # on
notmuch config set sessionmail.post_new_command                        # off

The value is an absolute path to an executable, run with no arguments and no shell; a command that needs arguments gets a wrapper script. It never blocks or fails indexing: the hook spawns a detached runner and exits 0 at once. Runs are coalesced, one at a time: mail arriving during a run gives exactly one more run. The command gets cwd /, its own process group, stdin /dev/null, and only HOME, PATH=/usr/bin:/bin:/usr/sbin:/sbin, NOTMUCH_CONFIG and SESSIONMAIL_IN_POST_NEW_COMMAND=1; it is killed after 300 s. A notmuch new it runs itself is tagged as usual and does not run it again (a command that clears its environment must pass --no-hooks instead). It must not send mail on every run: sent mail comes back as an arrival. Its output and a start/end line per run go to ~/.sessionmail/post-new/log (rolled to log.1 past 1 MiB). With the key unset, nothing is created and nothing is spawned.

Usage

message send NAME -s SUBJECT -f BODY.md   # and list, tree, read, mark
pager listen                              # block until mail; page; exit
pager status                              # what would page you now
memory ls / | memory cat PATH             # the box as a filesystem

Each command's own --help is the reference; <command> <verb> --help is the page for a verb.

Where mail comes from. sessionmail reads and writes maildirs; it does not require an MTA. On a host with postfix, mail arrives the ordinary way. Without one, anything that can write a maildir is a delivery agent - your own MTA, a script, or a chat bridge that drops each message into new/ (one door in, an outbox out). The pager watches the directory, whatever filled it.

How pager listen works

pager listen --help says when it pages, what it prints and how to stop. This is how, for whoever has to reason about it. Paths below are under <home>/sessions/<session>/, written <session>/; <home> is SESSIONMAIL_HOME, default ~/.sessionmail.

  • One box. It watches this session's box (the session id is a box); with no session id, the maildir $MAIL names, which must hold new/. It reads no subscriptions: what else reaches the box is postfix's doing, set by message subscription add BOX.
  • Birth. A session's box is created on its first listen when it is missing, and its alias line is minted in the same step, bounded in time. The banner says the doorbell's state: whether mail sent to the session will reach the box. That first listen also subscribes the session to each box in SESSIONMAIL_SUBSCRIBE, once. A $MAIL box is never created: a human's mailbox is theirs to make.
  • The ride queue. Every arrival in the box's new/ is hardlinked into <session>/ride/new/. The ride-along (the PostToolBatch hook) scoops that queue on each tool call, so a busy session reads its mail without being paged.
  • When it pages. Idle (not busy for SESSIONMAIL_IDLE_SECONDS, 1, by Claude Code's own status file, or session get idle): at once. Busy: when the oldest link has sat in the queue longer than SESSIONMAIL_PAGE_AGE_SECONDS (60) from its delivery time, because nobody scooped it. Then it prints the page and exits; the caller runs it again to keep listening. SESSIONMAIL_PAGE_LIST=on (or --list) adds one line per message: box, sender, subject, never a body. The flag wins: --no-list beats SESSIONMAIL_PAGE_LIST=on.
  • Marks. It claims nothing and moves nothing. It marks each message it pages with seen (reason pager wake), so the same message does not page this session again inside the reminder window (SESSIONMAIL_REMIND_MINUTES, 60). Another session's marks never silence this one.
  • Stamps. A running listener writes its pid to <session>/stamps/listening; that is what pager status and the hooks read as "listening", and what pager stop signals.
  • Keyed on the session. The queue, the stamps and the marks key on the session id, so a session with no seat gets the same page. With neither a session id nor a seat there is no queue; it still listens, and the banner says so.

As a Claude Code plugin monitor: pager listen --monitor (SM-678)

A hidden flag, for one caller: a plugin's monitors/monitors.json. Claude Code runs a monitor's command for the whole session and makes each stdout line a notification; it does not restart one that exits. So --monitor never exits on a page:

  • ONE line per page on stdout, flushed, and nothing else there: the count, the address each mail was sent to, what woke it - no sender, no subject, no re-arm. At most 480 characters (Claude Code cuts at 500).
  • Lines at least SESSIONMAIL_MONITOR_SPACING_SECONDS apart (2.5): Claude Code drops output past a bucket of 10 refilled 1 per 2 s, and a dropped page is a wake that never happens.
  • After a page it keeps listening. The seen marks, the carry stamp and the woke records are written as for any page, BEFORE the line, so the wake turn finds the carry stamp. A mail paged and never scooped pages again after the reminder window, as a re-arm would.
  • The "listening" confirm mail is sent once per monitor process, never per page.
  • SIGTERM, SIGINT or SIGHUP end it with exit 0 and clear its listening stamp. When it cannot look it prints pager: DEAF - ... and exits 2.
  • A monitor that finds another listener on its session waits, and takes over when that one ends.
  • It refuses --timeout, BOX and --quiet.
  • pager off / pager stop ends a monitor for good. Nothing restarts it; listen again with the bare pager listen.

Not running under Claude Code?

pager learns whether you are busy from Claude Code's own per-session sidecar (~/.claude/sessions/<pid>.json), and failing that from session get idle. A client with neither - a bare shell, your own script, a bot - never looks idle, so waits written for an agent that is mid-tool-call apply to it:

  • a page waits the running page age (60 s) instead of ringing on arrival;
  • and if your client names a seat (CLAUDE_CODE_AGENT), a second message from the same address inside the coalescing window (300 s) does not ring again - a running agent would have scooped it on its next tool call.

Nothing is lost; it is late, or it waits for your next pager listen. Turn both waits off in that client's environment:

export SESSIONMAIL_PAGE_AGE_SECONDS=0   # ring on arrival
export SESSIONMAIL_BURST_SECONDS=0      # ring for every message

pager listen says the same thing in its banner when nothing can tell it whether you are busy.

Development

make install     # uv tool install -e . - editable, so an edit is live
make             # check, then test
make progress    # how much of the CLI is built, counted by running it

openspec/ is the record of what this package is meant to do: read it before you design or build. openspec/specs/ is ratified, changes/ is proposed and ruled by nobody yet.

Status

sessionmail 0.0.1 on PyPI is a name reservation, not this code - a 1.3 KB placeholder with no dependencies, published 2026-09-08. Until 0.1.0 ships, install from source instead of the uv tool install sessionmail line above:

uv tool install git+https://github.com/mbailey/sessionmail   # from the repo
make install                                                 # from a clone

message, pager and memory all run; openspec/specs/ says what each verb is meant to do and make progress counts how much of it is built.

The plugin: three skills, one per command

skills/message, skills/pager, skills/memory - each seed is one sentence of what and when; each body points at that command's --help and repeats nothing; each references/ is a flat slipbox of items under 40 lines / 400 tokens. This README is the overview of how the three go together (Mike, voice 12:32 Wed 2026-09-09: three skills "to give some clear separation", flexible, to be reviewed).

slipbox/ ships one note, pager.md; the other notes the specs cite by name live in a private slipbox and are not part of this tree.

Metadata

Release files for sessionmail 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sessionmail 0.1.0
File Size Uploaded
sessionmail-0.1.0.tar.gz 379.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for sessionmail 0.1.0
File Interpreter ABI Platform
sessionmail-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 785.1 kB

Release files / sessionmail-0.1.0.tar.gz

Download URL sessionmail-0.1.0.tar.gz
Size 379.5 kB
Tags Source
SHA-256 checksum
How to use checksums
7244878140b274129f704d50706da74a0cd39cd82768c0c4c0200b7242c69086
BLAKE2b-256 checksum
How to use checksums
b24fe01eaa2447e9252d5b45994da0670b3d4fb4c438d64aa155569c5be39642
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.9 {"installer":{"name":"uv","version":"0.11.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / sessionmail-0.1.0-py3-none-any.whl

Download URL sessionmail-0.1.0-py3-none-any.whl
Size 405.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3b64e534ef46aa610bf4d65bea8d4d00466e4a127099f92454c261cb13756797
BLAKE2b-256 checksum
How to use checksums
16c07a0f362a7fb21872bca741f599aef21944fa67ced55c46a7f39e251ecaea
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.9 {"installer":{"name":"uv","version":"0.11.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page