A deep dive into hey-cli

By

hey-cli brings HEY email, contacts, calendars, todos, habits, time tracking, journal entries, automation, and AI agent workflows to your terminal.

~~~

hey-cli puts HEY in your terminal.

You can read and send email, search threads, manage The Screener, organize messages, and download attachments.

But it goes further than email.

You can also work with contacts, calendars, todos, habits, time tracking, and journal entries. There is an interactive terminal app for people, structured JSON for scripts, and an embedded skill for coding agents.

This also makes hey-cli great for AI agents. An agent now has full access to your email — or to their email, if you give the agent a HEY account of its own. Search, read, draft, organize: it all happens through one documented command.

It is not a generic email client. It works with HEY and uses HEY’s own concepts, including the Imbox, The Feed, Paper Trail, Reply Later, Set Aside, Bubble Up, and The Screener.

I’ve used HEY since it launched, and it’s my favorite email tool. My email already lives there, so hey-cli gives me a new way to work with a system I know well.

The HEY homepage in Safari announcing its email and calendar service

Let’s install it on macOS and see how the pieces fit together.

What is hey-cli?

hey-cli is both a CLI and a TUI.

A CLI runs one command, prints a result, and exits:

hey search "invoice"

A TUI is an interactive application drawn inside your terminal:

hey tui

The CLI is useful when you know what you want to do. It also works well in scripts and with AI agents.

The TUI is useful when you want to browse. You can move between mail, contacts, calendars, and your journal without remembering every command.

Both interfaces use the same HEY account.

How hey-cli is built

hey-cli is written in Go and distributed as one executable file.

You do not need Node.js, Python, Ruby, or a separate runtime. The macOS installer downloads the correct binary for your Mac.

The project uses:

  • Cobra for the command structure
  • Bubble Tea for the terminal interface
  • the HEY Go SDK for HEY operations
  • Action Cable connections for live updates
  • the macOS Keychain for stored credentials

There are separate macOS builds for Apple silicon and Intel Macs. The installer reads your architecture and chooses the correct one.

The project is open source under the MIT license. Basecamp publishes the source, release scripts, tests, and agent skill in the same repository.

What you need

You don’t need a Mac to use hey-cli. It also runs on Linux, WSL2, and Windows.

This tutorial uses macOS, so the installation steps focus on a Mac.

You only need 2 things:

  1. a HEY account
  2. a terminal

You do not need to install Go when using a release.

If terminal commands are new to you, my free Shell Commands Course explains paths, commands, pipes, files, environment variables, and processes.

Install hey-cli on macOS

The official installation command is:

curl -fsSL https://hey.com/install-cli | bash

Let’s unpack what this does.

curl downloads the official installer. The -f option stops on an HTTP error, -sS hides normal progress but keeps errors visible, and -L follows redirects.

The pipe sends the script to Bash.

The installer then:

  1. detects macOS
  2. detects Apple silicon or Intel
  3. finds the latest release
  4. downloads the matching archive
  5. verifies its SHA-256 checksum
  6. verifies the Sigstore signature when a supported cosign is installed
  7. extracts the hey executable
  8. adds its directory to your PATH when needed
  9. checks that the installed command runs

This is what a successful installation looks like:

hey-cli being downloaded, verified, and installed in a macOS terminal

On macOS, the default install directory is usually:

~/.local/bin

If ~/bin or ~/.local/bin is already in your PATH, the installer prefers the one already configured.

Zsh is the default shell on current macOS versions. When the chosen directory is missing from PATH, the installer adds it to ~/.zshrc.

Reload the file after installation:

source ~/.zshrc

Or close the terminal and open it again.

If you use Fish

The installer detects Fish too.

If ~/.local/bin is not already in your PATH, it adds this line to ~/.config/fish/config.fish:

fish_add_path "$HOME/.local/bin"

Open a new terminal, or reload the Fish configuration:

source ~/.config/fish/config.fish

Now check where the command is installed:

command -v hey

Then check the version:

hey version

