AgentStack
SKILL verified MIT Self-run

Emailops

skill-gerodp-hermes-productivity-skills-emailops · by gerodp

Query, search, chat with, and compose/draft/send email from a local EmailOps mailbox via emailops-cli.

No reviews yet
0 installs
11 views
0.0% view→install

Install

$ agentstack add skill-gerodp-hermes-productivity-skills-emailops

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Emailops? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

EmailOps CLI

emailops-cli is the headless front-end for EmailOps, a privacy-first, AI-native email client. AI runs locally by default (embedded llama.cpp); email data lives in a local SQLite database. This skill lets the agent read, search, chat with, and compose / draft / send mail without opening the desktop app.

It complements the himalaya skill: Himalaya speaks raw IMAP/SMTP, while emailops-cli adds Gmail/Outlook OAuth accounts plus local AI — semantic chat over your mail, classification, and embeddings.

The agent contract: always pass --json

Every command supports --json, which prints exactly one stable envelope to stdout (logs always go to stderr). Parse this single shape regardless of success or failure — never scrape the human-readable output:

{ "ok": true,  "data": { /* result */ }, "error": null }                                  // success
{ "ok": false, "data": null, "error": { "code": "not_found", "params": {}, "message": "" } } // failure

Add --quiet to suppress the stderr app-log stream when you only want the envelope.

Exit codes (branch on these, not on text)

| Code | Meaning | Code | Meaning | |------|--------------------|------|--------------------| | 0 | ok | 5 | network / sync | | 2 | invalid input | 6 | AI | | 3 | not found | 130 | cancelled | | 4 | auth | 1 | otherwise |

Prerequisites

  1. emailops-cli on PATH (emailops-cli --version to verify). It is built

from the EmailOps repo with the cli cargo feature; symlink the built binary into ~/.local/bin if needed.

  1. An EmailOps data directory with at least one configured account. Confirm

with doctor before anything else.

When to use

Use whenever the user asks about their own email: searching messages, asking "what did X say about Y?", listing accounts, reading a specific message, or checking mailbox health. Also use it to write mail — save or edit a draft, review existing drafts, or send a message. Prefer the cheap read paths (doctor, accounts, emails, show, search) over chat unless the question genuinely needs the local LLM.

Core commands

| Command | Purpose | Cost | |---------|---------|------| | emailops-cli doctor --json | Env readiness: DB, accounts, AI config. Loads no model. | cheap | | emailops-cli accounts --json | List configured accounts (id, email, provider). | cheap | | emailops-cli emails --json [--limit N] [--mailbox inbox\|sent\|spam\|trash] | List recent emails. | cheap | | emailops-cli show --json | Full single email (headers + body). | cheap | | emailops-cli search '' --json [--limit N] [--trace] | Full-text search across an account's mail. | cheap | | emailops-cli chat '' --json [--trace] | Ask the local AI a question against your mail. | loads LLM | | emailops-cli stats --json | Per-account dashboard totals + coverage. | cheap | | emailops-cli compose --to … --subject … (--body …\|--body-file …) --json | Save a draft (default) or --send to deliver. | cheap (write) | | emailops-cli drafts --json | List saved drafts, newest-first. | cheap | | emailops-cli draft --json | Show a single draft (recipients, subject, body, attachments). | cheap |

Scope to an account with --account (defaults to the single enabled account). When multiple accounts exist, list them first and pass --account explicitly.

Procedure

  1. Health check first (read-only, no model load):

``bash emailops-cli doctor --json ` Confirm data.ok == true and data.accountsEnabled > 0. If aiEnabled is false, avoid chat` and stick to search/read commands.

  1. Resolve the account when there's more than one:

``bash emailops-cli accounts --json ` Use the returned id or email with --account`.

  1. Read paths for lookups (fast, safe even while the desktop app is open):

``bash emailops-cli search 'invoice from acme' --json --limit 5 emailops-cli show --json emailops-cli emails --json --mailbox inbox --limit 25 ``

  1. AI path for natural-language questions (loads the local model):

``bash emailops-cli chat 'what did the landlord say about the lease renewal?' --json ` Add --trace to inspect routing, retrieval, tool calls, and the sources behind the answer under data.trace`.

  1. Multi-turn: carry context across one-shot invocations by passing the

conversationId from a previous chat --json result: ``bash emailops-cli chat 'and what date was that?' --json --conversation ``

  1. Parse the envelope, branch on ok / exit code, and answer the user

from data. Surface error.message on failure.

Writing mail: compose, drafts, send

compose is the single write path. By default it saves a draft (and, on Gmail/Outlook accounts, also pushes it to the provider's Drafts folder — no extra flag). Add --send to deliver immediately instead. Both paths return the resulting record under data.

> 🚫 NEVER send without explicit user confirmation. Do not run compose … > --send until you have shown the user the full email about to be sent — > the sending account, every To/Cc recipient, the subject, the complete > body, and any attachments — and the user has explicitly approved that exact > message. No exceptions: not for "obvious" replies, not because the user said > "send it" earlier about a different draft, not to save a round-trip. A vague > or prior "go ahead" is not sufficient — re-confirm after showing the final > content. When unsure whether you have approval, save a draft and ask — you > never need permission to save a draft, and you always need it to send.

# Save a draft (default). --to / --cc / --attach are repeatable.
emailops-cli --account  compose \
  --to a@x.com --to b@y.com --cc c@z.com \
  --subject "Q3 numbers" \
  --body "Hi — draft here." \
  --attach /abs/path/report.pdf --json

# Long/rich body: read it from a file instead of --body (mutually exclusive).
emailops-cli --account  compose \
  --to a@x.com --subject "Notes" --body-file /abs/path/body.txt --json

# Update an existing draft in place rather than creating a new one.
emailops-cli --account  compose --draft  \
  --subject "Q3 numbers (rev)" --body "Updated." --to a@x.com --json

# Send now — ONLY after showing the full email and getting explicit approval
# (see "Mandatory send procedure" below). Delivers immediately; irreversible.
emailops-cli --account  compose \
  --to a@x.com --subject "Q3 numbers" --body "Final." --send --json

Rules and shapes:

  • --to, --cc, --attach are repeatable; pass the flag once per value.
  • Body is --body XOR --body-file. Supplying both is invalid input

(exit 2, no JSON envelope — it's rejected at the argument layer). --body-file reads the body verbatim from the path.

  • Use real line breaks — write bodies like a normal email. The CLI stores

the body verbatim and does not interpret escape sequences, so a plain --body "Hi\n\nText" is stored as the literal characters \n and the recipient sees \n in the message. Format the body the way a person would: greeting on its own line, a blank line between paragraphs, and a blank line before the sign-off. Two reliable ways to pass actual newlines:

  • --body-file (preferred for anything multi-line): write the body to a

file with real newlines, then reference it — printf 'Hi there,\n\nFirst paragraph.\n\nThanks,\nGero\n' > /tmp/body.txt then compose … --body-file /tmp/body.txt.

  • ANSI-C quoting for short inline bodies: --body $'Hi there,\n\nText'

(the $'…' form makes the shell expand \n into real newlines; ordinary "…" double quotes do not).

  • Attachments are path references, resolved at compose/send time. Use

absolute paths readable now; filename and mimeType are inferred. Each returned attachment is {id, draftId, filePath, filename, mimeType}.

  • compose (draft) returns the draft object: id, accountId, status

("draft"), subject, toAddresses[], ccAddresses[], body, attachments[], providerDraftId (null on IMAP/local; set once pushed to Gmail/Outlook), createdAt/updatedAt. drafts returns an array of these (newest-first); draft returns one.

  • Drafts don't require recipients (a bare draft with empty toAddresses is

saved fine) — but confirm the recipient list before --send.

Review drafts:

emailops-cli --account  drafts --json      # newest-first list
emailops-cli --account  draft  --json  # one draft, full

Mandatory send procedure (never skip a step)

--send is irreversible and there is no undo. Every send MUST follow this order:

  1. Save it as a draft first (compose …, no --send) — never build the

send command straight from user text.

  1. Show the user the full outgoing email by reading it back with

draft --json: the sending account, all To/Cc recipients, the subject, the entire body, and the filename of each attachment. Present it plainly, not just a summary.

  1. Ask for explicit approval of that exact message and wait for a clear

"yes, send this". If the user asks for any change, edit the draft (compose --draft …) and repeat from step 2 — approval of an earlier version does not carry over.

  1. Only then run compose --draft --send --json (or an equivalent

--send with the identical, already-approved fields).

If you cannot show the full message and get a fresh, explicit yes, do not send — leave the draft saved and tell the user it's ready to review.

Pitfalls

  • Heavy writes need the app closed. sync, classify, embed, and the

heavy compose write paths — --send, and provider draft push on Gmail/Outlook — mutate the SQLite DB (and hit the network); run them only when the EmailOps desktop app is not running (WAL contention). A plain save-as-draft on an IMAP/local account is safe even while the app is open (the draft stays local — providerDraftId is null). Read commands are always safe.

  • Don't scrape pretty output. Human output is styled (color/tables) only on

an interactive TTY and is not a stable contract. Always --json.

  • Logs are on stderr. Don't merge stderr into stdout when parsing JSON;

use --quiet to silence the app-log stream.

  • No model for doctor. It's intentionally cheap — use it as the gate

before any AI command rather than calling chat blindly.

  • Never send without explicit user confirmation. --send is irreversible;

always follow the Mandatory send procedure above — save a draft, show the full email (account, all recipients, subject, body, attachments), get a fresh explicit yes, then send. When in doubt, leave it as a draft.

  • Same commands in the REPL. Running emailops-cli with no subcommand opens

an interactive REPL that exposes these as /compose, /drafts, and /draft . For the agent, prefer the one-shot subcommands with --json.

  • Double-encoded saved files. When emailops-cli output is written to a file

and ends up double-escaped (the outer JSON has an "output" key whose value is a string containing the inner JSON), unwrap it first:

``bash cat saved_result.json | jq -r '.output' | jq ... ``

Without -r .output, jq will try to parse the escaped string as literal JSON tokens and silently fail or return wrong results.

  • Outlook/Hotmail control characters break jq. Emails from Outlook/Hotmail

providers can inject raw \r\n sequences that cause jq: parse error: Invalid string: control characters … errors. Pre-filter before piping to jq:

``bash cat output.json | sed 's/\\r//g' | jq ... ``

Verification

emailops-cli doctor --json   # expect ok:true, accountsEnabled > 0, exit 0
emailops-cli accounts --json # expect a non-empty data array

If doctor returns ok:true with at least one enabled account, the skill is correctly wired and downstream commands can be trusted.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.