Home / Alt manpages / postfix-collate(1)

  • postfix-collate(1)
  • User command
  • linux

Untangle Postfix Logs into Mail Sessions with postfix-collate

Postfix normally writes one record per event, so a single message can be scattered across SMTP reception, cleanup, queue management and delivery lines. postfix-collate reads those records and prints related lines together, separated by blank lines. This gives you one block per session for manual inspection or for a script that reads blank-line-separated records.

This guide uses the postfix-collate shipped by Postfix 3.8.6 on the target machine. It takes no options: give it zero or more log files, or pipe records to standard input. Allow about ten minutes for a first useful grouping, plus time to interpret the mail events in each block.

Before you start

  • Use a copy of the log when experimenting. The command only reads its inputs, but a copied sample makes repeated tests easier.
  • Use an account that can read the selected log. Reading a system log may require elevated privileges, but running the command itself does not.
  • Choose a bounded log file or a carefully filtered stream. Do not casually feed an entire busy mail history into a terminal.

Checkpoint

Confirm the installed command and package version before relying on details in a script.

command -v postfix-collate
dpkg-query -W -f='\${Package} \${Version}\n' postfix

On this machine the command is /usr/sbin/postfix-collate and the package reports postfix 3.8.6-1ubuntu0.1. The manpage itself identifies the upstream script as Postfix 3.8.6. There is no documented version option. In this installation, postfix-collate --version is treated as a filename and fails if that file does not exist.

1. Collate a saved log file

Start with a file rather than a live stream. Replace the placeholder with a log that your account can read. Keep the output in a new file so the original remains unchanged.

postfix-collate /path/to/mail.log > /tmp/postfix-sessions.txt

The output contains the original matching log lines, with an empty line between grouped records. Open /tmp/postfix-sessions.txt in a pager and look for a block containing the queue identifier, recipient, and delivery result.

less /tmp/postfix-sessions.txt

Only lines in the format recognised by the script are useful. It expects the usual syslog prefix, a Postfix instance and a Postfix process name with a process ID. Lines that do not match that shape are skipped rather than copied through. That is a common reason an apparently relevant line is absent.

2. Read a stream from standard input

With no filename, the command reads standard input. This is useful when another command has already selected a time range or a host. Put the filter first, then pass its output through the collator.

journalctl --no-pager -u postfix.service --since '2026-09-26 08:00:00' \
  | postfix-collate > /tmp/postfix-today.txt

The exact journal unit and available timestamps depend on the local service setup. If your logs are plain files, the equivalent is:

sed -n '/Sep 26 08:00/,/Sep 26 09:00/p' /var/log/mail.log \
  | postfix-collate

When a pipeline produces no output, check the input before debugging the grouping. First run the selector without postfix-collate, then check that the lines include the normal Postfix process prefix.

Checkpoint

The collated output should contain blank-line-separated blocks. A blank line is a record separator, not evidence that the message was rejected.

3. Understand what forms a block

The script follows several kinds of Postfix activity. An SMTP server connection starts a log, and a queue ID ties that activity to the transaction. Queue-manager records close a completed transaction when they say the message was removed. Pickup, cleanup, bounce, SMTP, LMTP and delivery-agent records can extend the same transaction when their queue ID matches.

For an SMTP client session, the connection and transaction lines stay together. If one connection handles multiple messages, each queue ID becomes its own transaction. The script also keeps the Postfix instance name with the queue ID, which avoids confusing identical queue IDs from different instances.

Records do not need to arrive in perfect delivery order for the normal cases. The script stores incomplete transactions and prints any that remain at end of input, ordered by when the transaction was first seen. This means an end-of-file block is not necessarily a failed delivery; it may simply be a truncated time range or a message still in the queue.

Some lines are deliberately ignored. The script recognises the Postfix commands and delivery agents it knows about, and it does not act as a general syslog formatter. Do not use missing lines as proof that Postfix never emitted them.

4. Inspect and verify a result

Count the blocks before investigating them. This simple check confirms that the output has the separators the manpage describes.

awk 'BEGIN { RS=""; ORS="\n" } { print "session " NR ": " NF " fields" }' \
  /tmp/postfix-sessions.txt

To locate a queue ID or recipient without losing the surrounding session, use an awk record search rather than a line-only grep:

awk -v needle='QUEUEID' 'BEGIN { RS="" } $0 ~ needle { print $0 "\n" }' \
  /tmp/postfix-sessions.txt

Replace QUEUEID with the identifier from your log. The blank-line record separator is the useful contract here: Perl can use $/="", and awk can use RS="". If your later tool expects one line at a time, split or transform the blocks explicitly rather than assuming the original line order is preserved as independent events.

5. Handle incomplete or sensitive investigations safely

Do not run this command directly against a log that may contain addresses, message IDs or other operational data while sharing a terminal or recording a session. Save output with restrictive permissions when it will be kept.

umask 077
postfix-collate /path/to/mail.log > /tmp/postfix-private.txt
less /tmp/postfix-private.txt

The command does not rewrite the input and does not contact Postfix, so there is no service restart or delivery change to undo. Recovery is simply to rerun it with the correct input or a narrower time range. If the output is no longer needed, remove the temporary file deliberately after checking that no other process needs it.

For a live incident, avoid assuming that the last block is complete. Capture a window that ends after the relevant queue-manager or delivery record, then compare the result with the original log. If the block ends at the end of your sample, extend the time range before drawing a conclusion.

Common traps

  • Using unsupported flags: the manpage documents only postfix-collate file.... Treat filenames beginning with a hyphen carefully and do not assume GNU-style options exist.
  • Expecting all syslog lines: non-matching lines are skipped. Preserve the original log when you need an audit trail.
  • Calling every block a delivery: blocks can represent SMTP reception, pickup, a bounce, an incomplete transaction or a completed delivery.
  • Reading a changing file as a snapshot: the command processes the input it receives. Use a bounded copy or journal time range for repeatable analysis.
  • Confusing queue IDs across instances: the script includes the instance name internally, so inspect the full context when multiple Postfix instances are in use.

Done means

  • You checked the installed Postfix version and command path.
  • You ran postfix-collate on a bounded file or filtered standard input.
  • You found related lines grouped into blank-line-separated sessions.
  • You checked the original log before interpreting skipped or incomplete records.
  • You kept temporary output private and left the Postfix service unchanged.