Email Protocols Demystified
Modern email communication relies on standardized protocols to ensure interoperability across clients and servers. Two foundational protocols are SMTP for sending and POP3 for retrieving messages—each serving a distinct role in the email lifecycle.
SMTP: The Outbound Courier
Simple Mail Transfer Protocol (SMTP) operates at the application layer and is responsible for routing outgoing messages between mail user agents (MUAs) and mail transfer agents (MTAs). It typical uses port 25 (unencrypted), 465 (legacy SSL), or 587 (STARTTLS). SMTP is strictly a push mechanism—it transmits messages but does not store or retrieve them. Clients must rely on complementary protocols like IMAP or POP3 for inbox management.
POP3: The Local Inbox Fetcher
Post Office Protocol version 3 (POP3) enables clients to download messages from a remote mailbox to a local device. It commonly runs on port 110 (plaintext) or 995 (SSL/TLS). By default, POP3 removes messages from the server after retrieval—though many implementations support optional server-side retention. This behavior makes POP3 less suitable for multi-device synchronization compared to IMAP.
Sending Emails Programmatically
Python’s smtplib module provides a clean interface for SMTP interactions. To send messages reliably, you’ll also need the email package to construct properly formatted MIME-compliant payloads.
Basic Text Email
import smtplib
from email.mime.text import MIMEText
from email.header import Header
sender = "sender@domain.com"
recipient = "receiver@domain.com"
subject = "Automated Notification"
body = "This message was generated and dispatched programmatically."
msg = MIMEText(body, "plain", "utf-8")
msg["From"] = Header("System Bot", "utf-8")
msg["To"] = Header("User", "utf-8")
msg["Subject"] = Header(subject, "utf-8")
try:
with smtplib.SMTP("smtp.domain.com", 587) as server:
server.starttls()
server.login(sender, "app_password_or_token")
server.sendmail(sender, [recipient], msg.as_string())
print("✅ Email delivered successfully.")
except Exception as e:
print(f"❌ Delivery failed: {e}")
QQ Mail Configuration Example
For QQ Mail, use smtp.qq.com on port 587 and authenticate with an app-specific authorization code—not your account password.
Retrieving and Parsing Emails
The poplib module supports POP3-based inbox access. Once connected, you can list, fetch, and parse messages using Python’s email parser.
Fetching the Latest Message
import poplib
from email.parser import Parser
from email.header import decode_header
def decode_mime_words(s):
decoded_fragments = decode_header(s)
return "".join(
fragment.decode(encoding or "utf-8") if isinstance(fragment, bytes) else fragment
for fragment, encoding in decoded_fragments
)
host = "pop.qq.com"
email_addr = "user@qq.com"
auth_token = "your_app_password"
with poplib.POP3_SSL(host) as server:
server.user(email_addr)
server.pass_(auth_token)
# Retrieve count and latest message
count, _ = server.stat()
if count > 0:
_, lines, _ = server.retr(count)
raw_email = b"\r\n".join(lines).decode("utf-8")
parsed = Parser().parsestr(raw_email)
# Extract headers
print("From:", decode_mime_words(parsed.get("From", "")))
print("To:", decode_mime_words(parsed.get("To", "")))
print("Subject:", decode_mime_words(parsed.get("Subject", "")))
# Get plain-text body
if parsed.is_multipart():
for part in parsed.walk():
if part.get_content_type() == "text/plain":
print("Body:", part.get_payload(decode=True).decode("utf-8"))
break
Handling Attachments
MIME multipart messages allow embedding files alongside textual content. Both sending and receiving attachments require proper encoding and header configuration.
Sending with Attachment
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
msg = MIMEMultipart()
msg["From"] = "sender@domain.com"
msg["To"] = "receiver@domain.com"
msg["Subject"] = "Report with Attachment"
# Plain text body
msg.attach(MIMEText("Please find the attached report.", "plain", "utf-8"))
# Attach file
file_path = "/path/to/report.pdf"
with open(file_path, "rb") as f:
part = MIMEBase("application", "pdf")
part.set_payload(f.read())
encoders.encode_base64(part)
part.add_header(
"Content-Disposition",
f"attachment; filename={file_path.split('/')[-1]}",
filename=file_path.split('/')[-1]
)
msg.attach(part)
# Send
with smtplib.SMTP("smtp.domain.com", 587) as server:
server.starttls()
server.login("sender@domain.com", "token")
server.send_message(msg)
Extracting Attachments from Received Mail
def extract_attachments(email_obj, output_dir="./attachments"):
import os
os.makedirs(output_dir, exist_ok=True)
for part in email_obj.walk():
if part.get_content_maintype() == "multipart":
continue
if part.get("Content-Disposition") and "attachment" in part.get("Content-Disposition"):
filename = part.get_filename()
if filename:
filepath = os.path.join(output_dir, filename)
with open(filepath, "wb") as f:
f.write(part.get_payload(decode=True))
print(f"Saved attachment: {filepath}")
Advanced Parsing of Multi-Part Messages
Real-world emails often contain mixed contant types (e.g., HTML + plain text + images). Use recursive traversal to inspect all parts:
from email.parser import Parser
def analyze_message_structure(msg):
if msg.is_multipart():
print(f"→ Multipart ({msg.get_content_subtype()})")
for idx, part in enumerate(msg.get_payload()):
print(f" Part {idx + 1}: {part.get_content_type()}")
analyze_message_structure(part)
else:
content_type = msg.get_content_type()
print(f"→ {content_type} | Size: {len(msg.get_payload(decode=True) or '')} bytes")
# Example usage with parsed message object
# analyze_message_structure(parsed)
Security and Best Practices
- Always use TLS/SSL when connecting to SMTP or POP3 servers.
- Store credentials securely—prefer environment variables or dedicated secret managers over hardcoded strings.
- Use application-specific passwords instead of primary account passwords where supported (e.g., Gmail App Passwords, QQ Mail Authorization Codes).
- Validate recipient addresses and sanitize user-supplied content before constructing messages.
- Implement timeouts and retry logic for production-grade reliability.