<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:blog="https://jonesrussell.github.io/blog/ns"><channel><title>Email on Web Developer Blog</title><link>https://jonesrussell.github.io/blog/tags/email/</link><description>Recent content in Email on Web Developer Blog</description><image><title>Web Developer Blog</title><url>https://jonesrussell.github.io/blog/images/og-default.png</url><link>https://jonesrussell.github.io/blog/images/og-default.png</link></image><generator>Hugo -- 0.169.0</generator><language>en-us</language><lastBuildDate>Fri, 30 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://jonesrussell.github.io/blog/tags/email/feed.xml" rel="self" type="application/rss+xml"/><item><title>Prove SMTP authentication on every connection</title><link>https://jonesrussell.github.io/blog/prove-smtp-authentication-on-every-connection/</link><pubDate>Fri, 30 Oct 2026 00:00:00 +0000</pubDate><guid>https://jonesrussell.github.io/blog/prove-smtp-authentication-on-every-connection/</guid><category>php</category><blog:tag>php</blog:tag><blog:tag>email</blog:tag><blog:tag>security</blog:tag><blog:tag>testing</blog:tag><description>Treat SMTP authentication as connection state, require fresh proof after every handshake, and fail closed when a server tries to downgrade the protocol.</description><content:encoded><![CDATA[<p>Ahnii!</p>
<p>An SMTP server advertising authentication is not the same as the client successfully authenticating. The first fact describes a capability. The second is security state that belongs to one connection.</p>
<p>This post follows a versioned Waaseyaa mail transport from its initial authenticated SMTP implementation through review-driven hardening. The useful lesson is broader than one library: require fresh authentication evidence after every handshake, including reconnects.</p>
<h2 id="make-transport-selection-explicit">Make transport selection explicit</h2>
<p>Email configuration should name the transport it intends to use. Do not infer production delivery from a loose collection of environment variables.</p>
<p>The implementation accepted a bounded set of transports, including local capture, an in-memory test transport, a provider transport, and authenticated SMTP. SMTP configuration was validated before a connection could be attempted. It required a valid host, port, username, secret source, and supported authentication method.</p>
<p>Fail early on incomplete or ambiguous configuration. A value that looks unusual, such as the username <code>0</code>, must not disappear because a truthiness check mistakes it for an absent value.</p>
<h2 id="establish-a-verified-encrypted-channel">Establish a verified encrypted channel</h2>
<p>Authentication credentials should only cross a channel whose peer has been verified.</p>
<p>The transport required STARTTLS and configured the socket to:</p>
<ul>
<li>Verify the peer certificate</li>
<li>Verify the peer name</li>
<li>Reject self-signed certificates</li>
<li>Limit TLS negotiation to supported modern protocol versions</li>
</ul>
<p>The socket itself also had to be a valid stream before TLS options were applied. That check turns an unexpected runtime value into a controlled failure instead of letting it reach lower-level networking calls.</p>
<p>Encryption is a prerequisite here, not proof of authentication. Keep those states separate.</p>
<h2 id="track-completed-authentication-not-advertised-capability">Track completed authentication, not advertised capability</h2>
<p>The first implementation checked that the server advertised the requested AUTH mechanism before sending mail. That is a useful capability check, but review exposed a missing boundary: a reconnect creates a new protocol session.</p>
<p>A server can advertise AUTH on one connection, accept authentication, and then behave differently on the next connection. It can omit AUTH or attempt an EHLO-to-HELO downgrade. If the client remembers only what a previous server greeting advertised, it may confuse old connection state with current authorization.</p>
<p>The hardened transport instead records successful authentication only after the server returns the expected success response. It clears that state whenever a fresh greeting begins. Before a message is sent, the current connection must have completed its own authentication exchange.</p>
<p>That produces a simple invariant:</p>
<blockquote>
<p>Every connection that sends a message must independently prove successful authentication.</p>
</blockquote>
<p>If a reconnect cannot satisfy that invariant, delivery stops. The client does not continue through a weaker HELO path.</p>
<h2 id="protect-both-the-envelope-and-the-failure-path">Protect both the envelope and the failure path</h2>
<p>Mail safety extends beyond the connection handshake.</p>
<p>The transport rejected reserved routing headers supplied through message metadata so application input could not quietly replace envelope decisions. It also translated provider and SMTP exceptions into a stable delivery failure without attaching the original exception. That prevents credentials, server transcripts, and provider details from leaking through an exception chain.</p>
<p>Keep detailed diagnostics in an appropriately protected operational channel. Public application errors should describe the failed operation without replaying secrets or protocol conversations.</p>
<h2 id="test-protocol-transitions-not-only-successful-delivery">Test protocol transitions, not only successful delivery</h2>
<p>A happy-path SMTP test proves very little about reconnect behavior. Use a disposable test server that can control each protocol exchange and record what the client sent.</p>
<p>The qualification covered:</p>
<ul>
<li>LOGIN and XOAUTH2 success</li>
<li>Missing or mismatched AUTH capabilities</li>
<li>Invalid and falsey configuration values</li>
<li>TLS verification failures</li>
<li>A second connection that omits AUTH</li>
<li>A second connection that tries to fall back from EHLO to HELO</li>
<li>Redaction of the public failure surface</li>
</ul>
<p>TLS tests ran the mail client in a separate PHP process so certificate authority configuration was present when the runtime initialized. That is a good general rule for tests involving process-start configuration: exercise the real boundary instead of mutating global state after initialization and assuming the result is equivalent.</p>
<p>The important outcome was not merely that authenticated SMTP worked. It was that authentication could not be inherited across connections, inferred from an advertisement, or bypassed through downgrade behavior.</p>
<p>Baamaapii</p>
]]></content:encoded></item></channel></rss>