Anatomy of an SMTP session

A real delivery, captured with swaks against Gmail's MX. Every server response is verbatim; only the client IP is replaced. --quit-after RCPT stops before DATA, so the handshake completes and no message is ever sent, which is how you test a route without touching a recipient.

swaks — gmail-smtp-in.l.google.com:25 click to replay

Reading the prefixes: === is swaks talking to you, -> and <- are sent and received in the clear, and ~> and <~ are the same two inside TLS.

What each step is doing

Every line is a place delivery can fail, and most of the work is knowing which one it failed at. These are the ones worth stopping on.

Connect

=== Trying gmail-smtp-in.l.google.com:25...

swaks resolves the MX and opens a TCP connection on port 25. A refusal or a timeout here is a network problem, not a mail problem, and it is the cheapest failure to misdiagnose.

220 greeting

<- 220 mx.google.com ESMTP 4fb4d7f45d1cf-6aa67d1108csi.33 - gsmtp

The receiver announces itself. Anything other than 220 and you never get to send: some receivers return a 421 here when they are shedding load, and a few return a 554 when the connecting IP is blocked outright.

EHLO

-> EHLO rastu.tech

You announce who you claim to be. Receivers check this name against the reverse DNS of your connecting IP, and a mismatch costs reputation at several providers even though nothing rejects on it directly.

Capabilities

<- 250-mx.google.com at your service, [198.51.100.24]

Everything after 250- is something the receiver supports. The last line uses 250 with a space rather than a hyphen, which is how you know the list ended. If STARTTLS is absent here, the session cannot be encrypted at all.

STARTTLS

-> STARTTLS

Upgrades the connection already open rather than dialling a new one. This is the step MTA-STS and DANE exist to protect, because an attacker who can strip this line downgrades the whole session to cleartext.

The second EHLO

~> EHLO rastu.tech

Not a mistake. STARTTLS resets the session state, so the capability list has to be asked for again inside the encrypted channel, and it can legitimately differ from the first one.

MAIL FROM

~> MAIL FROM:<rastu@rastu.tech>

The envelope sender, and the domain SPF is actually checked against. It is not the From: header the recipient sees, and the gap between those two is what DMARC alignment is about.

RCPT TO

~> RCPT TO:<postmaster@gmail.com>

Where most rejections land: unknown user, mailbox full, recipient policy. A 550 here is about one address. A 550 at MAIL FROM or at connect is about you.

QUIT

<~ 221 2.0.0 closing connection 4fb4d7f45d1cf-6aa67d1108csi.33 - gsmtp

Because --quit-after RCPT stopped before DATA. The route is proven end to end and no message was ever transmitted.

When a line comes back wrong

A connection refused at the 220 is a different problem from a 550 at RCPT TO, which is a different problem again from a 250 at DATA followed by silence. That is the reason to read a clean session before reading a broken one: it tells you which question to ask.

The response reference covers what each response means, and the bounce classifier will tell you which action it needs.

Reproducing this

swaks is the tool worth having installed, and --quit-after RCPT is the flag worth remembering: it proves a full route, including TLS and recipient acceptance, without delivering anything to a real person.

swaks --to postmaster@gmail.com --from you@example.com \
      --server gmail-smtp-in.l.google.com --ehlo example.com \
      --tls --quit-after RCPT

Drop --quit-after RCPT and it delivers. Add --tlso instead of --tls for implicit TLS on port 465, and -au / -ap when you are testing a submission endpoint that requires authentication.