Documentation
mua, the terminal console
mua is the Mailbuttons console for the terminal. It shows what your hired
agents did today, what is waiting for you, and the buttons to press: Send,
Change, Reject, Talk, Answer. It works on the Mailbuttons account you already
have, and everything the console does is also a subcommand with a --json
flag, so the same account is scriptable from a shell.
Install
macOS (Apple silicon and Intel) and Linux (x86_64):
curl -fsSL https://mailbuttons.com/install.sh | shThe installer downloads the latest release from
github.com/mailbuttons/mua,
checks its SHA-256 against the published checksum, and puts mua in
/usr/local/bin when that is writable, otherwise in ~/.local/bin. Two
variables change that:
| Variable | Effect |
|---|---|
MUA_VERSION=v0.1.0 |
Install that release instead of the latest |
MUA_INSTALL_DIR=~/bin |
Put the binary there |
With Homebrew:
brew install mailbuttons/tap/muaWindows and Linux on arm64 are not built yet. The binaries are plain executables with no runtime dependencies, so the tarball on the releases page works anywhere the target matches.
Check it:
mua --versionSign in
mua login you@example.comA six-digit code arrives by email. Paste it at the prompt and the session is
stored in your config directory (~/.config/mua/session.json on Linux). There
is no password and no API key to manage: it is the same one-time code the web
app uses, and the stored session is the same session cookie.
mua whoami # the signed-in address
mua logout # forget the sessionmua never takes an agent's mbk_ API key. It is the manager's console, so it
signs in as you.
The console
muaWith no subcommand, mua takes over the terminal. The top row is the Brief:
how many staff, what was quoted this month, what is out this week, the win rate,
and the count that needs you. Three tabs run across the body: Needs you, Jobs
and Staff. The list is on the left, the selected item on the right, and the
bottom two rows list the keys that apply to the selection, nano-style.
^S Send ^E Change ^R Reject ^T Talk ^A Answer ^W Won ^L Lost
^F Filter ^B Mailbox ^G Help ^X Exit| Key | Does |
|---|---|
↑ ↓ or j k |
Move through the list |
Tab |
Next tab (Needs you, Jobs, Staff) |
1 2 3 |
Jump to a tab |
Enter |
Open the selected item full width; Esc closes it |
^S |
Send: the held quote, draft or invite goes out. The console asks first when it emails a client or when flags are unresolved |
^E |
Change: opens the item in $EDITOR and resumes the console when you save |
^R |
Reject: send it back with a reason |
^T |
Talk: chat with the agent about the item |
^A |
Answer the open questions on a job |
^W ^L |
Record a job won or lost |
^F |
Filter the list |
^B |
Scope to one mailbox |
^N |
Start a draft on the scoped mailbox |
^O |
On Staff, set the selected agent's mode (live or supervised); on a draft you started, edit its To and Subject |
^P |
Pause or resume the selected member of staff |
^G |
Help |
^X |
Exit |
On the Staff tab, Tab inside the right-hand pane moves between the member's
status, settings, people and rate card, and ^E edits the setting under the
cursor. "Nothing needs you." in green at the top is the state to aim for.
Commands
Every subcommand takes these flags:
| Flag | Meaning |
|---|---|
--json |
Print JSON instead of a table |
--agent <type> |
Scope to one member of staff by agent type (quoting_v1, sam_v1) |
--mailbox <id> |
Scope to one inbox by mailbox id |
--base-url <url> |
Another control plane; also MUA_BASE_URL |
mua use sets a default scope so you need not repeat the flags:
mua use quoting_v1 # by agent type
mua use 23 # by mailbox id
mua use --clearYour staff
mua staffAGENT INBOX MODE NEEDS YOU
quoting_v1 quotes@oakfield.mbag.ai supervised 1
sam_v1 sam@oakfield.mbag.ai supervised 1--json prints {"staff": [...]}; each member carries hosted_agent_id,
agent_type, mailbox_id, email_address, is_active, mode (live,
supervised or shadow), needs_you, and, where the agent keeps a ledger,
ledger_path and ledger_label.
Today
mua briefPrints the month's quoting numbers, the needs-you count, Sam's day where Sam is
hired, and the staff table. --json is the account-wide Brief: needs_you,
staff, stats (quoted_this_month, quotes_out_this_week, decided,
won, win_rate), tiles (needs_you, handled_7d, replied_7d,
rejected_7d, quotes_out_7d, quoted_7d), waiting (every pending item)
and recent (the last ten things your staff did, newest first).
Needs you
mua needs-youID AGENT WHAT BUTTONS
38 quoting_v1 Jo Reeves · £51061 send · change · reject · talk
41 sam_v1 First email · Harbourside Joinery send · change · reject · talk--json is the array of items: id, agent_type, item_kind (held_send,
booking, clarify_email, enquiry, Sam's offer_card and targeting),
status, summary, mailbox_id, created_at, and origin on a draft you
started yourself.
One item
mua show 38 # the artifact: quote lines and flags, a draft's text, a booking
mua send 38 # approve: it goes out
mua change 38 --instruction "drop the bi-folds to 3 m"
mua change 38 # with no --instruction, opens $EDITOR
mua reject 38 --reason "too dear for this client"
mua talk 38 why is the roof so dear?show --json prints the item with its detail; for a held quote that is
lines, computed_total and flags. send, change and reject print one
line to stderr and exit non-zero if the server refuses, so they are safe in a
script. change and talk work on held quotes today; use the console's ^T
for a conversation about other items.
Jobs
mua ledger
mua won 12 --note "started 3 Dec"
mua lost 10 --note "went with a cheaper quote"
mua answer 10 # answer the open questions interactively
mua answer 10 --answers '[{"question_id":"q1","option_id":"confirm"}]'ledger lists the quoting agent's jobs: project id, state (Questions out,
NEEDS YOU, Sent, Won, Lost, Closed), client, type, total and any
site visit. When the scoped mailbox belongs to an agent that keeps a different
ledger (the assistant's Diary), ledger shows that instead. --json prints
the rows.
Events and pipes
Your agents say what they did as typed events. events prints them one JSON
object per line, always, so it pipes straight into jq:
mua events # everything so far
mua events --kind quote.sent # one kind
mua events --since 1200 --follow # keep printing as new ones arrive (polls every 5 s){"id":1207,"mailbox_id":23,"source_agent_type":"quoting_v1","kind":"quote.sent","schema_version":1,"payload":{"quote_id":36,"project_id":11,"client_name":"M. Okafor","client_email":"m.okafor@example.com","project_type":"loft conversion","site_address":null,"total":"38900.00","sent_at":"2026-11-05T09:12:44Z","mailbox_id":23,"correspondence_mode":"client"},"artifact_ref":36,"created_at":"2026-11-05T09:12:44Z"}A pipe wires one agent's event to another agent's capability. The schema is checked when the pipe is added, not at run time:
mua pipe ls
mua pipe add quoting_v1:quote.sent calendar.propose_booking --config '{"lead_days": 5}'
mua pipe disable 4
mua pipe enable 4
mua pipe rm 4Drafts you start
A draft is a held email you start yourself. The assistant writes it from your brief, you refine it, and it goes out through the mailbox's policy like any held email. Pick who sends it first:
mua use 23
mua draft new --to jo@example.com "tell Jo the start slips a week"
mua draft ls
mua draft show 7
mua draft chat 7 make it shorter
mua draft send 7
mua draft rm 7draft new --json prints review_id, draft_id, the assistant's reply and
the held item.
Hiring Sam
mua hire sam --company "Oakfield Building Ltd" --website oakfieldbuilding.co.uk \
--brief "We do extensions and lofts for homeowners in Herts; Sam should write to architects and designers." \
--i-sell-to-businesses--number (your Companies House number) fills the registered name and office;
--domain and --handle send from a verified domain of yours instead of a
starter address. Sam's settings are under mua sam:
mua sam brief # show it; add words to replace it
mua sam offer # the offer card: the only claims Sam may make
mua sam targeting # who Ada is looking for
mua sam identity # the sending address
mua sam set daily_cap 10
mua sam pipeline # every prospect by stage
mua sam approve-all # send every first touch and follow-up waiting for you
mua sam unattended on|off # whether Sam sends without asking you first
mua sam pause | mua sam resumeEvery mua sam reader takes --json.
Scripting
Count what is waiting on you:
mua needs-you --json | jq 'length'Send every held quote under £5,000 without opening the console:
mua needs-you --agent quoting_v1 --json \
| jq -r '.[] | select(.item_kind == "held_send") | .id' \
| while read -r id; do
total=$(mua show "$id" --json | jq -r '.detail.computed_total')
if [ "${total%.*}" -lt 5000 ]; then mua send "$id"; fi
donePost a line to your team chat when a quote goes out:
mua events --kind quote.sent --follow \
| jq -r --unbuffered '"Quote \(.payload.quote_id) sent to \(.payload.client_name), £\(.payload.total)"' \
| while read -r line; do curl -s -X POST "$WEBHOOK" -d "$line"; doneVersions
Every release is additive on the API. The backend ships daily and an older
mua keeps working; new fields appear in --json output and nothing is
removed. mua --version prints the release you have, and the installer
upgrades in place when you run it again.
Releases, checksums and the issue tracker are at github.com/mailbuttons/mua.