Q&A
How to send emails using Python
Overview
Sending email in Python means talking to an SMTP (Simple Mail Transfer Protocol) server - your own, your provider's (Gmail, Outlook), or a transactional service (SendGrid, SES, Mailgun). The standard library gives you smtplib for the connection and email for composing the message. Always use a secure connection (TLS/SSL) and keep credentials out of code.
graph LR
A[Build message: EmailMessage] --> B[Connect: smtplib.SMTP]
B --> C[Secure: starttls]
C --> D[Authenticate: login]
D --> E[Send: send_message]
E --> F[Recipient inbox]
style A fill:#1e1b4b,color:#fff
Basic Plain-Text Email (modern API)
import smtplib
from email.message import EmailMessage
import os
msg = EmailMessage()
msg["Subject"] = "Hello from Python"
msg["From"] = "you@example.com"
msg["To"] = "friend@example.com"
msg.set_content("This is the plain-text body.")
with smtplib.SMTP("smtp.gmail.com", 587) as server:
server.starttls() # upgrade to encrypted connection
server.login("you@example.com", os.environ["EMAIL_PASSWORD"])
server.send_message(msg)
print("Sent!")
Key steps: build the EmailMessage, open an SMTP connection (port 587 for STARTTLS), call starttls() to encrypt, login(), then send_message().
SSL Instead of STARTTLS
You can connect over SSL from the start (port 465) with SMTP_SSL:
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
server.login("you@example.com", os.environ["EMAIL_PASSWORD"])
server.send_message(msg)
HTML Email (with a plain-text fallback)
msg = EmailMessage()
msg["Subject"] = "HTML Email"
msg["From"] = "you@example.com"
msg["To"] = "friend@example.com"
msg.set_content("Plain-text fallback for non-HTML clients.")
msg.add_alternative("""
<html><body>
<h1 style="color:#6366f1;">Hello!</h1>
<p>This is an <strong>HTML</strong> email.</p>
</body></html>
""", subtype="html")
add_alternative attaches an HTML version; clients show HTML if they can, else the plain text.
Adding an Attachment
with open("report.pdf", "rb") as f:
msg.add_attachment(
f.read(),
maintype="application",
subtype="pdf",
filename="report.pdf",
)
Multiple Recipients
msg["To"] = "a@example.com, b@example.com"
msg["Cc"] = "manager@example.com"
# Bcc: pass recipients explicitly to send and DON'T add a Bcc header
server.send_message(msg, to_addrs=["a@example.com", "b@example.com", "secret@example.com"])
Security Essentials
- Never hard-code credentials. Load from environment variables or a secrets manager.
- Gmail/most providers require app-specific passwords or OAuth2 - your normal account password won't work with 2FA enabled.
- Always use TLS/SSL (
starttls()orSMTP_SSL) so credentials and content aren't sent in clear text.
Higher-Level Options
For production volume and deliverability, prefer a transactional email API over raw SMTP:
- SendGrid, Amazon SES, Mailgun, Postmark - provide Python SDKs, handle deliverability, bounces, and analytics. These avoid SMTP throttling and improve inbox placement. Mention them when "at scale" matters.
Why This Matters in Interviews
This tests practical stdlib usage and security awareness. Know the smtplib + email.message.EmailMessage flow (build → connect → starttls → login → send_message), the difference between STARTTLS (587) and SMTP_SSL (465), and how to add HTML and attachments. The strongest signals: never hard-coding credentials, using TLS, app passwords/OAuth for Gmail, and choosing an API service for high-volume deliverability.
Common Pitfalls
- Hard-coding passwords in source (security risk).
- Using a plain account password with 2FA accounts instead of an app password/OAuth.
- Skipping
starttls()and sending credentials unencrypted. - Adding a
Bccheader (it leaks recipients); pass Bcc addresses to the send call instead. - Hitting provider rate limits when blasting many emails via personal SMTP (use a transactional service).
References
- Python
smtplib: https://docs.python.org/3/library/smtplib.html - Python
emailexamples: https://docs.python.org/3/library/email.examples.html