直接用smtplib.smtp发html邮件会乱码或显示源码,因未构造mime结构及声明content-type:text/html;charset=utf-8,须用mimemultipart("alternative")包裹html与纯文本备选,并安全转义用户输入、规范附件编码与spf/dkim配置。

为什么直接用 smtplib.SMTP 发 HTML 邮件会乱码或只显示源码?
因为 smtplib 本身不处理邮件内容编码和 MIME 结构,纯文本发送 <h1>Hello</h1> 就真当字符串发过去,收件方客户端无法识别这是 HTML。必须手动构造 MIMEMultipart 和 MIMEText,并显式声明 subtype="html"。
- 漏设
Content-Type: text/html; charset=utf-8→ 中文变 或被当纯文本 - 没用
MIMEMultipart("alternative")包裹 HTML + 纯文本备选 → 部分邮箱客户端(如 Outlook 某些版本)拒绝渲染 HTML - HTML 模板里含变量但没做
str.format()或Template.safe_substitute()→ 模板语法残留或 XSS 风险
如何安全填充 HTML 模板并避免 XSS?
别用 str.replace() 或裸 format() 直接插用户输入。Python 内置 string.Template 更可控,配合 html.escape() 做最小化转义。
from string import Template
import html
template = Template("""
<h2>订单已确认:$order_id</h2>
<p>客户:$name</p>
<p>金额:¥$amount</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill7657" title="python 查询技能"><img
src="https://img.php.cn/upload/skill/000/000/081/179161384046229.jpg" alt="python 查询技能" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill7657" title="python 查询技能" class="overflowclass">python 查询技能</a>
<p class="overflowclass">查询客流数据,输出JSON格式,可直接导入Bitable等可视化工具</p>
</div>
<a rel="nofollow" href="/xiazai/skill7657" title="python 查询技能" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
""")
safe_content = template.safe_substitute(
order_id="ORD-7890",
name=html.escape("张三 <script>alert(1)</script>"),
amount="299.00"
)
-
Template.safe_substitute()对缺失键静默忽略,不会抛KeyError -
html.escape()只转义、<code>>、&、"、',不影响中文和常见符号 - 绝对不要用
eval()、exec()或第三方模板引擎(如 Jinja2)在自动化脚本中动态执行任意代码
怎么让附件和 HTML 同时生效且不被 Gmail 当垃圾邮件?
Gmail 对带附件的 HTML 邮件更敏感,关键在 MIME 层级和头部字段。必须用 MIMEMultipart("mixed") 作根容器,HTML 内容嵌套在 "alternative" 子部件里,附件单独追加。
- 设置
msg["X-Priority"] = "normal"(别设 high/urgent,触发反垃圾规则) - 附件名用
Header编码:header = Header(filename, "utf-8").encode(),否则中文附件名乱码 - HTML 部分必须包含真实可渲染内容(至少一个
<p></p>或<div>),空模板或纯 CSS 会被判为低质量邮件 <li>测试时先发给自己,检查原始邮件头:<code>Received-SPF、DKIM-Signature是否存在 —— 缺一不可 - 优先尝试
SMTP_SSL(端口 465),失败再降级到SMTP + starttls() - 捕获具体异常:
smtplib.SMTPAuthenticationError表示密码/APP 密码错;smtplib.SMTPRecipientsRefused是收件人格式非法;socket.timeout需重试而非退出 - 登录后立即调用
server.noop()验证连接有效性,避免发信中途断连 - 企业微信/钉钉等替代通道比 SMTP 更稳定,但需提前申请 API 权限 —— 别等到线上告警才去配
SMTP 连接超时或认证失败的实际应对策略
不是所有企业邮箱都支持 STARTTLS,也不是所有网络都放行 587 端口。硬编码 smtplib.SMTP("smtp.gmail.com", 587) 必然翻车。
HTML 邮件的“高级”不在功能堆砌,而在 MIME 结构的严谨性、内容转义的克制性、以及对各家邮箱反垃圾策略的妥协意识。少一个 charset=utf-8,就可能让整封邮件变成乱码;多一行无意义的 inline CSS,Gmail 就可能把它塞进推广目录。










