# Postfix (cluster) queue manager CLI tool to manage postfix queues (optionally across multiple hosts through ssh). It uses [trio](https://trio.readthedocs.io/en/stable/) for asynchronous operations (i.e. parallel handling of many nodes in a cluster). It also has proper readline support with history of commands. ## Workflow Start with `health` to get a feeling for the queue state. Show the list of deferred mails with `list`, or show all mails from a certain user (maybe `health` showed a suspicious user) with `list from bob@example.com`. Note that `list` (contrary to `list-verbose`) only shows the last (remaining) recipient of a mail (and how many targets are remaining). Build up filters to match only certain mails - e.g. start with `select from MAILER-DAEMON` to search for bounces, and then limit those to no-reply targets with `select to ~reply`. Use `list` or `list-verbose` to see which mails remain, and what the problem is. Use `show mailid` to show headers of suspicious emails, or `show -hb mailid` to show the body too if really necessary. Use `hold` to hold all currently selected mails (e.g. spam/...), and then delete them later with `delete`. Or use `delete mailid` to delete specific mails directly. Use `expire` to return mails to the sender (because you think the error from `list-verbose` is permanent, even if postfix doesn't know it yet). `delete` and `expire` operations prompt for confirmation after showing affected mails. ## List of commands There is an interactive help command. ``` # help clear Clear all filters clear Clear filter with given index (zero based; first filter is at index 0) current Show current filters delete Delete selected mails from the hold queue delete Delete mails with given IDs from the queues delete-all Delete selected mails from all queues exit Exit pqm (or press Ctrl-D on empty prompt) expire Expire mails and flush them (return error to sender). expire Expire mails (return error to sender) with given IDs and flush them. expire-noflush Expire mails (return error to sender on next delivery attempt). expire-noflush Expire mails (return error to sender on next delivery attempt) with given IDs. expire-release Expire mails and flush them (return error to sender); release if mails are on hold. expire-release Expire mails (return error to sender) with given IDs and flush them; release if mails are on hold. flush Flush (deferred) selected mails in the queue: attempt to deliver them flush Flush (deferred) mails with given IDs: attempt to deliver them flush-all Flush the queue: attempt to deliver all queued mail. health Show generic stats for queues and overly active address help Show all available commands help Show detailed help for a command hold Put selected mails on hold hold Put mails with given IDs on hold list [] List all mails (single line per mail); if no filter is configured hide active and hold queue list-all [] List all mails (including active + hold queue, single line per mail) list-verbose [] List all mails (verbose output); if no filter is configured hide active and hold queue list-verbose-all [] List all mails (including active + hold queue, verbose output) pop Remove last filter release Release mail that was put "on hold". release Release mails with given IDs that were put "on hold". requeue Requeue mail (move to "maildrop"; get reprocessed) requeue Requeue mail with given IDs (move to "maildrop"; get reprocessed) select Add filter for mails that other commands work on show usage: show [-b] [-e] [-h] ID [ID ...] ``` Available filter expressions are show in the help for `select`: ``` # help select >>>> select Add filter for mails that other commands work on Filter syntax: - `from bob@example.com`, `from alice@example.com,bob@example.com,@example.net`, `from "alice@example.com" "bob@example.com"` Multiple address can be given to match any of them; either unquoted and comma separated, or quoted and whitespace separated. In the unquoted case the (comma separated) pattern must not include any whitespace. An address starting with `@` only checks the domain name and ignores the local part. - `from ~regex.*@example.(com|net)`, `from ~"regex.*@example.(com|net)" "other@example.com` To use regular expressions either put `~` in front of an unquoted pattern (need to repeat for each regex in a comma separated pattern list) or before the quotes of a quoted pattern. - `to ...` and `address ...` (matching both to and from) work the same as `from ...` - `queue hold,deferred` Comma separated list of queues a mail must be in. For single queue names `queue` can be omitted, e.g. `hold`. - `$expr1 and $expr2` - `$expr1 or $expr2` (`a and b or c` is parsed as `a and (b or c)`) - `($expr)` - `not $expr` ``` The select expressions use the data provided by `postqueue -j` - which is why it doesn't support filtering by subject/other headers. ## Installation Dependencies: - python3, probably >= 3.10 - [trio](https://trio.readthedocs.io/en/stable/) - [pyparsing](https://github.com/pyparsing/pyparsing/) - [PyYAML](https://github.com/yaml/pyyaml) On Debian: `apt install python3-trio python3-pyparsing python3-yaml` Put the pqm script into your path and make it executable. ## Configuration Configuration is optional; by default it tries to use `~/.config/pqm.yaml` and `/etc/pqm.yaml` (picking the first one that exists). Also see `pqm.example.yaml` in this repository. ### Source (hosts load load mails from) By default it will use the local queue; it probably needs to run as root for that. Use `remote-sources` in the config to use remote hosts instead ("cluster" mode). Each remote source has a unique alias that is used in the `pqm` output (by default the first DNS label in the hostname). `pqm` uses `ssh -oBatchMode=yes root@remotesource ...` to execute postfix tools on the remote nodes; this means you'll probably want a public-key based authentication to the postfix nodes, and the server keys must already be trusted. You'll also want to make sure the ssh login is fast; `UseDNS no` (should be the default) on the server and ed25519 keys should help with that. ## Queue IDs Each mail in the postfix queues has an ID; when using the local source `pqm` will use this ID directly. With remote sources it will prepend `alias/` to the ID.