—— SMTP发送 + IMAP读取
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.utils import formataddr
# 纯文本邮件
def send_text_email(smtp_server, port, username, password,
from_name, to_addr, subject, body):
"""发送纯文本邮件"""
msg = MIMEText(body, "plain", "utf-8")
msg["From"] = formataddr((from_name, username))
msg["To"] = to_addr
msg["Subject"] = subject
with smtplib.SMTP(smtp_server, port) as server:
server.starttls() # 启用 TLS
server.login(username, password)
server.sendmail(username, [to_addr], msg.as_string())
# HTML 邮件
def send_html_email(smtp_server, port, username, password,
to_addr, subject, html_body):
"""发送 HTML 邮件"""
msg = MIMEMultipart("alternative")
msg["From"] = username
msg["To"] = to_addr
msg["Subject"] = subject
# 同时提供纯文本和 HTML 版本
text_part = MIMEText("请使用支持HTML的客户端查看", "plain", "utf-8")
html_part = MIMEText(html_body, "html", "utf-8")
msg.attach(text_part)
msg.attach(html_part)
with smtplib.SMTP(smtp_server, port) as server:
server.starttls()
server.login(username, password)
server.sendmail(username, [to_addr], msg.as_string())
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
from pathlib import Path
def send_email_with_attachments(
smtp_server, port, username, password,
to_addr, subject, body, file_paths
):
"""发送带附件的邮件"""
msg = MIMEMultipart()
msg["From"] = username
msg["To"] = to_addr
msg["Subject"] = subject
# 正文
msg.attach(MIMEText(body, "plain", "utf-8"))
# 附件
for fp in file_paths:
path = Path(fp)
with open(path, "rb") as f:
part = MIMEBase("application", "octet-stream")
part.set_payload(f.read())
encoders.encode_base64(part)
part.add_header(
"Content-Disposition",
f"attachment; filename*=UTF-8''{path.name}",
)
msg.attach(part)
with smtplib.SMTP(smtp_server, port) as server:
server.starttls()
server.login(username, password)
server.sendmail(username, [to_addr], msg.as_string())
import imaplib
import email
from email.header import decode_header
def read_emails(imap_server, username, password,
folder="INBOX", limit=10, search_criteria="ALL"):
"""读取邮件"""
with imaplib.IMAP4_SSL(imap_server) as mail:
mail.login(username, password)
mail.select(folder)
# 搜索邮件
status, messages = mail.search(None, search_criteria)
mail_ids = messages[0].split()
# 取最新的 N 封
mail_ids = mail_ids[-limit:]
results = []
for mid in mail_ids:
status, data = mail.fetch(mid, "(RFC822)")
if status != "OK":
continue
msg = email.message_from_bytes(data[0][1])
subject = decode_subject(msg["Subject"])
from_ = decode_subject(msg["From"])
date = msg["Date"]
body = get_email_body(msg)
results.append({
"id": mid.decode(),
"subject": subject,
"from": from_,
"date": date,
"body": body[:500], # 截取前500字符
})
return results
def decode_subject(header):
"""解码邮件主题"""
if not header:
return "(无主题)"
decoded = decode_header(header)
parts = []
for part, charset in decoded:
if isinstance(part, bytes):
parts.append(part.decode(charset or "utf-8", errors="replace"))
else:
parts.append(part)
return "".join(parts)
def get_email_body(msg):
"""提取邮件正文"""
if msg.is_multipart():
for part in msg.walk():
content_type = part.get_content_type()
if content_type == "text/plain":
charset = part.get_content_charset() or "utf-8"
return part.get_payload(decode=True).decode(charset, errors="replace")
else:
charset = msg.get_content_charset() or "utf-8"
return msg.get_payload(decode=True).decode(charset, errors="replace")
return ""
# 常用搜索条件
# "ALL" 所有邮件
# "UNSEEN" 未读邮件
# 'FROM "boss@co.com"' 来自某人
# 'SUBJECT "报告"' 主题含关键词
# 'SINCE "01-May-2026"' 指定日期后
import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr
import time
def send_bulk_emails(smtp_server, port, username, password,
recipients: list[dict], template: str,
subject: str, delay: float = 2.0):
"""
批量发送个性化邮件
recipients: [{"name": "张三", "email": "a@b.com", ...}, ...]
template: 含 {name} 等占位符的模板
"""
with smtplib.SMTP(smtp_server, port) as server:
server.starttls()
server.login(username, password)
for r in recipients:
body = template.format(**r)
msg = MIMEText(body, "plain", "utf-8")
msg["From"] = formataddr(("系统通知", username))
msg["To"] = r["email"]
msg["Subject"] = subject
try:
server.sendmail(username, [r["email"]], msg.as_string())
print(f"✅ 发送成功: {r['name']} <{r['email']}>")
except Exception as e:
print(f"❌ 发送失败: {r['name']} - {e}")
time.sleep(delay) # 礼貌间隔
#!/usr/bin/env python3
"""第18课 邮件自动化验证"""
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import decode_header
from email import message_from_string
def test_mime_text():
"""纯文本邮件构造测试"""
msg = MIMEText("测试内容", "plain", "utf-8")
msg["From"] = "test@example.com"
msg["To"] = "user@example.com"
msg["Subject"] = "测试邮件"
assert "测试内容" in msg.as_string()
assert "Subject: 测试邮件" in msg.as_string()
print("✅ 纯文本邮件构造测试通过")
def test_mime_multipart():
"""Multipart邮件构造测试"""
msg = MIMEMultipart("alternative")
msg.attach(MIMEText("纯文本版本", "plain", "utf-8"))
msg.attach(MIMEText("<h1>HTML版本</h1>", "html", "utf-8"))
raw = msg.as_string()
assert "纯文本版本" in raw
assert "HTML版本" in raw
print("✅ Multipart邮件构造测试通过")
def test_email_parsing():
"""邮件解析测试"""
raw = """From: test@example.com
To: user@example.com
Subject: =?utf-8?b?5rWL6K+V6YKu5Lu2?=
Hello World"""
msg = message_from_string(raw)
assert msg["From"] == "test@example.com"
# 解码 Subject
decoded = decode_header(msg["Subject"])
assert len(decoded) > 0
print("✅ 邮件解析测试通过")
def test_decode_subject():
"""主题解码测试"""
def decode_sub(header):
if not header:
return "(无主题)"
decoded = decode_header(header)
parts = []
for part, charset in decoded:
if isinstance(part, bytes):
parts.append(part.decode(charset or "utf-8", errors="replace"))
else:
parts.append(part)
return "".join(parts)
# 编码主题
result = decode_sub("=?utf-8?b?5rWL6K+V?=")
assert isinstance(result, str)
assert decode_sub(None) == "(无主题)"
print("✅ 主题解码测试通过")
if __name__ == "__main__":
test_mime_text()
test_mime_multipart()
test_email_parsing()
test_decode_subject()
print("\n🎉 第18课全部验证通过!")