A fast, native command-line interface for Zoho Mail, built in Zig. Manage your email, folders, labels, tasks, and organization -- all from the terminal.
zoho-mail is a zero-dependency CLI tool that wraps the Zoho Mail REST API into a set of composable subcommands. It authenticates via OAuth 2.0, supports all six Zoho datacenter regions, and outputs results as formatted tables, JSON, or CSV. Because it compiles to a single static binary, it runs anywhere with no runtime overhead.
- Email management -- list, search, read, send, delete, flag, move, and label messages
- Folder management -- list, create, rename, and delete mail folders
- Label management -- list, create, rename, and delete labels with custom colors
- Task management -- list, show, create, update, and delete tasks (personal and group)
- Account management -- list accounts, view details, set default account
- Organization admin -- list users, domains, and groups
- OAuth 2.0 authentication -- interactive login, token refresh, status check, logout
- Multiple output formats -- table (default), JSON, CSV
- Multi-region support -- US, EU, India, Australia, China, Japan datacenters
- Multi-account support -- switch between accounts with
--accountorset-default - Single binary -- compiles to a static executable with zero runtime dependencies
- XDG-compliant config -- stores configuration in
~/.config/zoho-mail/
- Zig 0.15.0 or later
- A Zoho Mail account
git clone https://github.com/your-username/zoho-mail.git
cd zoho-mail
zig build -Doptimize=ReleaseSafeThe binary is placed at ./zig-out/bin/zoho-mail. You can copy it to a directory on your $PATH:
cp zig-out/bin/zoho-mail /usr/local/bin/zoho-mail --version
# zoho-mail 0.1.0zoho-mail uses OAuth 2.0 with Zoho's Self Client flow. Follow these steps to authenticate.
- Go to the Zoho API Console
- Click Add Client and select Self Client
- Note the Client ID and Client Secret
In the Self Client page of the API Console:
- Under Generate Code, enter the following scopes (comma-separated):
ZohoMail.messages.ALL,ZohoMail.folders.ALL,ZohoMail.labels.ALL,ZohoMail.accounts.READ,ZohoMail.tasks.ALL,ZohoMail.organization.ALL
- Set the Time Duration (e.g., 10 minutes)
- Provide a Scope Description (e.g., "CLI access")
- Click Create and copy the generated authorization code
zoho-mail auth loginYou will be prompted for:
Enter client ID: 1000.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Enter client secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Open this URL in your browser:
https://accounts.zoho.com/oauth/v2/auth?scope=ZohoMail.messages.ALL,...
Enter authorization code: 1000.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
On success, the CLI stores your access and refresh tokens locally. Tokens are automatically refreshed as needed.
zoho-mail auth status
# Authenticated (token valid).Configuration is stored in JSON format at:
~/.config/zoho-mail/config.json
If $XDG_CONFIG_HOME is set, the path is $XDG_CONFIG_HOME/zoho-mail/config.json instead.
Tokens are stored separately at:
~/.config/zoho-mail/tokens.json
{
"region": "com",
"active_account_id": "1234567890",
"client_id": "1000.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"client_secret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"output_format": "table"
}| Field | Description | Default |
|---|---|---|
region |
Zoho datacenter region | com |
active_account_id |
Default account for all operations | (empty) |
client_id |
OAuth client ID | (empty) |
client_secret |
OAuth client secret | (empty) |
output_format |
Default output format | table |
| Variable | Description |
|---|---|
XDG_CONFIG_HOME |
Override base config directory (default: ~/.config) |
HOME |
User home directory (used if XDG_CONFIG_HOME is unset) |
Manage OAuth 2.0 authentication with Zoho.
Interactive OAuth login flow. Prompts for client credentials and authorization code.
zoho-mail auth loginForce a token refresh using the stored refresh token.
zoho-mail auth refresh
# Token refreshed successfully.Check whether the current session is authenticated.
zoho-mail auth status
# Authenticated (token valid).Clear all stored tokens.
zoho-mail auth logout
# Logged out. Tokens cleared.Manage Zoho Mail accounts.
List all accounts associated with the authenticated user.
zoho-mail account listID Email Name Primary
-------------------- ------------------------------ -------------------- --------
1234567890 user@example.com John Doe yes
9876543210 work@example.com John Doe no
Show detailed information for a specific account.
zoho-mail account info 1234567890Account Details
ID: 1234567890
Email: user@example.com
Name: John Doe
Type: personal
Primary: yes
Incoming: imappro.zoho.com
Outgoing: smtp.zoho.com
Set the default account for all subsequent operations.
zoho-mail account set-default 1234567890
# Default account updated.Send, list, search, read, and manage email messages.
List messages in a folder. Defaults to INBOX.
# List the latest 20 messages in INBOX
zoho-mail mail list
# List messages in a specific folder with pagination
zoho-mail mail list --folder INBOX --limit 50 --start 0| Option | Description | Default |
|---|---|---|
--folder |
Folder ID or name | INBOX |
--limit |
Maximum number of messages | 20 |
--start |
Pagination offset | 0 |
Search messages by keyword.
zoho-mail mail search --query "quarterly report"| Option | Description | Required |
|---|---|---|
--query |
Search keyword or phrase | Yes |
Read a single message by ID. Requires the folder ID.
zoho-mail mail read 17300000000000001 --folder INBOXMessage
From: sender@example.com
To: user@example.com
Subject: Meeting Tomorrow
Date: 2026-03-07 14:30:00
Content
Hello, just confirming our meeting tomorrow at 3 PM...
| Argument | Description | Required |
|---|---|---|
<message-id> |
The message ID | Yes |
--folder |
Folder containing the message | Yes |
Send an email message.
zoho-mail mail send \
--to "recipient@example.com" \
--subject "Project Update" \
--body "Please find the latest status report attached."# With CC and BCC
zoho-mail mail send \
--to "recipient@example.com" \
--cc "manager@example.com" \
--bcc "archive@example.com" \
--subject "Weekly Summary" \
--body "Here is this week's summary."| Option | Description | Required |
|---|---|---|
--to |
Recipient email address | Yes |
--subject |
Email subject line | No |
--body |
Email body text | No |
--cc |
CC recipient(s) | No |
--bcc |
BCC recipient(s) | No |
Delete a message by ID.
zoho-mail mail delete 17300000000000001 --folder INBOX
# Message deleted.| Argument | Description | Required |
|---|---|---|
<message-id> |
The message ID | Yes |
--folder |
Folder containing the message | Yes |
Toggle the flag on a message.
zoho-mail mail flag 17300000000000001
# Message flagged.Move a message to a different folder.
zoho-mail mail move 17300000000000001 --folder 17300000000000050
# Message moved.| Argument | Description | Required |
|---|---|---|
<message-id> |
The message ID | Yes |
--folder |
Destination folder ID | Yes |
Mark a message as read.
zoho-mail mail mark-read 17300000000000001
# Message marked as read.Mark a message as unread.
zoho-mail mail mark-unread 17300000000000001
# Message marked as unread.Apply a label to a message.
zoho-mail mail label 17300000000000001 --label 17300000000000099
# Label applied.| Argument | Description | Required |
|---|---|---|
<message-id> |
The message ID | Yes |
--label |
Label ID to apply | Yes |
Create, list, rename, and delete mail folders.
List all folders for the active account.
zoho-mail folder listID Name Path Unread Total
------------------ -------------------- ------------------------- -------- --------
INBOX Inbox /Inbox 12 847
SENT Sent /Sent 0 234
DRAFTS Drafts /Drafts 0 3
17300000000000050 Projects /Projects 5 42
Create a new folder.
# Create a top-level folder
zoho-mail folder create --name "Clients"
# Create a nested folder under an existing parent
zoho-mail folder create --name "Invoices" --parent 17300000000000050| Option | Description | Required |
|---|---|---|
--name |
Folder name | Yes |
--parent |
Parent folder ID for nesting | No |
Rename an existing folder.
zoho-mail folder rename 17300000000000050 --name "Active Projects"
# Folder renamed.| Argument | Description | Required |
|---|---|---|
<folder-id> |
Folder ID to rename | Yes |
--name |
New folder name | Yes |
Delete a folder by ID.
zoho-mail folder delete 17300000000000050
# Folder deleted.Create, list, rename, and delete message labels.
List all labels for the active account.
zoho-mail label listID Name Color Messages
------------------ -------------------- ---------- ----------
17300000000000099 Urgent #ff0000 15
17300000000000100 Follow Up #ff9900 8
17300000000000101 Personal #3366ff 23
Create a new label with a name and color.
zoho-mail label create --name "Review" --color "#9900ff"
# Label created.| Option | Description | Required |
|---|---|---|
--name |
Label name | Yes |
--color |
Hex color code | Yes |
Rename an existing label.
zoho-mail label rename 17300000000000099 --name "Critical"
# Label renamed.| Argument | Description | Required |
|---|---|---|
<label-id> |
Label ID to rename | Yes |
--name |
New label name | Yes |
Delete a label by ID.
zoho-mail label delete 17300000000000099
# Label deleted.Create, list, update, and manage tasks.
List personal tasks, or tasks for a specific group.
# List personal tasks
zoho-mail task list
# List tasks for a group
zoho-mail task list --group 17300000000000200ID Title Status Priority
------------------ ------------------------------ ------------ ----------
17300000000000300 Review Q1 financials open High
17300000000000301 Update team wiki in-progress Medium
17300000000000302 Order office supplies open Low
| Option | Description | Required |
|---|---|---|
--group |
Group ID to filter by | No |
Display detailed information for a single task.
zoho-mail task show 17300000000000300Task Details
ID: 17300000000000300
Title: Review Q1 financials
Status: open
Priority: High
Notes: Need to review before Friday's board meeting
Assignee: user@example.com
Progress: 25%
Create a new task.
zoho-mail task create \
--title "Prepare presentation" \
--notes "For the Q2 kickoff meeting" \
--priority 3
# Task created.| Option | Description | Required | Default |
|---|---|---|---|
--title |
Task title | Yes | -- |
--notes |
Task description / notes | No | (empty) |
--priority |
Priority level: 1 (Low), 2 (Medium), 3 (High) | No | 2 |
Update one or more fields on an existing task.
zoho-mail task update 17300000000000300 \
--status completed \
--priority 1
# Update just the title
zoho-mail task update 17300000000000300 --title "Review Q1 & Q2 financials"
# Task updated.| Argument | Description | Required |
|---|---|---|
<task-id> |
Task ID to update | Yes |
--title |
New title | No |
--status |
New status (e.g., open, completed) | No |
--priority |
New priority (1-3) | No |
Delete a task by ID.
zoho-mail task delete 17300000000000300
# Task deleted.Query organization-level resources. All subcommands require the --zoid (Zoho Organization ID) flag.
List all users in the organization.
zoho-mail org users --zoid 70000000001ZUID Email Name Status Role
------------------ ------------------------------ -------------------- ---------- ------------
80000000001 admin@company.com Jane Admin active admin
80000000002 dev@company.com John Dev active member
List all domains registered to the organization.
zoho-mail org domains --zoid 70000000001Domain Verified MX Status
------------------------------ ---------- ------------
company.com yes active
company.io yes active
staging.company.com no pending
List all email groups in the organization.
zoho-mail org groups --zoid 70000000001ID Email Name Members
------------------ ------------------------------ -------------------- ----------
90000000001 engineering@company.com Engineering 12
90000000002 sales@company.com Sales 8
These options can be placed before any command and apply globally.
zoho-mail [global-options] <command> [subcommand] [options]| Option | Description |
|---|---|
--format <json|table|csv> |
Override the output format for this invocation |
--account <id> |
Override the active account for this invocation |
--region <region> |
Override the Zoho datacenter region |
-h, --help |
Show help text |
-v, --version |
Show version information |
# Get folder list as JSON
zoho-mail --format json folder list
# Use a specific account for one command
zoho-mail --account 9876543210 mail list
# Query the EU datacenter
zoho-mail --region eu account listZoho operates independent datacenters in multiple regions. Set the region in your config file or override it per-command with --region.
| Region Code | Datacenter Location | API Domain |
|---|---|---|
com |
United States | mail.zoho.com |
eu |
Europe | mail.zoho.eu |
in |
India | mail.zoho.in |
com.au |
Australia | mail.zoho.com.au |
com.cn |
China | mail.zoho.com.cn |
jp |
Japan | mail.zoho.jp |
Human-readable, column-aligned output with headers.
zoho-mail mail listID Subject From Date Read
------------------ ---------------------------------------- ------------------------- -------------------- -----
17300000000000001 Meeting Tomorrow sender@example.com 2026-03-07 14:30:00 yes
17300000000000002 Invoice #4521 billing@vendor.com 2026-03-07 09:15:00 no
Machine-readable JSON output, suitable for piping to jq or other tools.
zoho-mail --format json mail list[
{
"messageId": "17300000000000001",
"subject": "Meeting Tomorrow",
"sender": "sender@example.com",
"receivedTime": 1741361400000,
"isRead": true
}
]Comma-separated values for spreadsheet import or data processing.
zoho-mail --format csv folder listzoho-mail/
build.zig Build configuration
build.zig.zon Package manifest (Zig 0.15+)
src/
main.zig Entry point, allocator setup
config.zig Config load/save (~/.config/zoho-mail/config.json)
auth.zig Token storage and refresh logic
http.zig HTTP client wrapper for Zoho API calls
output.zig Table, JSON, and CSV formatters; ANSI colors
cmd/
root.zig Global flag parsing, command dispatch
auth.zig auth login|refresh|status|logout
account.zig account list|info|set-default
mail.zig mail list|search|read (+ dispatch to mail_send, mail_update)
mail_send.zig mail send|delete
mail_update.zig mail flag|move|mark-read|mark-unread|label
folder.zig folder list|create|rename|delete
label.zig label list|create|rename|delete
task.zig task list|show|create|update|delete
org.zig org users|domains|groups
api/
accounts.zig Zoho Mail Accounts API client
messages.zig Zoho Mail Messages API client
folders.zig Zoho Mail Folders API client
labels.zig Zoho Mail Labels API client
tasks.zig Zoho Mail Tasks API client
org.zig Zoho Mail Organization API client
model/
common.zig Region enum, ApiResponse generic, Pagination
account.zig Account data model
message.zig Message data model
folder.zig Folder data model
label.zig Label data model
task.zig Task data model
org.zig OrgUser, Domain, Group data models
The architecture follows a clean three-layer pattern:
- cmd/ -- Command-line argument parsing and user interaction
- api/ -- HTTP calls to the Zoho Mail REST API, response deserialization
- model/ -- Data structures matching the Zoho API JSON schema
# Debug build (fast compilation, runtime safety checks)
zig build
# Release build (optimized)
zig build -Doptimize=ReleaseSafe
# Release build (smallest binary)
zig build -Doptimize=ReleaseSmall# Run without installing
zig build run -- mail list --folder INBOX --limit 10# Run all unit tests (main + models + libs)
zig build testThe test suite covers:
- All command modules via
src/main.zigtransitive reference - Standalone model tests (
common.zig,account.zig,message.zig,folder.zig,label.zig,task.zig,org.zig) - Standalone library tests (
http.zig,config.zig)
Zig supports cross-compilation out of the box:
# Linux x86_64
zig build -Dtarget=x86_64-linux-gnu -Doptimize=ReleaseSafe
# Linux ARM64 (e.g., Raspberry Pi 4)
zig build -Dtarget=aarch64-linux-gnu -Doptimize=ReleaseSafe
# Windows x86_64
zig build -Dtarget=x86_64-windows-gnu -Doptimize=ReleaseSafeContributions are welcome. To get started:
- Fork the repository
- Create a feature branch (
git checkout -b feature/your-feature) - Make your changes, ensuring all tests pass (
zig build test) - Commit with a descriptive message
- Open a pull request against
main
- Follow idiomatic Zig style
- Add tests for new functionality
- Keep command modules focused -- parsing in
cmd/, HTTP inapi/, data inmodel/ - Update this README if adding new commands or flags
The Zoho Mail API enforces rate limits per organization. Be aware of the following when scripting:
| Plan | API Calls / Day | Calls / Minute |
|---|---|---|
| Free | 100 | 10 |
| Standard | 1,000 | 20 |
| Professional | 2,000 | 30 |
| Enterprise | 5,000 | 60 |
When rate-limited, the API returns HTTP 429. The CLI will report the error; retry after the limit window resets.
For the most current rate limit information, consult the Zoho Mail API documentation.
This project is licensed under the MIT License. See LICENSE for details.