Eppie Email CLI

Security

Control the eppie-console CLI mail client to inspect accounts, folders, read/send messages, sync folders, and manage vaults with JSON output and automation s...

Install

openclaw skills install eppie-email-cli

Eppie CLI agent skill

Use 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:

  • Gmail,
  • Outlook,
  • Proton Mail,
  • regular IMAP/SMTP email services,
  • decentralized peer-to-peer Eppie mail.

Purpose

Use eppie-console in a predictable, automation-friendly way to:

  • inspect accounts,
  • inspect folders,
  • read messages,
  • send messages,
  • sync folders,
  • initialize, unlock, and reset the local vault when needed by the task.

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.

How to use this file

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 agents
  • Normative rules
  • Normative command patterns for agents
  • Normative stdin contracts
  • Normative execution policy for autonomous agents
  • Normative error handling

Reference sections in this file:

  • Reference: preferred installation
  • Reference: required inputs
  • Reference: launch modes
  • Reference: decision guide
  • Reference: end-to-end agent scenarios
  • Reference: command-specific notes and examples
  • Reference: recommended agent workflow
  • Reference: known limitations
  • Reference: contributing

Normative defaults for agents

When the task requires executing eppie-console, use these defaults unless the task explicitly requires otherwise:

  1. use --non-interactive=true
  2. use --output=json for agent automation unless text output is explicitly required
  3. use --unlock-password-stdin=true for stateful commands that need an existing vault
  4. use --assume-yes=true for destructive automation
  5. provide stdin exactly in the required order
  6. use one consistent working directory per vault

Canonical command patterns and exact stdin contracts are defined later in this file. Use those sections as the single source of truth for agent execution.

Use this skill when

  • the task requires executing eppie-console
  • the agent must inspect accounts, folders, messages, or contacts
  • the agent must send a message
  • the agent must synchronize a folder
  • the agent must initialize or reset the local vault
  • the agent must work with machine-readable CLI output

Do not use this skill when

  • the task does not require calling eppie-console
  • the task is only to explain behavior conceptually without running commands
  • the task is primarily about test coverage or validation workflows; use eppie-cli-agent-test-plan.md
  • an interactive REPL session is not explicitly required but would be the only execution path
  • the required executable or working directory is unavailable

Reference: preferred installation

Prefer the executable published in GitHub Releases. You can also build from source.

Latest release page:

Direct download links:

Linux

macOS

Windows

After extracting the archive, use the eppie-console executable directly.

Reference: required inputs

Common inputs

  • path to eppie-console
  • working directory
  • whether the task requires stateful access to an existing vault
  • whether structured JSON output is required

Vault access inputs

  • vault password
  • whether the vault must be initialized first

Reading inputs

  • account identifier for -a; for agent workflows, use the account address string returned by list-accounts in data[].address
  • folder name when folder-specific access is needed
  • message id
  • message pk
  • page size and total limit for paged commands

Sending inputs

  • sender address
  • receiver address
  • subject
  • message body

Account creation inputs

  • account type
  • vault password
  • structured JSON payload when required
  • provider-specific secrets only through standard input when supported

Reference: launch modes

Interactive mode

Use only when a REPL session is explicitly needed.

Example:

  • eppie-console

Typical sequence:

  • open
  • vault password
  • list-accounts
  • exit

open is useful in interactive mode to open the local vault inside the current process.

Non-interactive mode

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=json

JSON responses use a normalized envelope:

  • type: result, status, warning, or error
  • code: stable machine-readable outcome code
  • data: payload for result and status responses
  • meta: 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 text

In --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 rules

  • Treat Normative command patterns for agents and Normative stdin contracts as normative for agent workflows.
  • Put global launch options before -- and put the startup command after --.
  • Do not assume open creates a reusable session for future process launches.
  • Synchronize the relevant folder before reading when the task requires newly arrived messages.
  • Do not guess provider-specific folder names; call list-folders first.
  • Use -l / --limit when predictable payload size matters.
  • Do not pass account secrets in command-line arguments when structured standard input is supported.

eppie-console open only opens the vault inside the current process. It does not create a reusable session for future process launches.

Normative command patterns for agents

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.

TaskCanonical 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

Normative stdin contracts

Provide stdin exactly in the documented order. Unless a command explicitly reads until end-of-stream, stop after the documented input has been written.