Why setup does not start inside the installer

The install command sends a script through a pipe. This means Bash does not have an interactive input terminal during installation.

The installer finishes safely instead of trying to open a setup prompt through that pipe. It prints the next commands to run.

Start the setup yourself:

hey setup

You can also run the bare command:

hey

On the first interactive run, hey starts the setup flow when you are logged out.

Choose a different installation directory

You can tell the installer where to place the executable.

For example, install into ~/bin:

curl -fsSL https://hey.com/install-cli | HEY_BIN_DIR="$HOME/bin" bash

The environment variable belongs on the bash side of the pipe. That is the process running the installer.

Install without connecting a coding agent

The piped installer already skips the interactive setup wizard. It still installs the HEY skill without prompting. When it detects exactly one coding agent, it also connects that agent. When it detects several, it connects none and prints the per-agent setup commands instead.

Set HEY_SETUP_AGENT=none if you want installation to leave agent integrations unchanged:

curl -fsSL https://hey.com/install-cli | HEY_SETUP_AGENT=none bash

If you save the installer and run it interactively instead of piping it, HEY_SKIP_SETUP=1 skips its setup wizard.

Install with Homebrew

You can use Homebrew instead of the installer script:

brew install --cask basecamp/tap/hey

Homebrew manages the installed files and shell completions for you.

There is one practical difference when updating. A Homebrew installation follows Homebrew’s version. The hey upgrade command delegates the update to Homebrew instead of replacing the executable itself.

I would use the official installer on a personal Mac. It has fewer moving pieces and can update itself.

I would use Homebrew when I already manage all command-line tools through Homebrew and want one consistent upgrade workflow.

Use one method. Do not install the same command with both.

Sign in to HEY

The normal login uses browser-based OAuth.

Run:

hey auth login

hey-cli opens your browser. Sign in to HEY, approve access, then return to the terminal.

The first-run setup command does this too:

hey setup

The full setup also installs shell completions and offers to connect supported coding agents.

If you only want the HEY command and do not want agent integrations, run:

hey setup --skip-agents

Check the current session with:

hey auth status

Log out with:

hey auth logout

On macOS, hey-cli stores credentials in the system keyring, which means the Keychain. If the keyring is unavailable, it can fall back to ~/.config/hey-cli/credentials.json.

My advice is to use the browser login. Avoid putting a token directly in a shell command because that command can remain in your shell history.

Open the terminal interface

Start the interactive interface with:

hey tui

The hey-cli TUI showing the HEY Imbox and its keyboard shortcuts

The TUI has 4 main areas:

  • Mail
  • Contacts
  • Calendar
  • Journal

Press ? to show or hide the shortcut bar.

In Mail, use the arrow keys to move through threads and Enter to open one. The list loads more threads as you reach the bottom.

Some useful shortcuts are:

  • / or s searches
  • r replies
  • f forwards
  • v moves a thread
  • e marks it seen
  • u marks it unseen
  • i moves it to the Imbox
  • l moves it to Reply Later
  • a moves it to Set Aside
  • d moves it to The Feed
  • p moves it to Paper Trail
  • t moves it to Trash
  • ! marks it as spam
  • - ignores future activity
  • + stops ignoring it

Press Ctrl+S to open The Screener. Press Shift+O for Contacts, Shift+C for Calendar, Shift+L for Labels, and Shift+K for Collections.

You can also start the TUI on a specific destination:

hey tui --topic 123
hey tui --screener

The first command opens the thread with topic ID 123. The second opens The Screener.

The TUI keeps its visible mail and calendar data updated through live connections. If the connection drops, it shows an offline status and reconnects.

This is a real terminal application, not a web page squeezed into a terminal window.

Find the available commands

Start with the main help:

hey --help

The hey-cli help screen listing its main mail, contact, calendar, and journal commands

Every command has its own help page:

hey compose --help

To see the complete executable command catalog, run:

hey commands

This is especially useful because hey-cli has many commands. You do not have to memorize them.

There are also focused help topics:

