Where smtplib breaks
smtplib is the standard library doing exactly its job: speaking a wire protocol from 1982. The breakage is everything around it. Cloud hosts block port 25 outbound by default. Gmail retired plain passwords, then app passwords became a security review question. TLS negotiation differs between ports 587 and 465, and every failure surfaces as a different cryptic exception.
And when it works, you still know nothing: smtplib reports the handoff, not the delivery. No bounce, no open, no spam-folder signal.
The alternative is not another SMTP library
Swapping smtplib for another SMTP wrapper keeps every problem and adds a dependency. The actual alternative is transport-level: HTTPS instead of SMTP.
import requests requests.post( "https://api.nitrosend.com/v1/my/messages", headers={"Authorization": "Bearer $NITROSEND_API_KEY"}, json={"from": "[email protected]", "to": "[email protected]", "subject": "Welcome", "html": "<p>You're in.</p>"}, )
Port 443 is open everywhere Python runs. Auth is a bearer token, not a password dance. And the response is a message ID you can follow through delivery events.
The gap between the two stacks keeps widening. On our side, we made 47 email designs AI-readable with a design.md file for each, so an agent can build a decent email before your smtplib script has finished negotiating STARTTLS.
One more thing SMTP never gave you: guarded delivery state. On our platform a message that failed can never quietly flip back to sent, while sent can still move to failed when a late bounce lands. Those are the semantics you want from a system of record. A wire-protocol handoff has no opinion on any of it.
Full parameters and responses live in the REST API docs and the API reference.
Go deeper
The full picture, from any language, lives in send email programmatically. If your script sends to more than one person, read multiple recipients first.