Install
openclaw skills install zoho-cliRead, search, send, and manage Zoho Mail from the terminal. JSON output for scripting and agents. No third-party service required.
openclaw skills install zoho-cliRequirements: the zoho binary (install via brew/uv/pipx below), one-time OAuth setup, and local credential storage (config + OS keyring).
zoho is a command-line tool for Zoho Mail. All output is JSON by default — structured, pipe-friendly, and agent-ready. Use --md for markdown tables.
Requires Python 3.11+. Works on macOS, Linux, and Windows.
# Homebrew (macOS / Linux)
brew install robsannaa/tap/zoho-cli
# uv (all platforms)
uv tool install git+https://github.com/robsannaa/zoho-cli
# pipx (all platforms)
pipx install git+https://github.com/robsannaa/zoho-cli
After install, run the one-time setup:
zoho config init # save your Zoho OAuth client_id / client_secret
zoho login # open browser, region auto-detected
See the full setup guide in the README.
zoho must be installed and authenticated (zoho login completed).zoho config show — must return a JSON object with default_account and a non-empty accounts map.--md). Always machine-readable.# errors land on stderr, not stdout
zoho mail list 2>/dev/null # stdout empty on error
zoho mail list 2>&1 >/dev/null # see error JSON
Tokens are stored in the OS keyring and refreshed automatically before every command. No manual token management needed.
Credential storage (sensitive): Users must be aware that the CLI stores sensitive data locally:
zoho config init in the config file (e.g. ~/.config/zoho-cli/config.json).ZOHO_TOKEN_PASSWORD is set, in an encrypted file). These are used to access Zoho Mail on the user's behalf.Do not expose config files or keyring entries to untrusted parties.
If a command fails with not_logged_in or token_refresh_failed, tell the user to run:
zoho login
--account EMAIL select account (env: ZOHO_ACCOUNT)
--config PATH config file path (env: ZOHO_CONFIG)
--md markdown table output instead of JSON
--debug HTTP + debug logs to stderr
zoho mail listList messages in a folder.
zoho mail list # Inbox, 50 messages
zoho mail list --folder Sent --limit 20
zoho mail list -f "My Project" -n 100
Output — array of message summaries:
[
{
"messageId": "1771333290108014300",
"folderId": "8072279000000002014",
"subject": "Q1 invoice",
"from": "billing@acme.com",
"to": ["you@example.com"],
"date": "1740067200000",
"unread": true,
"hasAttachments": true,
"tags": []
}
]
date is a Unix timestamp in milliseconds.
zoho mail searchzoho mail search "invoice 2025"
zoho mail search "from:boss@example.com" --limit 10
Same output shape as mail list.
zoho mail getFull message content including body.
zoho mail get MESSAGE_ID
zoho mail get MESSAGE_ID --folder-id FOLDER_ID # faster: skips auto-scan
zoho mail get MESSAGE_ID | jq '.textBody'
Output:
{
"messageId": "...",
"folderId": "...",
"subject": "Q1 invoice",
"from": "billing@acme.com",
"to": ["you@example.com"],
"cc": [],
"bcc": [],
"date": "1740067200000",
"unread": false,
"hasAttachments": true,
"tags": [],
"textBody": "Please find the invoice attached.",
"htmlBody": "<p>Please find...</p>"
}
zoho mail attachmentszoho mail attachments MESSAGE_ID
zoho mail attachments MESSAGE_ID --folder-id FOLDER_ID
Output:
[
{ "attachmentId": "256063600000020001", "fileName": "invoice.pdf", "size": 123456 }
]
zoho mail download-attachmentzoho mail download-attachment MESSAGE_ID ATTACHMENT_ID --out /tmp/invoice.pdf
Output:
{ "status": "ok", "path": "/tmp/invoice.pdf", "size": 123456 }
zoho mail sendzoho mail send --to alice@example.com --subject "Hi" --text "Hello!"
# HTML + multiple recipients + attachments
zoho mail send \
--to alice@example.com --to bob@example.com \
--cc manager@example.com \
--subject "Report" \
--html-file report.html \
--attach report.pdf --attach data.csv
Output:
{ "status": "ok", "messageId": "..." }
All accept one or more MESSAGE_ID arguments.
zoho mail mark-read ID [ID …]
zoho mail mark-unread ID [ID …]
zoho mail move ID [ID …] --to "Archive"
zoho mail spam ID [ID …]
zoho mail not-spam ID [ID …]
zoho mail archive ID [ID …]
zoho mail unarchive ID [ID …]
zoho mail delete ID [ID …] # → Trash
zoho mail delete ID [ID …] --permanent # hard delete
Output:
{ "status": "ok", "updated": ["ID1", "ID2"], "failed": [] }
zoho folders listzoho folders list
Output:
[
{ "folderId": "8072279000000002014", "folderName": "Inbox", "folderType": "Inbox",
"path": "/Inbox", "isArchived": 0, "unreadCount": 3, "messageCount": 42 }
]
zoho folders create / rename / deletezoho folders create "Project X" [--parent-id FOLDER_ID]
zoho folders rename FOLDER_ID "New Name"
zoho folders delete FOLDER_ID
zoho config show / path / initzoho config show # JSON config dump (secret redacted)
zoho config path # {"config_path": "...", "exists": true}
zoho config init # interactive wizard (prompts on stderr)
# All unread message subjects
zoho mail list | jq -r '.[] | select(.unread) | .subject'
# Get the latest message ID
zoho mail list -n 1 | jq -r '.[0].messageId'
# Get plain-text body of a specific message
zoho mail get "$MSG_ID" | jq -r '.textBody'
# Download all attachments from a message
zoho mail attachments "$MSG_ID" | jq -r '.[].attachmentId' | while read id; do
zoho mail download-attachment "$MSG_ID" "$id" --out "/tmp/${id}"
done
# Find unread emails from a sender
zoho mail search "from:boss@example.com" | jq '[.[] | select(.unread)]'
# Mark all results as read
zoho mail search "invoice" | jq -r '.[].messageId' | xargs zoho mail mark-read
# Move search results to a folder
zoho mail search "newsletter" | jq -r '.[].messageId' | xargs zoho mail move --to "Archive"
# Send with computed body
zoho mail send \
--to report-reader@example.com \
--subject "Daily digest $(date +%F)" \
--text "$(zoho mail list | jq -r '.[].subject' | head -10 | nl)"
All errors are JSON on stderr:
{
"status": "error",
"error": "not_logged_in",
"details": "No stored token for you@example.com. Run: zoho login --account you@example.com"
}
Common error codes:
| Code | Meaning |
|---|---|
not_logged_in | No token stored — user must run zoho login |
token_refresh_failed | Token revoked — user must run zoho login |
no_account_id | accountId not saved — run zoho login again |
folder_not_found | Folder name doesn't match any folder |
message_not_found | Message ID not found in any folder |
api_error | Upstream Zoho API error (details in message) |