SMTP is a simple, text-based, line-oriented protocol — which is exactly why it's so easy to test by hand with telnet (a trick used constantly throughout the Postfix notes in this guide). Understanding the raw conversation makes every Postfix log line and every smtpd_*_restrictions setting make sense.
A manual SMTP conversation¶
$ telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
220 mail.example.com ESMTP Postfix
EHLO client.example.com
250-mail.example.com
250-PIPELINING
250-SIZE 10240000
250-STARTTLS
250-AUTH PLAIN LOGIN
250 8BITMIME
MAIL FROM:<[email protected]>
250 2.1.0 Ok
RCPT TO:<[email protected]>
250 2.1.5 Ok
DATA
354 End data with <CR><LF>.<CR><LF>
Subject: Test message
This is the body of the message.
.
250 2.0.0 Ok: queued as A1B2C3D4
QUIT
221 2.0.0 Bye
Breaking that down:
220— the server's banner, sent unprompted as soon as the connection opens.EHLO(or the olderHELO) — the client introduces itself and asks the server what extensions it supports. The server's250-continuation lines advertise capabilities:STARTTLS,AUTH,PIPELINING,SIZE,8BITMIME, etc. This is the "E" in ESMTP (Extended SMTP) — plainHELOgets you no extension list and noSTARTTLS/AUTH.MAIL FROM:— sets the envelope sender. Independent of anything in the message headers.RCPT TO:— sets the envelope recipient. Can be repeated for multiple recipients; each gets its own accept/reject response.DATA— everything from here until a line containing only a single.is the message itself: headers, blank line, body. This is whereheader_checks/body_checks/mime_header_checksoperate.QUIT— closes politely.
Response codes¶
SMTP responses are three-digit codes, and the first digit tells you the category:
| First digit | Meaning |
|---|---|
| 2xx | Success |
| 3xx | Intermediate — more input needed (e.g. 354 after DATA) |
| 4xx | Temporary failure — try again later (the client should re-queue and retry) |
| 5xx | Permanent failure — don't bother retrying, this recipient/command is rejected |
The extended codes you'll see in Postfix logs (like 4.7.1 or 5.1.1) are the more granular Enhanced Status Codes from RFC 3463 — the pattern is class.subject.detail. 4.7.1 is a temporary policy-related rejection (very commonly what greylisting returns); 5.1.1 is a permanent "no such user."
This 4xx/5xx distinction is the whole reason greylisting works (see the hardening doc): a 450 4.7.1 temporary rejection tells a legitimate, RFC-compliant mail server "try again in a few minutes," while most spam-sending software never retries at all.
STARTTLS vs. implicit TLS¶
Two different ways to get an SMTP session encrypted:
- STARTTLS — the connection starts in plaintext on the normal port (25 or 587). The client sees
STARTTLSadvertised in theEHLOresponse, issues aSTARTTLScommand, and the session switches to TLS mid-connection, then theEHLO/MAIL FROM/etc. conversation restarts encrypted. This is whatsmtp_tls_security_level/smtpd_tls_security_level = mayenables opportunistically. - Implicit TLS (SMTPS) — the TLS handshake happens immediately when the connection opens, before any SMTP commands at all. This is what port 465 traditionally means.
If you ever see Must issue a STARTTLS command first, it means smtpd_tls_security_level (or smtp_tls_security_level on the sending side) was set to encrypt, but the peer tried to send commands before negotiating TLS.
Bounces and Delivery Status Notifications (DSN)¶
When a message can't be delivered — after all retries are exhausted, or immediately on a permanent 5xx rejection — the MTA that gave up generates a bounce message back to the envelope sender, formatted as a Delivery Status Notification (RFC 3464). This is why the envelope sender matters so much: it's where failure reports go, which is also why spammers forge it and why mailing list software uses special "bounce" addresses (VERP-style addresses) rather than the list's normal posting address.
Why this matters for reading Postfix logs¶
A single mail transaction produces multiple log lines as it moves through Postfix's internal pipeline (smtpd accepts → cleanup processes headers → qmgr queues → smtp/local/virtual delivers). Each line corresponds to one of the protocol steps above, tagged with a queue ID that ties them together. Once you can mentally replay the raw SMTP conversation behind a log entry, troubleshooting stops being guesswork.