CommandExact stdin contractEOF required
--non-interactive=true --output=json -- initline 1: new vault passwordno
--non-interactive=true --unlock-password-stdin=true --output=json -- list-accountsline 1: vault passwordno
--non-interactive=true --unlock-password-stdin=true --output=json -- list-folders -a <account>line 1: vault passwordno
--non-interactive=true --unlock-password-stdin=true --output=json -- show-all-messages -s <page-size> -l <limit>line 1: vault passwordno
--non-interactive=true --unlock-password-stdin=true --output=json -- show-message -a <account> -f <folder> -i <id> -k <pk>line 1: vault passwordno
--non-interactive=true --unlock-password-stdin=true --output=json -- delete-message -a <account> -f <folder> -i <id> -k <pk>line 1: vault passwordno
--non-interactive=true --unlock-password-stdin=true --output=json -- list-contacts -s <page-size> -l <limit>line 1: vault passwordno
--non-interactive=true --unlock-password-stdin=true --output=json -- show-contact-messages -c <contact> -s <page-size> -l <limit>line 1: vault passwordno
--non-interactive=true --unlock-password-stdin=true --output=json -- show-folder-messages -a <account> -f <folder> -s <page-size> -l <limit>line 1: vault passwordno
--non-interactive=true --unlock-password-stdin=true --output=json -- sync-folder -a <account> -f <folder>line 1: vault passwordno
--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 stdinyes
--non-interactive=true --assume-yes=true --output=json -- resetno stdinno
--non-interactive=true --unlock-password-stdin=true --output=json -- add-account -t decline 1: vault passwordno
--non-interactive=true --unlock-password-stdin=true --output=json -- add-account -t email --input-json-stdinline 1: vault password; remaining bytes: one JSON objectyes
--non-interactive=true --unlock-password-stdin=true --output=json -- add-account -t proton --input-json-stdinline 1: vault password; remaining bytes: one JSON objectyes

Command summary

Command groupNeeds existing vaultUse --unlock-password-stdin=truePrefer --output=jsonNotes
initnonoyes for agent workflowswrites a new local vault password from stdin
resetnonoyesdestructive; also use --assume-yes=true
list-accounts, list-foldersyesyesyesinspect vault content
show-all-messages, show-message, show-folder-messages, show-contact-messagesyesyesyesuse -l when payload size matters
sync-folderyesyesyescall before reading when fresh messages are required
sendyesyesyespassword first in stdin, then body until EOF
add-account -t decyesyesyessimple vault-backed account creation
add-account -t email, add-account -t protonyesyesyesuse --input-json-stdin; do not pass secrets in arguments

Avoid these mistakes

  • Do not use open in a non-interactive workflow.
  • Do not guess provider-specific folder names; call list-folders first.
  • Do not rely only on process exit code when JSON output is available.
  • Do not omit -l when an agent needs a predictable maximum payload size.
  • Do not pass secrets in command-line arguments when structured standard input is supported.

Normative execution policy for autonomous agents

Output handling

  • Follow the Normative defaults for agents defaults unless the task explicitly requires otherwise.
  • Parse type, code, data, meta, and message from the normalized envelope.
  • Do not rely only on process exit code when structured output is available.
  • Treat warning and error responses as non-success outcomes even when the process exits cleanly.

Standard input handling

  • Follow Normative stdin contracts exactly.
  • For send, close stdin after the last body line.
  • For commands using --input-json-stdin, pass the password first when required, then the JSON payload.
  • Do not prepend labels, prompts, or extra blank lines to the documented stdin contract.

Working directory handling

  • Use one consistent working directory for related commands that operate on the same local vault.
  • Use an isolated working directory when the run is disposable or destructive.

Account identifier handling

  • For agent workflows, pass the account address from list-accounts data[].address to -a.
  • Do not assume the numeric id is accepted interchangeably by all commands.
  • Re-read the current address from list-accounts when a workflow depends on a newly created account.

Pagination handling

  • Use both -s / --page-size and -l / --limit in agent workflows when payload size must be predictable.
  • Treat -s as page size only. Treat -l as the total maximum number of records returned by the command.
  • If -l is omitted, paged read commands return up to 20 records by default.
  • Use meta.returned to confirm how many records were actually returned.
  • Use meta.hasMore to decide whether another read with a higher -l or a narrower filter is needed.
  • Do not assume provider folders or message volume are small enough to omit -l.

Reference: decision guide

Use Normative command patterns for agents for the exact command line. This guide is only for choosing the next command.

  • inspect available accounts → list-accounts
  • inspect available folders for an account → list-folders
  • inspect recent messages across folders → sync the relevant folders when new messages are required, then use show-all-messages
  • inspect messages in a specific folder → list-folders, then sync-folder, then show-folder-messages when new messages are required
  • inspect one message → show-message
  • delete one message → delete-message
  • inspect contacts → list-contacts
  • inspect messages for one contact → show-contact-messages
  • send a message → send
  • sync one folder → list-folders, then sync-folder
  • initialize a vault → init
  • reset local data → reset

Reference: end-to-end agent scenarios

This section is informative. Use Normative command patterns for agents for the exact command line and Normative stdin contracts for the exact stdin shape.

Initialize a new vault, add an account, then verify it

Use this flow when the working directory does not yet contain the required local vault.

  1. initialize the vault with init
  2. add the required account with add-account
  3. verify the created account with list-accounts
  4. use the returned data[].address as <account> in later commands

Typical sequences:

  • DEC account: initadd-account -t declist-accounts
  • regular IMAP/SMTP account: initadd-account -t emaillist-accounts
  • Proton Mail account: initadd-account -t protonlist-accounts

Agent notes:

  • keep one consistent working directory across the whole sequence so all commands operate on the same local vault
  • for 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 contracts
  • after account creation, re-read the current address from list-accounts instead of assuming another identifier format

Read the latest message from a specific folder

Use 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.

  1. inspect accounts with list-accounts
  2. inspect folders for the chosen account with list-folders
  3. choose the correct folder name from data[].fullName or data[].roles
  4. synchronize that folder with sync-folder
  5. read a bounded message list with show-folder-messages
  6. open one returned message with show-message using its id and pk

Typical sequence:

  • list-accountslist-folderssync-foldershow-folder-messagesshow-message

Agent notes:

  • do not guess the folder name; use the fullName returned by list-folders
  • use -s and -l on show-folder-messages when predictable payload size matters
  • when meta.hasMore is true, treat the result as partial and increase -l only if the task requires a larger set

Send a message from an existing account

Use this flow when the local vault already contains the sender account and the task is to deliver one message deterministically.

  1. inspect accounts with list-accounts
  2. choose the sender address from data[].address
  3. call send with the sender, receiver, and subject
  4. pass the vault password on the first stdin line
  5. pass the message body starting from line 2, then close stdin

Typical sequence:

  • list-accountssend

Agent notes:

  • if the sender is unknown or the workflow depends on a newly created account, refresh the account list before sending
  • in --non-interactive=true mode, send reads the body until end-of-stream, so the agent should close stdin after the last body line
  • prefer --output=json and verify the messageSent status response instead of relying only on the process exit code

Reference: command-specific notes and examples

The 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.

Init local vault

Notes:

  • writes a new local vault password from stdin

Show all messages

Notes:

  • -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
  • if -l is omitted, paged read commands return up to 20 records by default
  • message items use the same address shape as show-message: to and from are arrays
  • paged result metadata can include returned and hasMore
  • agents should check meta.hasMore before assuming the returned list is complete

List folders

Expected 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[].address
  • use this command before show-folder-messages or sync-folder instead of guessing provider-specific folder names
  • Proton may expose names such as All Sent; rely on the returned fullName
  • use roles when you need a machine-readable way to find folders such as inbox or sent mail

Delete one message

Notes:

  • when called for a message outside Trash, the command moves the message to Trash
  • when called for a message already in Trash, the command deletes it permanently

Show contact messages

Notes:

  • paged result metadata can include returned and hasMore

Show folder messages

Notes:

  • paged result metadata can include returned and hasMore

Sync folder

Expected result shape:

  • {"type":"status","code":"folderSynced","data":{"account":"<account>","folder":"Inbox"}}

Send message

Examples:

  • vault password
  • Hello from automation
  • Second body line
  • close stdin

Notes:

  • in --non-interactive=true mode, send reads the message body until end-of-stream
  • JSON success shape: {"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:

  • on Linux and macOS, use ./eppie-console after extracting the archive and making sure the file is executable.

Open local vault

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:

  • do not use open as part of the non-interactive agent workflow

Reset local data

Notes:

  • reset is a destructive command and should be called with --assume-yes=true in automation or other non-interactive scripts.

Add DEC account

Expected result shape:

  • {"type":"result","code":"accountAdded","data":{"address":"<generated-address>","accountType":"Dec"},"meta":null}

Add regular IMAP/SMTP account

Examples:

JSON object:

{
  "email": "user@example.com",
  "accountPassword": "account password",
  "imapServer": "imap.example.com",
  "imapPort": 993,
  "smtpServer": "smtp.example.com",
  "smtpPort": 465
}

stdin shape:

  • first line: vault password
  • remaining bytes:
{"email":"user@example.com","accountPassword":"account password","imapServer":"imap.example.com","imapPort":993,"smtpServer":"smtp.example.com","smtpPort":465}

Notes:

  • use this mode for regular IMAP/SMTP providers when browser-based OAuth is not needed
  • do not pass account secrets in command-line arguments
  • required structured properties are email, accountPassword, imapServer, imapPort, smtpServer, and smtpPort
  • invalid structured input returns one of these machine-readable errors:
    • structuredStandardInputInvalidJson
    • structuredStandardInputMissingProperty

Add Proton Mail account

Examples:

JSON object:

{
  "email": "user@proton.me",
  "accountPassword": "account password",
  "mailboxPassword": "mailbox password",
  "twoFactorCode": "123456"
}

stdin shape:

  • first line: vault password
  • remaining bytes:
{"email":"user@proton.me","accountPassword":"account password","mailboxPassword":"mailbox password"}

Notes:

  • do not pass Proton secrets in command-line arguments
  • email and accountPassword are required in structured input
  • mailboxPassword is required only if the Proton flow asks for it
  • twoFactorCode is required only if the Proton flow asks for it
  • if the mailbox password is the same as the account password, repeat the same value in mailboxPassword
  • invalid structured input returns one of these machine-readable errors:
    • structuredStandardInputInvalidJson
    • structuredStandardInputMissingProperty

Reference: recommended agent workflow

  1. Start from the defaults in Normative defaults for agents.
  2. Choose the exact command line from Normative command patterns for agents.
  3. Provide stdin in the exact shape defined in Normative stdin contracts.
  4. Use the account address from list-accounts data[].address for -a.
  5. Use an isolated working directory when the run is disposable or destructive.
  6. For message reading:
    • start with list-folders if the next step requires a specific folder name,
    • synchronize the relevant folder before reading when you need newly arrived messages,
    • then use show-all-messages when you need recent messages across all folders,
    • then call show-message or delete-message with the returned id and pk.
  7. In --non-interactive=true mode text output stops after the first page instead of prompting.
  8. For common task chains, use Reference: end-to-end agent scenarios as a quick reference.

Reference: known limitations

Some commands still require explicit non-interactive inputs

--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 mode
  • add-account -t email and add-account -t proton should use --input-json-stdin
  • destructive commands such as reset still need --assume-yes=true
  • paged read commands still default to 20 records when -l is omitted

Normative error handling

For agent automation, prefer deterministic handling over implicit retries.

Interpret the normalized envelope

When --output=json is enabled, handle responses by type first:

typeMeaningAgent action
resultcommand returned dataparse data and meta
statuscommand completed successfully without a list-style resultread data when present
warninghandled problem or non-success conditioninspect code; do not assume success; stop unless the workflow explicitly defines a recovery step
errorhandled failureinspect code, message, and data; stop unless the input can be corrected deterministically

Common machine-readable codes

codeMeaningTypical agent response
invalidPasswordvault password was rejectedstop; do not retry automatically with the same password
structuredStandardInputInvalidJsonstructured payload is not valid JSONfix serialization and retry once with corrected JSON
structuredStandardInputMissingPropertyrequired JSON property is missing or emptyprovide the missing property and retry once
unhandledExceptioncommand failed and returned exception detailsinspect data.exceptionType and command context; do not retry blindly

Agent response rules

  • Parse type first, then code.
  • Do not treat warning as success.
  • Do not retry invalidPassword automatically.
  • Retry only when the failure is deterministic and local to the request, such as malformed JSON or a missing structured property.
  • When retrying after structuredStandardInputInvalidJson or structuredStandardInputMissingProperty, change only the invalid input and keep the command line unchanged.
  • For unhandledException, record the command, the parsed code, and data.exceptionType when present.
  • When a paged read returns meta.hasMore: true, treat that as an incomplete result set rather than an error.

Error-output examples

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"}}

Reference: contributing

Contributions are welcome.

Please open issues for bugs, UX problems, and feature ideas. Pull requests on GitHub are also welcome.

Repository: