Install
openclaw skills install eppie-email-cliControl the eppie-console CLI mail client to inspect accounts, folders, read/send messages, sync folders, and manage vaults with JSON output and automation s...
openclaw skills install eppie-email-cliUse this skill when an autonomous or semi-autonomous agent must operate eppie-console directly.
eppie-console is a console mail client that can work with:
Use eppie-console in a predictable, automation-friendly way to:
This skill focuses on deterministic command execution, structured output handling, and explicit standard-input contracts.
For end-to-end validation, coverage, and reproducible test scenarios, use eppie-cli-agent-test-plan.md.
Treat sections marked Normative as the required contract for agent execution.
Treat sections marked Reference as supporting material, examples, or quick navigation aids.
Normative sections in this file:
Normative defaults for agentsNormative rulesNormative command patterns for agentsNormative stdin contractsNormative execution policy for autonomous agentsNormative error handlingReference sections in this file:
Reference: preferred installationReference: required inputsReference: launch modesReference: decision guideReference: end-to-end agent scenariosReference: command-specific notes and examplesReference: recommended agent workflowReference: known limitationsReference: contributingWhen the task requires executing eppie-console, use these defaults unless the task explicitly requires otherwise:
--non-interactive=true--output=json for agent automation unless text output is explicitly required--unlock-password-stdin=true for stateful commands that need an existing vault--assume-yes=true for destructive automationstdin exactly in the required orderCanonical command patterns and exact stdin contracts are defined later in this file. Use those sections as the single source of truth for agent execution.
eppie-consoleeppie-consoleeppie-cli-agent-test-plan.mdPrefer the executable published in GitHub Releases. You can also build from source.
Latest release page:
Direct download links:
After extracting the archive, use the eppie-console executable directly.
eppie-console-a; for agent workflows, use the account address string returned by list-accounts in data[].addressidpkUse only when a REPL session is explicitly needed.
Example:
eppie-consoleTypical sequence:
openlist-accountsexitopen is useful in interactive mode to open the local vault inside the current process.
Prefer this mode for agents.
Use:
eppie-console --non-interactive=true <command> [command options]To avoid passing the same startup flags on every call, you can put them in appsettings.json next to eppie-console, for example:
{ "non-interactive": true, "output": "json", "unlock-password-stdin": true }eppie-console also reads configuration from environment variables.
When the default .NET host configuration pipeline is used, environment variables override appsettings.json. Command-line arguments override both.
For structured machine-readable output, also use:
--output=jsonJSON responses use a normalized envelope:
type: result, status, warning, or errorcode: stable machine-readable outcome codedata: payload for result and status responsesmeta: optional metadata for result responses; usually null, but paged read commands can return objects such as {"header":"All messages:","returned":5,"hasMore":true}message: human-readable warning/error textIn --non-interactive=true --output=json mode, structured responses are emitted on stdout without a preceding stack trace on stderr for handled command failures such as unhandledException.
In non-interactive mode, do not use open as part of the agent workflow. It does not establish reusable state for later process launches. For stateful non-interactive commands, use --unlock-password-stdin=true instead.
For all agent examples in this file, <account> means the account address returned by list-accounts in data[].address. Prefer that address string for -a instead of the numeric id, unless a command explicitly documents another identifier format.
Common success-output examples:
--non-interactive=true --output=json list-accounts (with accounts):
{"type":"result","code":"accounts","data":[{"id":1,"address":"<address>","accountType":"Dec"}],"meta":null}--non-interactive=true --assume-yes=true --output=json reset:
{"type":"status","code":"reset","data":null}--non-interactive=true --unlock-password-stdin=true --output=json send ...:
{"type":"status","code":"messageSent","data":{"subject":"<subject>","to":"<receiver>","from":"<sender>"}}--non-interactive=true --unlock-password-stdin=true --output=json sync-folder ...:
{"type":"status","code":"folderSynced","data":{"account":"<account>","folder":"Inbox"}}--non-interactive=true --unlock-password-stdin=true --output=json show-all-messages ...:
{"type":"result","code":"messages","data":[{"id":1,"pk":1,"to":["<receiver>"],"from":["<sender>"],"folder":"Inbox","subject":"<subject>"}],"meta":{"header":"All messages:","returned":1,"hasMore":false}}Additional command-specific JSON result shapes are documented later in Reference: command-specific notes and examples.
Common text success-output examples:
open:
The application was opened successfully.reset:
The application has just been reset.add-account -t dec:
Account <generated-address> added (Dec).send:
Message sent from <sender> to <receiver>. Subject: <subject>sync-folder -f Inbox:
Folder 'Inbox' for account <account> synchronized.Normative command patterns for agents and Normative stdin contracts as normative for agent workflows.-- and put the startup command after --.open creates a reusable session for future process launches.list-folders first.-l / --limit when predictable payload size matters.eppie-console open only opens the vault inside the current process.
It does not create a reusable session for future process launches.
Use this section as the single source of truth for agent command lines. Unless text output is explicitly required, all agent examples below use --output=json.
| Task | Canonical command |
|---|---|
| Initialize a vault | --non-interactive=true --output=json -- init |
| List accounts | --non-interactive=true --unlock-password-stdin=true --output=json -- list-accounts |
| List folders | --non-interactive=true --unlock-password-stdin=true --output=json -- list-folders -a <account> |
| Show all messages | --non-interactive=true --unlock-password-stdin=true --output=json -- show-all-messages -s 10 -l 10 |
| Show one message | --non-interactive=true --unlock-password-stdin=true --output=json -- show-message -a <account> -f <folder> -i <id> -k <pk> |
| Delete one message | --non-interactive=true --unlock-password-stdin=true --output=json -- delete-message -a <account> -f <folder> -i <id> -k <pk> |
| List contacts | --non-interactive=true --unlock-password-stdin=true --output=json -- list-contacts -s 10 -l 10 |
| Show contact messages | --non-interactive=true --unlock-password-stdin=true --output=json -- show-contact-messages -c <contact> -s 10 -l 10 |
| Show folder messages | --non-interactive=true --unlock-password-stdin=true --output=json -- show-folder-messages -a <account> -f <folder> -s 10 -l 10 |
| Sync a folder | --non-interactive=true --unlock-password-stdin=true --output=json -- sync-folder -a <account> -f <folder> |
| Send a message | --non-interactive=true --unlock-password-stdin=true --output=json -- send -s <sender> -r <receiver> -t "<subject>" |
| Reset local data | --non-interactive=true --assume-yes=true --output=json -- reset |
| Add a DEC account | --non-interactive=true --unlock-password-stdin=true --output=json -- add-account -t dec |
| Add a regular IMAP/SMTP account | --non-interactive=true --unlock-password-stdin=true --output=json -- add-account -t email --input-json-stdin |
| Add a Proton Mail account | --non-interactive=true --unlock-password-stdin=true --output=json -- add-account -t proton --input-json-stdin |
Provide stdin exactly in the documented order. Unless a command explicitly reads until end-of-stream, stop after the documented input has been written.
| Command | Exact stdin contract | EOF required |
|---|---|---|
--non-interactive=true --output=json -- init | line 1: new vault password | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- list-accounts | line 1: vault password | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- list-folders -a <account> | line 1: vault password | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- show-all-messages -s <page-size> -l <limit> | line 1: vault password | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- show-message -a <account> -f <folder> -i <id> -k <pk> | line 1: vault password | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- delete-message -a <account> -f <folder> -i <id> -k <pk> | line 1: vault password | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- list-contacts -s <page-size> -l <limit> | line 1: vault password | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- show-contact-messages -c <contact> -s <page-size> -l <limit> | line 1: vault password | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- show-folder-messages -a <account> -f <folder> -s <page-size> -l <limit> | line 1: vault password | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- sync-folder -a <account> -f <folder> | line 1: vault password | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- send -s <sender> -r <receiver> -t "<subject>" | line 1: vault password; line 2 and later: message body; then close stdin | yes |
--non-interactive=true --assume-yes=true --output=json -- reset | no stdin | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- add-account -t dec | line 1: vault password | no |
--non-interactive=true --unlock-password-stdin=true --output=json -- add-account -t email --input-json-stdin | line 1: vault password; remaining bytes: one JSON object | yes |
--non-interactive=true --unlock-password-stdin=true --output=json -- add-account -t proton --input-json-stdin | line 1: vault password; remaining bytes: one JSON object | yes |
| Command group | Needs existing vault | Use --unlock-password-stdin=true | Prefer --output=json | Notes |
|---|---|---|---|---|
init | no | no | yes for agent workflows | writes a new local vault password from stdin |
reset | no | no | yes | destructive; also use --assume-yes=true |
list-accounts, list-folders | yes | yes | yes | inspect vault content |
show-all-messages, show-message, show-folder-messages, show-contact-messages | yes | yes | yes | use -l when payload size matters |
sync-folder | yes | yes | yes | call before reading when fresh messages are required |
send | yes | yes | yes | password first in stdin, then body until EOF |
add-account -t dec | yes | yes | yes | simple vault-backed account creation |
add-account -t email, add-account -t proton | yes | yes | yes | use --input-json-stdin; do not pass secrets in arguments |
open in a non-interactive workflow.list-folders first.-l when an agent needs a predictable maximum payload size.Normative defaults for agents defaults unless the task explicitly requires otherwise.type, code, data, meta, and message from the normalized envelope.warning and error responses as non-success outcomes even when the process exits cleanly.Normative stdin contracts exactly.send, close stdin after the last body line.--input-json-stdin, pass the password first when required, then the JSON payload.stdin contract.list-accounts data[].address to -a.id is accepted interchangeably by all commands.list-accounts when a workflow depends on a newly created account.-s / --page-size and -l / --limit in agent workflows when payload size must be predictable.-s as page size only. Treat -l as the total maximum number of records returned by the command.-l is omitted, paged read commands return up to 20 records by default.meta.returned to confirm how many records were actually returned.meta.hasMore to decide whether another read with a higher -l or a narrower filter is needed.-l.Use Normative command patterns for agents for the exact command line. This guide is only for choosing the next command.
list-accountslist-foldersshow-all-messageslist-folders, then sync-folder, then show-folder-messages when new messages are requiredshow-messagedelete-messagelist-contactsshow-contact-messagessendlist-folders, then sync-folderinitresetThis section is informative. Use Normative command patterns for agents for the exact command line and Normative stdin contracts for the exact stdin shape.
Use this flow when the working directory does not yet contain the required local vault.
initadd-accountlist-accountsdata[].address as <account> in later commandsTypical sequences:
init → add-account -t dec → list-accountsinit → add-account -t email → list-accountsinit → add-account -t proton → list-accountsAgent notes:
add-account -t email and add-account -t proton, provide the structured payload through standard input after the vault password as documented in Normative stdin contractslist-accounts instead of assuming another identifier formatUse this flow when the task requires a concrete folder such as inbox or sent mail and the messages may have changed since the last sync.
list-accountslist-foldersdata[].fullName or data[].rolessync-foldershow-folder-messagesshow-message using its id and pkTypical sequence:
list-accounts → list-folders → sync-folder → show-folder-messages → show-messageAgent notes:
fullName returned by list-folders-s and -l on show-folder-messages when predictable payload size mattersmeta.hasMore is true, treat the result as partial and increase -l only if the task requires a larger setUse this flow when the local vault already contains the sender account and the task is to deliver one message deterministically.
list-accountsdata[].addresssend with the sender, receiver, and subjectstdin linestdinTypical sequence:
list-accounts → sendAgent notes:
--non-interactive=true mode, send reads the body until end-of-stream, so the agent should close stdin after the last body line--output=json and verify the messageSent status response instead of relying only on the process exit codeThe canonical command lines are defined in Normative command patterns for agents, and the exact inputs are defined in Normative stdin contracts. This section keeps only command-specific notes, result shapes, and examples that are easy to reference during agent execution.
Notes:
stdinNotes:
-s / --page-size controls only the page size-l / --limit caps the total number of returned records and should be used by agents to keep payload size predictable-l is omitted, paged read commands return up to 20 records by defaultshow-message: to and from are arraysreturned and hasMoremeta.hasMore before assuming the returned list is completeExpected result shape:
{"type":"result","code":"folders","data":[{"fullName":"Inbox","unreadCount":0,"totalCount":0,"roles":["inbox"]}],"meta":{"account":"<account>"}}Notes:
<account> should be the account address from list-accounts data[].addressshow-folder-messages or sync-folder instead of guessing provider-specific folder namesAll Sent; rely on the returned fullNameroles when you need a machine-readable way to find folders such as inbox or sent mailNotes:
Trash, the command moves the message to TrashTrash, the command deletes it permanentlyNotes:
returned and hasMoreNotes:
returned and hasMoreExpected result shape:
{"type":"status","code":"folderSynced","data":{"account":"<account>","folder":"Inbox"}}Examples:
vault passwordHello from automationSecond body linestdinNotes:
--non-interactive=true mode, send reads the message body until end-of-stream{"type":"status","code":"messageSent","data":{"subject":"<subject>","to":"<receiver>","from":"<sender>"}}PowerShell example with the command line and exact stdin in one block:
@"
vault password
Hello from automation
Second body line
"@ | .\eppie-console --non-interactive=true --unlock-password-stdin=true --output=json -- send -s sender@example.com -r receiver@example.com -t "Hello from automation"
Linux / bash example with the command line and exact stdin in one block:
cat <<'EOF_INPUT' | ./eppie-console --non-interactive=true --unlock-password-stdin=true --output=json -- send -s sender@example.com -r receiver@example.com -t "Hello from automation"
vault password
Hello from automation
Second body line
EOF_INPUT
Notes:
./eppie-console after extracting the archive and making sure the file is executable.Use open only in interactive mode when the agent is already inside a REPL session and must open the local vault in that same process.
Notes:
open as part of the non-interactive agent workflowNotes:
reset is a destructive command and should be called with --assume-yes=true in automation or other non-interactive scripts.Expected result shape:
{"type":"result","code":"accountAdded","data":{"address":"<generated-address>","accountType":"Dec"},"meta":null}Examples:
JSON object:
{
"email": "user@example.com",
"accountPassword": "account password",
"imapServer": "imap.example.com",
"imapPort": 993,
"smtpServer": "smtp.example.com",
"smtpPort": 465
}
stdin shape:
vault password{"email":"user@example.com","accountPassword":"account password","imapServer":"imap.example.com","imapPort":993,"smtpServer":"smtp.example.com","smtpPort":465}
Notes:
email, accountPassword, imapServer, imapPort, smtpServer, and smtpPortstructuredStandardInputInvalidJsonstructuredStandardInputMissingPropertyExamples:
JSON object:
{
"email": "user@proton.me",
"accountPassword": "account password",
"mailboxPassword": "mailbox password",
"twoFactorCode": "123456"
}
stdin shape:
vault password{"email":"user@proton.me","accountPassword":"account password","mailboxPassword":"mailbox password"}
Notes:
email and accountPassword are required in structured inputmailboxPassword is required only if the Proton flow asks for ittwoFactorCode is required only if the Proton flow asks for itmailboxPasswordstructuredStandardInputInvalidJsonstructuredStandardInputMissingPropertyNormative defaults for agents.Normative command patterns for agents.stdin in the exact shape defined in Normative stdin contracts.list-accounts data[].address for -a.list-folders if the next step requires a specific folder name,show-all-messages when you need recent messages across all folders,show-message or delete-message with the returned id and pk.--non-interactive=true mode text output stops after the first page instead of prompting.Reference: end-to-end agent scenarios as a quick reference.--non-interactive=true suppresses interactive prompts, but it does not invent missing data.
Commands that normally ask follow-up questions still need explicit arguments or stdin data.
The main examples are already covered above:
send reads the message body until end-of-stream in --non-interactive=true modeadd-account -t email and add-account -t proton should use --input-json-stdinreset still need --assume-yes=true-l is omittedFor agent automation, prefer deterministic handling over implicit retries.
When --output=json is enabled, handle responses by type first:
type | Meaning | Agent action |
|---|---|---|
result | command returned data | parse data and meta |
status | command completed successfully without a list-style result | read data when present |
warning | handled problem or non-success condition | inspect code; do not assume success; stop unless the workflow explicitly defines a recovery step |
error | handled failure | inspect code, message, and data; stop unless the input can be corrected deterministically |
code | Meaning | Typical agent response |
|---|---|---|
invalidPassword | vault password was rejected | stop; do not retry automatically with the same password |
structuredStandardInputInvalidJson | structured payload is not valid JSON | fix serialization and retry once with corrected JSON |
structuredStandardInputMissingProperty | required JSON property is missing or empty | provide the missing property and retry once |
unhandledException | command failed and returned exception details | inspect data.exceptionType and command context; do not retry blindly |
type first, then code.warning as success.invalidPassword automatically.structuredStandardInputInvalidJson or structuredStandardInputMissingProperty, change only the invalid input and keep the command line unchanged.unhandledException, record the command, the parsed code, and data.exceptionType when present.meta.hasMore: true, treat that as an incomplete result set rather than an error.Invalid password example:
{"type":"warning","code":"invalidPassword","message":"Warning: Invalid password.","data":null}Unknown sender account example for send:
{"type":"error","code":"unhandledException","message":"An error has occurred: ...","data":{"exceptionType":"Tuvi.Core.Entities.AccountIsNotExistInDatabaseException"}}Invalid Proton structured input example:
{"type":"error","code":"structuredStandardInputInvalidJson","message":"Error: The remaining standard input for the 'add-account' command is not valid JSON.","data":{"commandName":"add-account"}}Missing Proton structured property example:
{"type":"error","code":"structuredStandardInputMissingProperty","message":"Error: The structured standard input for the 'add-account' command must contain a non-empty 'email' property.","data":{"commandName":"add-account","propertyName":"email"}}Contributions are welcome.
Please open issues for bugs, UX problems, and feature ideas. Pull requests on GitHub are also welcome.
Repository: