Python3 SMTP发送邮件
Building MIME messages, STARTTLS vs SSL, and the pitfalls that land mail in spam.
Key PointsUse port 587 with STARTTLS, build messages with EmailMessage, and never put credentials in source code.
Minimal Message
Python
import os
import smtplib
from email.message import EmailMessage
msg = EmailMessage()
msg["Subject"] = "Deploy finished"
msg["From"] = "ops@example.com"
msg["To"] = "dev@example.com"
msg.set_content("Build 20261102 deployed to prod.")
with smtplib.SMTP("smtp.example.com", 587, timeout=10) as s:
s.starttls() # 升级为加密连接
s.login(os.environ["SMTP_USER"], os.environ["SMTP_PASS"])
s.send_message(msg)HTML and Attachments
Python
from email.message import EmailMessage
msg = EmailMessage()
msg["Subject"] = "Weekly report"
msg["From"] = "ops@example.com"
msg["To"] = ["a@example.com", "b@example.com"]
msg.set_content("Plain-text fallback") # 必须保留纯文本版本
msg.add_alternative("<h1>Report</h1><p>All green.</p>", subtype="html")
with open("report.pdf", "rb") as f:
msg.add_attachment(f.read(), maintype="application", subtype="pdf", filename="report.pdf")| Port | Mode | Use when |
|---|---|---|
| 587 | STARTTLS (plain then upgrade) | Default choice, most providers |
| 465 | Implicit SSL | smtplib.SMTP_SSL |
| 25 | Plain, often blocked | Legacy relay only |
Why Mail Lands in Spam
- SPF / DKIM / DMARC records missing on the sending domain.
- From address does not match the authenticated account.
- HTML-only message with no text/plain part.
- Sending through a residential IP or an unverified new domain.
- High volume burst to unknown recipients.
Never log or hard-code SMTP passwords. Use environment variables or a secret manager, and rotate on leak.
最小可用示例
Python
import os
import smtplib
from email.message import EmailMessage
msg = EmailMessage()
msg["Subject"] = "Deploy finished"
msg["From"] = "ops@example.com"
msg["To"] = "dev@example.com"
msg.set_content("Build 20261102 deployed to prod.")
with smtplib.SMTP("smtp.example.com", 587, timeout=10) as s:
s.starttls() # 升级为加密连接
s.login(os.environ["SMTP_USER"], os.environ["SMTP_PASS"])
s.send_message(msg)HTML 与附件
Python
from email.message import EmailMessage
msg = EmailMessage()
msg["Subject"] = "Weekly report"
msg["From"] = "ops@example.com"
msg["To"] = ["a@example.com", "b@example.com"]
msg.set_content("Plain-text fallback") # 必须保留纯文本版本
msg.add_alternative("<h1>Report</h1><p>All green.</p>", subtype="html")
with open("report.pdf", "rb") as f:
msg.add_attachment(f.read(), maintype="application", subtype="pdf", filename="report.pdf")| 端口 | 模式 | 适用 |
|---|---|---|
| 587 | STARTTLS(先明文后升级) | 默认首选,多数服务商 |
| 465 | 隐式 SSL | smtplib.SMTP_SSL |
| 25 | 明文,常被封 | 仅老式中继 |
为什么进垃圾箱
- 发信域名缺 SPF / DKIM / DMARC 记录。
- From 地址与认证账号不一致。
- 只有 HTML 没有纯文本部分。
- 用住宅 IP 或未验证的新域名发信。
- 短时间大量发送给陌生收件人。
不要把 SMTP 密码写进日志或硬编码。用环境变量或密钥管理,泄漏后立即轮换。