SMTPLIB
smtplib raises this when login() is called but EHLO didn't advertise AUTH. Use the ehlo/starttls/ehlo/login sequence or SMTP_SSL on port 465 to fix it.
Updated Jul 1, 2026
The short answer
This Python smtplib error means your code called login() but the server never advertised the AUTH extension, so smtplib refuses to send credentials. Usually AUTH is offered only after TLS and your cached capabilities do not show it (you called has_extn before upgrading), or you connected to a port or host that does not offer AUTH. Fix it with the ehlo, starttls, ehlo, login sequence (or SMTP_SSL on port 465), and confirm has_extn("auth") is true before login().
This message is raised by Python's smtplib, not by the mail server itself. Inside SMTP.login(), smtplib checks whether the server advertised the AUTH extension in its EHLO response before it will send your username and password:
# CPython Lib/smtplib.py, inside login()self.ehlo_or_helo_if_needed()if not self.has_extn("auth"):raise SMTPNotSupportedError("SMTP AUTH extension not supported by server.")
In older Python versions this was a plain SMTPException with the same text; modern versions (3.5+) raise the more specific SMTPNotSupportedError, which is a subclass of SMTPException (and ultimately of OSError). Either way, the meaning is identical: smtplib looked at the cached EHLO reply, did not find AUTH in the advertised extensions, and refused to attempt authentication. It is a client-side guard, not a 5xx reply from the server.
There is one dominant cause and a few secondary ones.
1. AUTH is only offered after TLS, and your cached capability list does not show it (most common).
Many servers — Gmail, Office 365, Amazon SES, and others — deliberately withhold the AUTH extension on a plaintext connection and only advertise it after the TLS handshake. This is required behavior: per RFC 3207 §4.2, after STARTTLS the server "MUST discard any knowledge obtained from the client" and the client "MUST discard any knowledge obtained from the server, such as the list of SMTP service extensions." So a pre-TLS EHLO reply tells you nothing about post-TLS capabilities. If your code inspects has_extn("auth") (or otherwise acts) using a stale pre-TLS EHLO result, AUTH will appear missing.
Note that smtplib protects you here in the common path: starttls() clears the cached ehlo_resp/esmtp_features, and login() begins by calling ehlo_or_helo_if_needed(), so calling login() directly after starttls() re-issues EHLO automatically. You typically hit this error when you check capabilities yourself, reuse a stale EHLO, or never reach the TLS state at all.
2. You connected to a port/host that genuinely does not offer AUTH on that channel. For example, connecting with plain smtplib.SMTP and calling login() against a server that only advertises AUTH after STARTTLS, without ever upgrading to TLS.
3. You used the wrong class for the port. Port 465 expects implicit TLS from the first byte — you must use smtplib.SMTP_SSL (not SMTP + starttls()). Port 587/25 expect smtplib.SMTP followed by starttls(). Mixing these up means the server never reaches a state where it advertises AUTH.
For a STARTTLS port (587, sometimes 25), use the canonical ehlo → starttls → ehlo → login sequence:
import smtplibserver = smtplib.SMTP("smtp.example.com", 587)server.ehlo() # initial EHLO (no AUTH advertised yet)server.starttls() # upgrade the connection to TLSserver.ehlo() # re-issue EHLO so AUTH is read after TLSserver.login(username, password)
The explicit second ehlo() matches the Python documentation's example and makes the capability refresh obvious in your code. It is good practice but is not strictly mandatory: starttls() resets the cached EHLO state, and login() calls ehlo_or_helo_if_needed() internally, so login() will re-issue EHLO on its own if you skip the manual call. The error most often appears when you check capabilities or branch on a stale pre-TLS EHLO yourself, rather than letting login() re-EHLO.
For an implicit-TLS port (465), use SMTP_SSL; TLS is established on connect, so a single login() is enough:
import smtplibserver = smtplib.SMTP_SSL("smtp.example.com", 465)server.login(username, password)
Both 465 (implicit TLS) and 587 (STARTTLS) are valid, current submission ports per RFC 8314 — 465 is not deprecated.
Confirm what the server actually advertises. Before debugging credentials, prove AUTH is present after your handshake (and only after TLS, if the port uses STARTTLS):
server.ehlo()print(server.has_extn("auth")) # should be True before login()print(server.esmtp_features) # dict of advertised extensions
If has_extn("auth") is False even after a correct starttls() + ehlo(), the server simply does not offer authentication on that endpoint — check the provider's documented submission host and port.
The error means AUTH was never offered; once it is offered, you still need valid credentials. A few gotchas:
smtp.gmail.com, port 587 (STARTTLS) or 465 (SSL).This is distinct from SMTPAuthenticationError (a 535 reply — credentials were sent but rejected). It is also distinct from the case where the server offers AUTH but no mechanism is shared with the client, where login() raises SMTPException("No suitable authentication method found."). This particular "AUTH extension not supported" message fires before any credentials leave your machine.
References
FAQ
Because STARTTLS resets the SMTP session (RFC 3207 §4.2), and many servers only advertise AUTH after TLS. smtplib still holds the pre-TLS EHLO response, which had no AUTH. Call ehlo() again after starttls() so smtplib re-reads the capability list.
One API, every provider
Courier connects to your email, SMS, and push providers, handles retries and failover, and surfaces delivery errors in plain language.
Reply-code definitions per RFC 4954 (SMTP AUTH); RFC 3207 (STARTTLS). Last reviewed Jul 1, 2026. Courier is not affiliated with third-party providers; error behavior may vary by implementation.
© 2026 Courier. All rights reserved.