hey help output
hey help exit-codes
hey help environment
hey help linked-accounts

Work with email from the command line

Let’s start with the most common email workflow.

List your boxes

List the available mailboxes:

hey box list

The hey box list command showing the Imbox, Feed, Set Aside, Reply Later, Paper Trail, and Bubble Up boxes

Open the Imbox:

hey box view imbox

You can pass a box name or ID.

The command prints a readable table when it runs in a terminal. Add --json when you need every field:

hey box view imbox --json

Understand the 2 email IDs

HEY exposes 2 IDs that look similar but have different jobs.

The posting id identifies the item inside a box. Use it for organization actions such as moving, labeling, or marking a thread as seen.

The topic_id identifies the email conversation. Use it to read, reply, forward, and share a thread.

The JSON output includes both.

This distinction matters. If a command says it needs a thread ID, it usually means topic_id. If it changes the item inside a box, it usually needs id.

Read a thread

Read a complete thread using its topic ID:

hey thread read 123

The hey thread read command displaying an email as readable text inside the terminal

Entries appear from oldest to newest.

To save the thread as Markdown, run:

hey thread read 123 --markdown > thread-123.md

This format is useful for notes and AI agents. Headings, lists, quotes, code blocks, and links remain structured.

You can also save HEY’s original HTML:

hey thread read 123 --html > thread-123.html

HTML output is only allowed when writing to a pipe or file. hey-cli refuses to dump raw HTML directly into your terminal.

Search email

Search with plain text:

hey search "quarterly planning"

You can refine the search:

hey search --from jane@example.com --date last_30_days

See the values accepted by date, box, label, and attachment filters:

hey search filters

Search results include the topic ID you need to read the full conversation.

Write a new email

Compose an email from the command line:

hey compose --to jane@example.com --subject "Lunch plans" -m "Tuesday works for me."

If you omit -m, hey-cli opens your $EDITOR:

hey compose --to jane@example.com --subject "Lunch plans"

Set your preferred editor through the normal shell variable. For example:

export EDITOR=nano

Message bodies use Markdown. You can write headings, lists, links, quotes, bold text, and fenced code blocks.

Add an attachment with --attach:

hey compose --to jane@example.com --subject "Q3 report" -m "The report is attached." --attach ./report.pdf

The flag is repeatable when you need several files.

Reply and forward

Reply using the topic ID:

hey reply 123 -m "Friday works for me."

Forward the latest message in a thread:

hey forward 123 --to jane@example.com -m "For your review"

hey-cli asks HEY for the correct reply recipients. It removes your own addresses, aliases, and catch-all addresses from the recipient list.

Reply to several threads at once

Sometimes the same short answer fits many threads. bulk-reply sends one reply to all of them. It takes posting IDs.

Always preview first:

hey bulk-reply preview 12345 67890

The preview shows each thread and the exact recipients. You see the blast radius before anything is sent.

Then send:

hey bulk-reply send 12345 67890 -m "Thanks for the update."

The response includes a delivery ID. While HEY’s undo window is open, that ID can recall the whole bulk reply:

hey bulk-reply undo 98765

The TUI does this too. Select threads with Space, press Ctrl+B to preview the recipients, and recall a delayed bulk reply with Ctrl+U.

Use drafts as a review step

Add --draft to save instead of sending:

hey compose --to jane@example.com --subject "Board update" -m "Numbers to follow." --draft

The command returns the new draft ID.

List and inspect drafts:

hey draft list
hey draft show 12345

Edit a field without replacing the others:

hey draft edit 12345 --subject "Board update for August"

Send the reviewed draft:

hey draft send 12345

Or delete it:

hey draft delete 12345

Drafts are one of the best parts of the agent workflow. An agent can prepare a reply, but a person can review it in any HEY app before sending.

The CLI cannot schedule a message for an exact future time. Set that schedule in a HEY app. A schedule already attached to a draft survives CLI edits.

Save attachments

List the attachments in a thread:

hey attachment list 123

Each attachment gets an ID containing its message and position, such as 456:1.

Save one using that ID:

hey attachment save 456:1

hey-cli keeps the original filename and refuses to replace an existing file unless you pass --force.

Share a thread

HEY can create a public link for a thread:

hey share 123

Anyone with the link can read the whole conversation, including future replies. Use it carefully.

Turn the link off with:

hey unshare 123

Organize your email

Mark one or more posting IDs as seen:

hey seen 12345 67890

hey unseen reverses it.

Move a posting to The Feed:

hey move 12345 --to feed

Move several postings to Paper Trail:

hey move 12345 67890 --to "paper trail"

Bubble a thread back to the top of the Imbox tomorrow:

hey bubble up 12345 --tomorrow

List scheduled Bubble Up threads:

hey bubble list

Cancel a scheduled Bubble Up with hey bubble pop 12345.

Trash, spam, and ignore have their own top-level commands:

hey trash 12345
hey spam 12345
hey ignore 12345

Marking a thread as spam also trains HEY’s filters. An ignored thread stays in its box, but new replies stop demanding your attention. hey stop-ignoring reverses it.

You can also manage labels, collections, workflows, clips, and reusable snippets.

For example, create a label and apply it to a posting:

hey label create "Travel receipts" 12345

Create a collection:

hey collection create "Kitchen remodel" --summary "Plans and decisions"

Create a reusable email snippet:

hey snippet create --name "Scheduling reply" --content "Tuesday works for me."

Clips and snippets are different. A clip is a passage saved from an email you received. A snippet is reusable content you insert into a message you write.

Manage The Screener

The Screener holds mail from first-time senders until you decide what to do.

See how many people are waiting:

hey screener list --count

List the queue:

hey screener list

Approve a sender using the clearance ID returned by the list:

hey screener approve 91

Deny a sender:

hey screener deny 91

Marking a denied sender as spam also trains HEY’s filter:

hey screener deny 91 --spam

Use the spam option carefully. It has a stronger effect than a normal denial.

Manage contacts

The TUI has a Contacts area, and the CLI covers contacts too.

List them:

hey contact list

The hey contact list command showing contact IDs, names, email addresses, and update dates

Add one:

hey contact add --name "Jane Doe" --email jane@example.com

Each contact can carry a private note that only you see:

hey contact note set 12345 "Prefers email over calls"

HEY hides contacts instead of deleting them:

hey contact hide 12345

A hidden contact leaves lists, autocomplete, and search, but stays available by ID. hey contact show-again 12345 brings it back.

You can also bundle a contact’s mail into one Imbox row with hey contact bundle 12345, and list it separately again with hey contact unbundle 12345.

Work with linked HEY accounts

One HEY identity can have several linked mail accounts.

List them:

hey account list

Choose a default account:

hey account use 12345

Return to the combined view:

hey account use all

Override the account for one command:

hey --account 12345 box list

Mail commands follow the selected account. Calendars, todos, habits, time tracking, and journal entries remain identity-wide.

In the TUI, press Ctrl+A to switch between All Accounts and each linked account.

Use structured output

hey-cli is designed for both people and programs.

At a terminal, it prints styled human output. When stdout is redirected or piped, it defaults to a JSON response envelope.

You can request JSON explicitly:

hey box list --json

Use --quiet to print the result data without the outer response envelope:

hey box list --quiet

Use --ids-only when another command only needs IDs:

hey box list --ids-only

Use --count for a number:

hey screener list --count

hey-cli also includes a jq-compatible filter. You do not need the separate jq command for basic filtering:

hey box list --jq '.data[] | {id, name}'

Combine --quiet and --jq to filter the result data directly:

hey box list --quiet --jq '.[].id'

The CLI uses stable exit codes. Authentication failures return 3, missing resources return 2, network failures return 6, and rate limits return 5.

This makes scripts more reliable. They can tell the difference between “not logged in,” “not found,” and “the network is down.”

Watch for live changes

The watch command keeps running and prints one JSON object for every change:

hey watch

Watch only for new mail in the Imbox:

hey watch --box imbox --events new

The hey watch command reporting new and updated messages in the Imbox

Wait for one new message, then exit:

hey watch --box imbox --events new --exit-on-first

The hey watch command exiting after the first new message arrives

This is useful in an automation. A script can block until something arrives, handle it, then stop.

You can also ask watch to run a command for every event. Use --run-sync when events must be processed in order, or --run-async when separate events can overlap.

Both modes send the JSON event to the child command through standard input. They also set environment variables such as HEY_CHANGE, HEY_BOX_ID, HEY_POSTING_ID, and HEY_THREAD_ID.

If you want to build reliable automations around this, my free Shell Scripting and Automation Course covers input validation, exit codes, logs, retries, locks, and testing.

Manage calendars and personal data

hey-cli is not limited to mail.

Calendar events

List your calendars:

hey calendar list

List upcoming events:

hey event list

Add an event:

hey event add "Design review" --starts-on 2026-09-02 --start-time 14:00 --end-time 15:00

The Design review event created with hey-cli appearing in HEY Calendar

Without a start time, the event lasts all day. With a start time but no end time, it lasts one hour.

Edit only the title:

hey event edit 4821 --title "Design review moved"

Delete the event:

hey event delete 4821

Todos

List todos:

hey todo list

Add and complete one:

hey todo add "Buy milk"
hey todo complete 1

Undo the completion:

hey todo uncomplete 1

Habits

Create a habit for weekdays:

hey habit create "Practice piano" --icon music --color green --days mon,wed,fri

Mark today’s habit complete:

hey habit complete 1

List the habits for the week containing a date:

hey habit list --date 2026-09-02

Time tracking

Start and stop a timer:

hey timetrack start
hey timetrack stop

Stop a running timer and file it under a category:

hey timetrack stop --category "Client work"

See the current timer:

hey timetrack current

Export completed entries to CSV:

hey timetrack export > tracked-time.csv

The export does not include a timer that is still running.

Journal

Read today’s journal entry:

hey journal read

Write an entry:

hey journal write "Finished the first prototype today."

Omit the text to use your editor:

hey journal write

The editor opens with the existing entry. Saving an empty file removes that day’s entry.

Connect hey-cli to a coding agent

hey-cli contains an agent skill inside the executable.

For me, this is one of the most interesting parts. It unlocks agentic use of HEY in a much simpler way than before.

An agent can use a documented command interface and request structured output. You do not need to build and maintain a custom HEY integration first.

The setup wizard detects supported agents and offers to connect them. You can also do it yourself.

For Codex, run:

hey setup codex

For Claude Code, run:

hey setup claude

Install only the shared skill:

hey skill install

The skill teaches the agent how to use the command, choose IDs, request structured output, and follow HEY workflows.

hey-cli only updates skill directories it owns. A managed directory carries a .managed-by-hey-cli marker. If you already created a custom hey skill, the installer refuses to overwrite or claim it.

Run this to inspect the health of the integration:

hey doctor

An agent can then search email, read a thread, prepare a draft, inspect a calendar, or update a todo through the same hey command.

Remember what this means: the agent can access the HEY data and actions available to your signed-in account.

Use the same care you would use with any tool connected to email. Review the agent’s permissions, keep sending behind a draft when possible, and do not expose bearer tokens in prompts or logs.

Configuration and local trust

Show the active configuration with:

hey config show

The global configuration lives under:

~/.config/hey-cli

A project can provide local settings in .hey/config.json.

That file could change the HEY server or selected account. hey-cli does not trust it silently.

The first interactive command asks whether to use the local values once, trust them, or cancel. A non-interactive or JSON command fails closed until you trust the file from that directory:

hey config trust-local

This protects scripts and agents from a repository that tries to redirect the CLI to another server.

Review trusted local configurations with:

hey config trusted-locals

Remove trust with:

hey config untrust-local

Install shell completions

The setup wizard normally installs shell completions.

You can run the installer yourself:

hey shell-completion install

It detects the shell from $SHELL.

To select Fish explicitly, run:

hey shell-completion install fish

This writes the completion file to ~/.config/fish/completions/hey.fish.

For Zsh, it writes a completion file into a directory on your fpath. If none is available, it uses ~/.local/share/zsh/site-functions/_hey and tells you how to add that directory.

Completion files are also ownership-aware. hey-cli does not replace a file it did not create unless you pass --force.

Upgrade hey-cli

Check the installed version:

hey version

Upgrade to the latest release:

hey upgrade

For a binary installed by the official script, the upgrade downloads the new release, verifies it, replaces the executable, and checks the installed version.

The replacement is transactional. If something fails, hey-cli restores the previous executable when possible.

For Homebrew installations, the command delegates to Homebrew.

Run the health checker after an update or when something feels wrong:

hey doctor

It checks authentication, configuration, updates, shell completions, and agent integrations.

Remove hey-cli

Before removing the program, log out and remove the managed agent integrations:

hey auth logout
hey setup agents --remove

If you installed it with Homebrew, uninstall the cask:

brew uninstall --cask basecamp/tap/hey

If you used the official installer, first find the executable:

command -v hey

Remove that exact file after checking the path. Do not delete the whole containing directory because it may hold other commands.

The installer may also have added its binary directory to ~/.zshrc. Remove that line only if no other tool uses the same directory.

How I would use hey-cli

I see 2 different ways to use hey-cli with agents.

Give an agent its own email address

One option is to give an agent a separate HEY address and let it run an email workflow on its own.

For example, Waiting Lists depends on email across the entire product lifecycle. People join a list, receive launch updates, ask questions, and reply with feedback.

An agent with its own address could send onboarding and follow-up messages. It could also read incoming replies, analyze feedback, manage contacts, and keep every conversation organized.

It could watch for new mail and react without using my personal inbox.

That is the fully autonomous model:

app event → agent sends email → person replies → agent analyzes reply → next action

I would keep its scope narrow. It would use its own address, follow clear rules, and only work with the contacts for that app.

Let an agent help with my email

The other option is to use an AI agent to handle my email under my supervision.

Because HEY is already my email tool, I would use hey-cli as another interface for it. I would not try to replace the HEY apps completely.

I would use the TUI for quick mail checks while working in the terminal. For more precise work, I would use the CLI.

I could ask an agent to search my email, summarize threads, analyze received messages, manage contacts, and prepare drafts. I would review important actions, especially anything that sends email from my address.

The workflow would look like this:

search → read → prepare draft → human review → send

I would also use hey screener list --count for a small status check. It gives me one number without opening the whole queue.

I would use hey watch only for a real automation that needs live events. I would not keep it running just because I can. Email does not need to interrupt every minute of the day.

I would not let an agent send arbitrary email automatically from my personal address. Full autonomy belongs on a separate, app-specific account. For my inbox, the draft workflow gives us a useful checkpoint.

Where hey-cli is not a good fit

hey-cli only works with HEY. It is not an IMAP client for Gmail, Fastmail, or a mail server you run yourself.

It also does not make the visual HEY apps obsolete.

I would still use the web or native app when I want a large calendar view, a visual inbox session, or careful drag-and-drop organization.

The CLI is strongest when the action is clear:

  • find this thread
  • draft this reply
  • move these messages
  • save this attachment
  • list today’s events
  • start a timer
  • wait for one new message

It is also a young project with a large command surface. Commands and features can evolve, so use hey --help and the official repository as the current reference.

hey-cli is interesting because it treats the terminal as more than a smaller inbox.

It gives people, scripts, and coding agents one consistent way to work with HEY. The TUI handles exploration. The CLI handles precise actions. JSON, exit codes, drafts, local trust, and managed skills make the same tool useful for automation without hiding the important boundaries.

Tagged: CLI · All topics

Want me to talk about your product? You can sponsor this site.

~~~

Related posts about cli: