Sending email from Python isn’t just about smtplib; it’s fundamentally about understanding the SMTP protocol as a state machine that your client (Python script) orchestrates.

Let’s send an email using Gmail as our SMTP server. First, you need an "App password" from your Google Account security settings, not your regular password. Go to your Google Account -> Security -> App passwords. Generate one for "Mail" on "Other (Custom name)" and call it "Python Emailer". This 16-character password is what you’ll use.

import smtplib
from email.mime.text import MIMEText

# --- Configuration ---
smtp_server = "smtp.gmail.com"
smtp_port = 587  # For TLS
sender_email = "your_email@gmail.com"
sender_password = "your_16_character_app_password"  # Use your App Password
receiver_email = "recipient@example.com"
subject = "Test Email from Python"
body = "This is a test email sent using Python's smtplib."

# --- Create the email message ---
message = MIMEText(body)
message['Subject'] = subject
message['From'] = sender_email
message['To'] = receiver_email

# --- Connect to the SMTP server and send ---
try:
    # Establish connection
    server = smtplib.SMTP(smtp_server, smtp_port)
    server.ehlo()  # Extended Hello
    server.starttls()  # Secure the connection
    server.ehlo()  # Re-identify after TLS

    # Login
    server.login(sender_email, sender_password)

    # Send email
    server.sendmail(sender_email, receiver_email, message.as_string())

    print("Email sent successfully!")

except Exception as e:
    print(f"Error sending email: {e}")

finally:
    # Close the connection
    if 'server' in locals() and server:
        server.quit()

When smtplib.SMTP(smtp_server, smtp_port) is called, your Python script initiates a TCP connection to the specified server and port. This is the initial "connection" state. The server responds with a 220 Service ready greeting. Then, server.ehlo() sends the EHLO (Extended HELO) command. This is crucial: it tells the server "I’m a client, what capabilities do you offer?" The server replies with a list of supported commands (like STARTTLS, AUTH, SIZE, etc.).

The server.starttls() command is where the magic of secure transmission happens. It sends the STARTTLS command to the server. If the server supports it (and Gmail does), it initiates a TLS/SSL handshake. After a successful handshake, the entire subsequent communication between your client and the server is encrypted. This is why server.ehlo() is called again after starttls() – the client needs to re-identify itself on the now-secure channel.

Next, server.login(sender_email, sender_password) executes the AUTH LOGIN (or similar, depending on server capabilities) command. Your email address and the App Password are sent over the encrypted channel. If authentication succeeds, the server replies with 235 Authentication successful.

Finally, server.sendmail(sender_email, receiver_email, message.as_string()) sends the actual email. This involves a few SMTP commands: MAIL FROM:<sender_email>, RCPT TO:<receiver_email>, and DATA. The DATA command tells the server "I’m about to send you the email content." The server responds with 354 Start mail input; end with <CR><LF>.<CR><LF>. Your script then sends the message.as_string() content, followed by the termination sequence (\r\n.\r\n). The server acknowledges with 250 Ok: queued as ....

The server.quit() command sends the QUIT command to the server, initiating a graceful shutdown of the connection, typically acknowledged with a 221 Bye.

The most counterintuitive part of smtplib is that it doesn’t handle email composition itself; it’s purely a transport mechanism. The email.mime library is essential for constructing valid email messages with headers and bodies, especially for anything beyond plain text. Without MIMEText (or MIMEMultipart for attachments), your "email" would just be raw text, likely misinterpreted by receiving mail servers.

The next hurdle you’ll encounter is handling more complex email structures, like sending emails with attachments or HTML content, which requires using email.mime.multipart.MIMEMultipart.

Want structured learning?

Take the full Smtp course →