Skip to content

FEAT: Round out the non-interactive CLI (read + manage mail, not just send) #1642

Closed as not planned

Description

@Robin-Everaars

Is your feature request related to a problem?

The CLI docs open with "these work without launching the TUI and are ideal for scripts, cron jobs, and AI agent integration," but on the mail side the only thing you can actually do non-interactively today is matcha send. Reading and managing mail still needs the TUI, so there's no way to script "list what's in this folder", "print this message", or "archive/mark these" without driving the interactive UI. That's the gap between what the docs promise and what's exposed.

Describe the solution you'd like

Expose the read and manage operations as subcommands, following the same pattern matcha send already uses. I'm filing this as one request rather than piecemeal because the groundwork is basically all there: the operations already exist as plain package-level functions in fetcher/ that take a *config.Account and run directly (no daemon needed), which is what runSendCLI calls today. For example:

  • fetcher.FetchFolders
  • fetcher.FetchFolderEmails / FetchFolderEmailBody
  • fetcher.MarkEmailAsReadInMailbox / MarkEmailAsUnreadInMailbox
  • fetcher.ArchiveEmailFromMailbox / DeleteEmailFromMailbox / MoveEmailsToFolder
  • fetcher.SearchMailbox

So a first cut is mostly flag parsing plus an output formatter. Rough shape, mirroring matcha send:

matcha folders [--account <email>]
matcha list    --folder INBOX [--account <email>] [--limit 50] [--offset 0]
matcha read    --folder INBOX --uid <n> [--account <email>]
matcha search  --folder INBOX --query "..." [--account <email>]
matcha mark-read | mark-unread --folder INBOX --uid <n> [--account <email>]
matcha archive | delete --folder INBOX --uid <n> [--account <email>]
matcha move    --uid <n> --from INBOX --to Archive [--account <email>]

A --json option on the read commands (list, read, folders, search) would make this genuinely usable from scripts and agents, which the docs already name as a goal. Default to a human-readable table, JSON when asked. Account selection could reuse the same --from matching send already has (login + fetch email).

Describe alternatives you've considered

Scripting the TUI with expect-style input (brittle, breaks on any UI change), or talking to the daemon socket directly (daemonrpc is internal and not a stable contract). Neither is a real substitute for first-class subcommands.

Additional context

runSendCLI in main.go is the template to copy (parse flags, LoadConfig, resolve account, call the fetcher/sender function, print), and new commands slot into cli/ next to config.go, contacts_export.go, dict.go, etc., wired through the os.Args dispatch in main(). matcha also has the daemonclient.Service interface (used by the TUI and daemon) wrapping these same operations mode-agnostically, if routing the CLI through that is preferred for daemon-awareness. Either way the underlying operations already exist, so this is mostly surface work.

Before I build anything though I'd like your read on it: is this something you'd want, and is the command/flag shape roughly right? If it looks good to you I'll take it on and open a PR.

Version: matcha 0.43.0.

Activity

  1. added
    area/buildBuild system / Makefile / packaging
    area/cliCLI flags / commands
    area/configConfiguration / settings
    area/docsDocs site / README
    area/fetcherIMAP fetch / IDLE / search
    area/tuiTerminal UI / view layer
    ciCI / build pipeline
    on Jul 2, 2026
  2. Robin-Everaars commented on Jul 10, 2026

    @Robin-Everaars
    Author

    Couldn't resist taking a crack at this on my fork: https://github.com/Robin-Everaars/matcha/tree/feat/non-interactive-cli

    It does list/read/search/folders plus archive/delete/mark-read/mark-unread/move, all through the existing service layer (search needed a new RPC method on the daemon). Same query syntax as the TUI search, and tests for everything.

    Would be glad to turn this into a PR if this lines up with what you had in mind!

  3. andrinoff commented on Jul 10, 2026

    @andrinoff
    Member

    @Robin-Everaars could you open a PR, so that we could review, please?

  4. moved this from Backlog to In review in v1 Releaseon Jul 13, 2026
  5. Robin-Everaars commented on Jul 13, 2026

    @Robin-Everaars
    Author

    Opened #1680 for this.

    A few notes on where the implementation landed vs. the shape I sketched above:

    • Positional args instead of flags. Rather than --folder/--uid, the commands take positionals to match how send already reads, e.g.:
      matcha folders    [--from <email>] [--json]
      matcha list       [folder] [--from <email>] [--json]        # folder defaults to INBOX
      matcha read       [--from <email>] [--json] <folder> <uid>  # a lone numeric positional = UID in INBOX
      matcha search     [--from <email>] [--json] <folder> <query>
      matcha archive|delete|mark-read|mark-unread [--from <email>] <folder> <uids>
      matcha move       [--from <email>] <src_folder> <dst_folder> <uids>
      
      <uids> accepts more than one, so you can archive/mark a batch in one call.
    • --from, not --account, reusing the exact account-matching send already has (login, with a fetch-email fallback).
    • --json on the read side (folders, list, read, search); the read command's JSON includes attachment metadata but omits the raw bytes.
    • Routed through daemonclient.Service rather than calling the fetcher functions directly. This was the daemon-aware option I mentioned. A one-shot invocation uses a running daemon if there is one and otherwise falls back to a direct connection, so it never forks a daemon just to run a single command. That needed a Search method adding to the service layer (there was a handler gap), which the PR includes.

    All commands have unit tests, and go build / go vet / go test ./... are green.

    Happy to switch to explicit --folder/--uid flags or tweak any of the naming if you'd prefer a different shape which is easy to change before merge.

  6. floatpanebot commented on Aug 13, 2026

    @floatpanebot
    Member

    This has had no activity for 30 days. It will be closed in 14 days unless updated. Comment or remove the stale label to keep it open.

  7. floatpanebot commented on Aug 27, 2026

    @floatpanebot
    Member

    Closing due to inactivity. Reopen if still relevant.

  8. moved this from In review to Done in v1 Releaseon Aug 27, 2026
  9. Robin-Everaars commented on Oct 27, 2026

    @Robin-Everaars
    Author

    I've split #1680 into five smaller PRs against release/v1, as you asked there. Merge them in order:

    1. feat(cli): add folders subcommand #1751 folders
    2. feat(cli): add list subcommand #1752 list
    3. feat(cli): add read subcommand #1753 read
    4. feat(cli): add search subcommand #1754 search
    5. feat(cli): add mail manage commands #1755 archive, delete, mark-read, mark-unread, move

    The stale bot closed this issue while I was redoing the work, and I can't reopen it myself. @andrinoff, could you reopen it? #1755 closes it once the stack is in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/buildBuild system / Makefile / packagingarea/cliCLI flags / commandsarea/configConfiguration / settingsarea/daemonDaemon / RPCarea/docsDocs site / READMEarea/fetcherIMAP fetch / IDLE / searcharea/senderSMTP send patharea/tuiTerminal UI / view layerciCI / build pipelinedocumentationDocumentation changesstaleNo activity for extended period

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions