What a Postfix mail line looks like
The syslog sample below is fed verbatim into the engine to produce every parser on this page.
Jul 3 14:22:15 mail01 postfix/smtpd[2210]: NOQUEUE: reject: RCPT from unknown[203.0.113.99]: 554 5.7.1 Service unavailable
Jul 3 14:22:41 mail01 postfix/smtpd[2213]: NOQUEUE: reject: RCPT from mail.spam.example[198.51.100.7]: 554 5.7.1 Client host rejected Detected fields
The engine classified this sample as syslog3164 and consolidated 14 fields across 2 lines. Fields marked literal were identical on every sample line, so they are baked into the pattern as anchors rather than captured.
- timestamp : timestamp
- hostname : hostname · literal
- program : literal · literal
- pid : number
- _lit1 : literal · literal
- _lit2 : literal · literal
- _lit3 : literal · literal
- _lit4 : literal · literal
- literal : literal
- number : number · literal
- _lit5 : literal · literal
- literal2 : literal
- literal3 : literal
- literal4 : literal
Regex (named capture groups)
# sample: Jul 3 14:22:15 mail01 postfix/smtpd[2210]: NOQUEUE: reject: RCPT from unknown[203.0.113.99]: 554 5.7.1 Service unavailable
# groups: timestamp=Jul 3 14:22:15, pid=2210, literal=unknown[203.0.113.99], literal2=Service, literal3=unavailable
^(?<timestamp>[A-Za-z]+ \d+ \d+:\d+:\d+) mail01 postfix/smtpd\[(?<pid>-?\d+(?:\.\d+)?)\]: NOQUEUE: reject: RCPT from (?<literal>(?:[A-Za-z]+\.[A-Za-z]+\.[A-Za-z]+\[\d+\.\d+\.\d+\.\d+\]|[A-Za-z]+\[\d+\.\d+\.\d+\.\d+\])): 554 5\.7\.1 (?<literal2>[A-Za-z]+) (?<literal3>[A-Za-z]+)(?: (?<literal4>[A-Za-z]+))?$ Grok pattern (Logstash / Elastic)
%{SYSLOGTIMESTAMP:timestamp} mail01 postfix/smtpd\[%{NUMBER:pid}\]: NOQUEUE: reject: RCPT from %{DATA:literal}: 554 5\.7\.1 %{NOTSPACE:literal2} %{NOTSPACE:literal3}(?: %{GREEDYDATA:literal4})? - note constant field "hostname" embedded as literal anchor "mail01" (varying=false)
- note constant field "number" embedded as literal anchor "554" (varying=false)
- note 1 optional field(s) wrapped in (?:…)? inline regex — grok has no native optional syntax
Wazuh decoder (OS_Regex XML)
<!--
Generated by LogForge - Wazuh decoder (OS_Regex dialect, not PCRE)
sample: Jul 3 14:22:15 mail01 postfix/smtpd[2210]: NOQUEUE: reject: RCPT from unknown[203.0.113.99]: 554 5.7.1 Service unavailable
test with: /var/ossec/bin/wazuh-logtest
-->
<decoder name="postfix-syslog3164">
<program_name>^postfix/smtpd$</program_name>
</decoder>
- note syslog header fields handled by Wazuh pre-decoding (not re-parsed): timestamp, hostname, program, pid
- note parent matches by <program_name> (1 program(s) seen in the samples) — extend the alternation for other programs
- note field "literal" has no safe OS_Regex pattern before the ":" terminator — template truncated; field(s) omitted: literal, number, _lit5, literal2, literal3, literal4
- note no message fields could be captured — parent decoder emitted alone
- note decoder order and prematch specificity may need site-specific tuning (other decoders in your ruleset can shadow these) — validate with /var/ossec/bin/wazuh-logtest
rsyslog template / liblognorm rulebase
# rsyslog template — put in /etc/rsyslog.d/postfix.conf
# Emits the detected RFC 3164 header fields plus the raw
# message body as JSON-ish text using standard rsyslog properties.
# Bind the template to an action, e.g.:
# action(type="omfile" file="/var/log/postfix.log" template="postfix")
# A literal "%" inside a template string must be escaped as "%%".
template(name="postfix" type="string" string="{\"timestamp\":\"%timereported%\",\"host\":\"%hostname%\",\"program\":\"%programname%\",\"procid\":\"%procid%\",\"msg\":\"%msg:::json%\"}\n")
# --- mmnormalize rulebase for the message body ----------------------
# rsyslog properties cover only the syslog header. To extract the fields
# inside the message body, save the lines below (starting at 'version=2',
# which must be the VERY FIRST line of the file) as /etc/rsyslog.d/postfix.rb
# and load them with:
# module(load="mmnormalize")
# action(type="mmnormalize" rulebase="/etc/rsyslog.d/postfix.rb")
version=2
rule=postfix_msg: NOQUEUE: reject: RCPT from %literal:char-to{"extradata":":"}%: 554 5.7.1 %literal2:word% %literal3:word% %literal4:word%
rule=postfix_msg: NOQUEUE: reject: RCPT from %literal:char-to{"extradata":":"}%: 554 5.7.1 %literal2:word% %literal3:word%
- note header property mapping: timestamp -> %timereported%, hostname -> %hostname%, program -> %programname%, pid -> %procid%
- note message-body fields cannot be extracted by rsyslog properties alone — an mmnormalize (liblognorm v2) rulebase for the msg part is appended below the template; save it as its own .rb file
- note dropped header separator "]:" before the first message field (it belongs to the syslog tag, not the msg property)
- note chosen parser types: literal=char-to(:), literal2=word, literal3=word, literal4=word
- note optional columns (literal4): liblognorm has no optional parts within a single rule — emitted a second rule variant with only the always-present columns (max 2 variants; lines with other column combinations will not match and need extra rule= lines)
FAQ
- What is the Postfix queue ID and why does it matter?
- The queue ID (e.g. 4Z8xY1) is the identifier Postfix assigns to a message once it is queued. It appears at the start of every log line for that message across smtpd, cleanup, qmgr, and smtp, so it is the join key for reconstructing a full delivery. NOQUEUE means the message was rejected before ever getting a queue ID.
- How do I correlate Postfix log lines for a single email?
- Group lines by queue ID. One message produces multiple entries from different subprocesses — reception (smtpd), header cleanup (cleanup), queueing (qmgr), and delivery (smtp) — all sharing the same queue ID. Ordering those by timestamp reconstructs the message lifecycle; you cannot get the full picture from any single line.
- What does "NOQUEUE: reject" with unknown[IP] mean?
- NOQUEUE means Postfix rejected the message during the SMTP conversation, before assigning a queue ID. unknown[203.0.113.99] means the connecting client's IP has no valid reverse-DNS (PTR) record — a common spam heuristic. The trailing codes (554 5.7.1) are the SMTP reply and enhanced status, indicating a policy or reputation rejection.
- Which Postfix subprocess logs the reject and delivery events?
- smtpd (the SMTP server) logs inbound connections and rejects; qmgr (queue manager) logs queue events; smtp (the delivery client) logs outbound delivery results (sent/deferred/bounced); cleanup logs header processing. The subprocess name in the program tag — postfix/smtpd[…] — tells you which stage produced the line and how to interpret its message.
Try it on your own Postfix mail lines
Paste a few real lines, review the detected fields, and copy whichever format your stack needs. Free, no account, nothing uploaded.
Open this sample in LogForge →