Files
emcli/skills/emcli/references/commands.md
T
steve 7ad4f1adc2 feat(skill): add emcli Agent Skill (agentskills.io standard)
skills/emcli/ — an Agent Skill teaching an agent to read and send mail through
emcli's JSON agent commands:

- SKILL.md: name/description (what + when + trigger keywords), compatibility,
  metadata; body covers the security model (agent-only commands, never touch
  EMCLI_KEY), setup, the list→get→ack workflow, sending, and enforcement
  awareness. Frontmatter validated against the spec (name matches dir; desc
  574/1024; compatibility 239/500); body 146 lines (<500).
- scripts/install.sh: detects OS/arch, downloads the release binary, verifies
  the sha256 checksum when present, fails gracefully. Release tag/assets
  (v0.4.0, emcli_<ver>_<os>_<arch>) are placeholders until the first release.
- references/{commands.md,install.md}: full agent command reference (flags, JSON
  shapes, error codes, enforcement) and install options, loaded on demand.

README links to the skill.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 20:25:08 +01:00

5.4 KiB

emcli agent command reference

The five agent commands you may use. Each prints one JSON object to stdout and sets a matching exit code (0 success, non-zero error). All take --account <name>; most take --folder (default INBOX).

The JSON envelope

{ "error": false, "error_detail": {}, "data": { } }
  • error — boolean. Check it first.
  • error_detail{} on success; { "code": "...", "message": "..." } on failure.
  • data — command-specific payload (below).

Error codes: config (key/config), db, network, auth, policy (blocked by a rule), not_found (missing or filtered/invisible mail), usage (bad/missing flag).


list — headers, newest first

emcli list --account A [--folder F] [--new] [--limit N] [--before U] [--since U]
Flag Default Meaning
--folder INBOX Mailbox
--new off Only messages not yet acked
--limit 50 Max results (capped at 500)
--before <uid> Only UIDs lower than this (page to older mail)
--since <uid> Only UIDs higher than this (page to newer mail)

data:

{ "messages": [
  { "uid": 70314,
    "from": "\"Boss\" <boss@example.com>",
    "to": "<you@example.com>",
    "subject": "Hello",
    "date": "Mon, 22 Jun 2026 17:00:30 +0000",
    "message_id": "abc@example.com",
    "has_attachments": false } ] }

Headers only — no body is downloaded. data.messages is [] when nothing matches.


get — one full message

emcli get --account A [--folder F] --uid U

data:

{ "uid": 70314,
  "from": "\"Boss\" <boss@example.com>",
  "to": "<you@example.com>",
  "subject": "Hello",
  "date": "Mon, 22 Jun 2026 17:00:30 +0000",
  "message_id": "abc@example.com",
  "body_text": "the decoded plain-text body…",
  "attachments": [
    { "name": "report.pdf", "size": 20480, "mime": "application/pdf", "content_b64": "JVBERi0…" } ] }
  • body_text is the decoded plain-text part.
  • Each attachment's bytes are base64 in content_b64; decode to recover the file (echo "$b64" | base64 -d > report.pdf). size is the decoded byte length.
  • get does not acknowledge the message.
  • A filtered/invisible or missing UID returns error: true, code not_found.

search — server-side search (whole folder)

emcli search --account A [--folder F] [--from X] [--subject-contains X] [--text X] \
             [--since-date D] [--before-date D] [--limit N]
Flag Meaning
--from Sender contains
--subject-contains Subject contains
--text Full-text
--since-date / --before-date RFC 3339 bounds, e.g. 2026-06-01T00:00:00Z
--limit Max results (default 50)

data shape is identical to list ({ "messages": [ … ] }). Searches the whole folder regardless of new/acked state. Filtered mail never appears.


ack — mark message(s) processed

emcli ack --account A [--folder F] --uid-list U1,U2,U3

data:

{ "acked": [70314, 70315, 70320] }
  • The only command that changes state. Call it after you've actually handled a message.
  • Idempotent and order-independent; batch multiple UIDs comma-separated.
  • After ack, those UIDs no longer appear under list --new.
  • You cannot ack a message you aren't allowed to see — returns not_found.

send — send or reply (RW accounts only)

emcli send --account A --to X [--cc X] [--bcc X] --subject S --body B \
           [--attach P]… [--reply-to U [--folder F]]
Flag Meaning
--to / --cc / --bcc Recipients — repeat the flag or comma-separate (--to a@x,b@x)
--subject Subject
--body Plain-text body
--attach File path to attach (repeatable)
--reply-to <uid> Thread the reply onto this source message
--folder Folder of the --reply-to source (default INBOX)

data:

{ "sent": true, "recipients": ["alice@example.com", "boss@example.com"] }

Blocked sends return error: true, code policy:

  • ro_mode — the account is read-only; it cannot send.
  • whitelist_out — a recipient isn't on the outbound whitelist; the whole send is blocked and nothing was sent. Don't silently drop recipients — tell the user.

--reply-to reads the source message's Message-ID/References so the reply threads. The source is subject to the inbound filter: a filtered/missing source returns not_found.


Enforcement rules (set by the user; you can't change them)

  • Mode: RO accounts reject send. RW can read and send.
  • Inbound whitelist / subject filter: disallowed mail is invisible everywhere (list/search omit it; get/ack return not_found). You can't tell a filtered message from a non-existent one — by design.
  • Outbound whitelist: every recipient (to+cc+bcc) must match, or the send is blocked whole.
  • Address matching: case-insensitive; an entry @domain.com matches any address at that domain; otherwise an exact-address match.

Parsing tips

# Guard on success, then read data:
out=$(emcli list --account gmail --new --limit 10)
if echo "$out" | jq -e '.error == false' >/dev/null; then
  echo "$out" | jq -r '.data.messages[] | "\(.uid)\t\(.subject)"'
else
  echo "$out" | jq -r '.error_detail | "\(.code): \(.message)"' >&2
fi

# Save an attachment from get:
emcli get --account gmail --uid 70314 \
  | jq -r '.data.attachments[0].content_b64' | base64 -d > report.pdf