██╗███╗   ██╗██████╗ ██║████╗  ██║██╔══██╗██║██╔██╗ ██║██████╔╝██║██║╚██╗██║██╔══██╗██║██║ ╚████║██████╔╝╚═╝╚═╝  ╚═══╝╚═════╝ 

The agent inbox.
A tiny Go CLI for IMAP & SMTP — no SDK, no OAuth dance, no TTY.

$ go install github.com/yuzu-hub/inb@latest
$ inb setup
~/inb $ cat WHY-AGENTS.md
the design brief

Most email tools assume a human on the other end.

inb assumes nothing. Every surface — auth, output, addressing, errors — is shaped for a process that can't click "Allow" in a browser tab, can't read a screenshot of a thread, and shouldn't have to. Six concrete consequences:

01output

JSON on every command

Add --json and you get parseable, stable schemas. Pipe straight into jq or your model's tool layer. No screen-scraping.

inb list --unread --json | jq '.[].subject'
02auth

Env vars. That's it.

Credentials live in INB_* environment variables or ~/.inb.env. No OAuth redirect, no browser, no token refresh thread.

INB_IMAP_HOST=imap.gmail.com
03addressing

UIDs, not nicknames

Every message is referenced by IMAP UID — stable within a folder, easy to thread through a plan. Agents can re-read their own state.

inb read 421 --max-bytes=8192
04idempotence

Re-running is safe

Marking read twice is fine. Moving to a folder it's already in is fine. The agent doesn't need to remember whether it already did the thing.

inb mark 421 --read
05exit codes

0 / 1 / 2, like UNIX

0 success, 1 anything went wrong (net, auth, bad flag, missing message), 2 unknown subcommand. Errors go to stderr — loops branch on $?, no prose parsing.

inb send ... || retry_with_backoff
06no TTY

Flag-driven, never asks

Outside of setup, inb never prompts. Body via --body, --body-file, or stdin. Headless containers, cron jobs, agent sandboxes — all fine.

echo "hi" | inb send --body=-
~/inb $ man quickstart
three commands, two minutes

From zero to first list.

  1. Install the binary

    One static binary, no runtime deps. Or grab a release from GitHub and drop it on $PATH.

    install.sh
    $ go install github.com/yuzu-hub/inb@latest $ inb version 0.1.0
  2. Configure credentials

    The wizard detects your provider (Gmail, Fastmail, iCloud, generic IMAP) and writes ~/.inb.env. Or set env vars directly — agents typically prefer the latter so secrets live in their sandbox, not on disk.

    setup — interactive
    $ inb setup # detected: gmail. uses app passwords (not your account password). # https://myaccount.google.com/apppasswords imap user alice@gmail.com imap pass •••••••••••••••• testing imap.gmail.com:993 ........................... ok testing smtp.gmail.com:587 ........................... ok wrote ~/.inb.env (chmod 600)
    …or env vars only (no file)
    export INB_IMAP_HOST=imap.gmail.com export INB_IMAP_USER=alice@gmail.com export INB_IMAP_PASS=app_password_here export INB_SMTP_HOST=smtp.gmail.com export INB_FROM='Alice <alice@gmail.com>'
  3. Do something useful

    Read your inbox, draft a reply, mark it done — entirely from the shell.

    ~/inb $
    $ inb list --unread --limit 5 UID DATE FROM SUBJECT 421 2026-05-10 alice@example.com Re: PR review needed 419 2026-05-10 ci@buildkite.io Build #2841 failed 417 2026-05-09 newsletter@golangweekly.com weekly digest 414 2026-05-09 noreply@github.com [inb] new issue 412 2026-05-08 founder@startup.com intro? warm lead $ inb read 421 --max-bytes=2000 --mark-read # <headers + truncated body>
~/inb $ man inb
surface area

Eleven commands. No surprises.

Every command takes --json for machine output and --folder <name> to scope to a non-INBOX folder. Run inb <cmd> --help for the full flag set.

SYNOPSIS

inb <command> [flags]

READ COMMANDS

list (ls)
list messages with --since/--before/--from/--subject/--unread/--search
read (show) <UID>
fetch headers + body. --max-bytes (default 32768) truncates body; --full=no limit; --raw dumps raw RFC822; --headers-only skips body; --html prefers HTML; --mark-read sets \Seen atomically
search <query>
server-side full-text search (IMAP TEXT); shorthand for list --search
folders
list IMAP folders (returns name + delimiter + attributes in JSON mode)
attachments <UID>
list attachments: index (1-based), filename, mime type, size. Heads-up: JSON keys are PascalCase (Filename, ContentType, Size) — every other command uses snake_case
save-attachment <UID>
extract one attachment: --name (case-insensitive) or --index (1-based). --out accepts a directory (joins the filename) or a full path; default = cwd. Written 0600.

WRITE COMMANDS

send
compose & send: --to/--cc/--bcc/--subject/--body (use - for stdin); --attach is repeatable; appends to Sent by default. Footer: configured INB_FOOTER is auto-appended with RFC 3676 sigdash; override per call with --footer/--footer-file/--footer-html, suppress with --no-footer
mark <UID>
flag changes: --read/--unread/--flag/--unflag
delete (rm) <UID>
move to Trash (auto-detected per provider)
archive <UID>
move to Archive (auto-detected per provider)
move (mv) <UID>
move to an arbitrary folder via --to <folder>

GLOBAL FLAGS

--folder <name>
IMAP folder to operate on (default: INBOX)
--json
emit JSON to stdout instead of the human-readable layout

ENVIRONMENT

INB_IMAP_*
HOST, PORT (default 993 if TLS else 143), USER, PASS, TLS=tls|starttls|none (default tls)
INB_SMTP_*
HOST, PORT (default 587 starttls / 465 tls / 25 none), USER, PASS, TLS=starttls|tls|none (default starttls)
INB_FROM
default From: address for send (e.g. 'Alice <alice@example.com>')
INB_FOOTER
optional signature appended to outgoing mail (RFC 3676 sigdash). For HTML sends, this is auto-escaped + <br>-wrapped unless INB_FOOTER_HTML is set.
INB_FOOTER_FILE
path to a file used as the footer. Wins over INB_FOOTER if both are set. Supports ~/ prefix.
~/.inb.env
dotenv file loaded BEFORE env vars; env wins on collision. Supports KEY=v, KEY="v" (with \n \r \t escapes), KEY='v' (literal), # comments.

EXIT STATUS

0
success
1
anything went wrong: network, auth, message/file not found, bad flag, missing required arg, parse error (stderr has the reason)
2
top-level CLI error: unknown subcommand or no subcommand

SEE ALSO

Full schema reference, error catalog, provider settings, behavior guarantees → llms.txt (agent-readable, ~600 lines).

~/inb $ ls recipes/
in the wild

Three ways agents actually use this.

None of these need an SDK. They're shell. That's the point — anything that can spawn a subprocess can read and write email.

01 — TRIAGE LOOP

Pipe unread mail through your model

List + JSON output + jq + your favorite agent runner. The model decides what to archive, what to flag, what needs a reply. Re-running tomorrow is safe — already-marked items don't reappear.

shelljqidempotent
triage.sh
#!/usr/bin/env bash # Triage the last 25 unread; idempotent on repeat runs. set -euo pipefail inb list --unread --json --limit 25 \ | jq -c '.[]' \ | while read -r msg; do uid=$(echo "$msg" | jq -r '.uid') verdict=$(inb read "$uid" --max-bytes=4096 --json \ | claude --model claude-opus-4-7 -p "classify: keep|archive|reply") case "$verdict" in archive*) inb archive "$uid" ;; reply*) inb mark "$uid" --flag ;; *) inb mark "$uid" --read ;; esac done

02 — CLAUDE CODE TOOL

Drop-in shell tool for Claude Code

Add a single .claude/commands/email.md that wraps the four commands an agent actually needs. The model invokes them like any other shell tool.

claude-codeslash-command
.claude/commands/email.md
--- description: Read, search, and send email via inb. allowed-tools: Bash --- You have an email CLI available. Use these commands: - `inb list --unread --json` — list unread mail - `inb read <UID> --json --max-bytes 8000` — read one - `inb send --to X --subject Y --body -` — send (stdin body) - `inb archive <UID>` / `inb mark <UID> --read` — clean up Always pass --json. UIDs are stable within a folder. Errors arrive on stderr; check exit code before continuing. User task: $ARGUMENTS

03 — REPLY PIPELINE

Read → think → send, in one pipe

inb reads from stdin for --body=-, so you can stream the model's response straight into a send. No temp files, no shell-escaping of the body.

stdin/stdoutno-tempfile
~/inb $
$ inb read 421 --json --max-bytes 12000 \ | claude -p 'Draft a short, friendly reply. Body only.' \ | inb send \ --to alice@example.com \ --subject 'Re: PR review needed' \ --body=- sent: 1 recipient(s), 1247 bytes (saved to Sent)

inb · MIT · made for processes, not people github.com/yuzu-hub/inb  ·  issues  ·  ↑ top