Python Email Automation: Sending and Parsing Messages with SMTP, POP3, and MIME

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.

Tags: python smtplib poplib email-mime automation

Posted on Thu, 30 Jul 2026 16:13:26 +0000 by nickdd