Python3 SMTP发送邮件

Building MIME messages, STARTTLS vs SSL, and the pitfalls that land mail in spam.

Intermediate
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")
PortModeUse when
587STARTTLS (plain then upgrade)Default choice, most providers
465Implicit SSLsmtplib.SMTP_SSL
25Plain, often blockedLegacy 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.

Friends & Partners