ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python邮件自动化全攻略:从smtplib到HTTP接口与定时发送

Python邮件自动化全攻略:从smtplib到HTTP接口与定时发送 办公自动化里的“邮件发送服务”说实话没有想象中那么玄。核心就三件事把要“发出去的内容”拼好交给 SMTP 服务器再由 SMTP 服务器投递到收件人的邮箱。对 Python 开发者来说标准库里的smtplib和email就已经覆盖了大部分办公场景自动发日报、系统告警、批量通知、报表分发都可以不用花一分钱搞定。如果你正在折腾 Python 办公自动化或者想把“发邮件”这个动作从手动操作里去掉这篇内容很适合收藏。我会从最简的纯文本邮件开始一路写到 HTML 邮件、附件发送、代码封装、HTTP 接口、批量任务、异常重试再给一张常见问题排查表。过程中使用的都是标准库和少量第三方库不需要自己维护复杂的邮件服务器。这次没有用很重的一键包而是把一个容易被忽略的问题摆在前面邮箱服务商提供的不是“登录密码”而是 SMTP 授权码。授权码怎么拿、代码里怎么配、哪些错误源于配置不对都会在下面讲清楚。1. 邮件发送服务核心能力速览能力项说明项目用途用 Python 编写办公自动化邮件发送服务覆盖文本邮件、HTML 邮件、附件和批量通知核心协议SMTP / SMTP_SSL / STARTTLS开发语言Python 3.8主要依赖标准库smtplib、email可选Flask、openpyxl、APScheduler配置方式环境变量或独立配置文件服务形态可函数调用、可命令行执行、可封装 HTTP API批量任务支持读取 Excel / CSV 收件人列表并循环发送定时运行可用系统计划任务或 APScheduler 触发显存 / GPU 占用不使用 GPU运行成本可部署在任何能运行 Python 的服务器上表格里没有写具体邮箱服务商的端口是因为不同服务商的加密方式不同。最常用的是465 端口 SSL和587 端口 STARTTLS后面的环境准备章节会展开区分。2. 适用场景与使用边界邮件发送服务适合的办公场景非常明确。比较典型的一类是“周期性任务”每天把数据库查询结果、运营报表或自动化测试报告发送给固定人群另一类是“事件触发任务”程序出现异常时给运维发告警邮件或者用户提交工单后自动发送受理回执。把邮件发送功能封装好之后任何 Python 程序都可以用三五行代码接入这是它在自动化体系里最实用的地方。不适合的场景也很清楚如果需要发送营销类邮件、大附件批量群发或高频率通知免费邮箱服务商通常会有严格的频率限制发多了会被拦截。这时候更适合接入专门的企业邮件服务或第三方邮件 API。另外“回复复杂邮件、管理收件箱、解析对方回复内容”这类交互需求也不是smtplib的强项应该交给更大的平台或者使用 IMAP 协议做进一步处理。使用边界方面要特别注意合规。用 Python 发邮件给别人之前必须确认对方知情并同意接收邮件底部最好加上“如需退订请联系管理员”之类的说明。涉及内部报表时要注意收件人名单是否属于敏感信息不要把大量收件人的真实邮箱地址放在“收件人”一栏导致互见内部通知尽量使用 BCC 密送方式。3. 环境准备与配置3.1 基础环境开发机上需要有 Python 3.8 或更高版本。Windows 用户建议直接去 python.org 下载安装包安装时勾选“Add Python to PATH”Linux 用户则先确认系统自带版本如果偏低可以用apt install python3或者源码编译。不建议直接在系统 Python 里安装第三方依赖。先建一个独立虚拟环境可以避免后续多个项目互相干扰。如果你之前没有用过虚拟环境下面的命令可以当作模板# Windows python -m venv venv venv\Scripts\activate # macOS / Linux python3 -m venv venv source venv/bin/activate pip install --upgrade pip3.2 需要的依赖包标准库部分不需要额外安装它们是 Python 安装包里自带的smtplib负责连接 SMTP 服务器并执行发送。email.mime负责构造符合 MIME 标准的邮件主体。email.utils负责生成合法的邮件头和格式化地址。后面的章节如果要把邮件发送能力暴露成 HTTP 接口还需要安装一个轻量 Web 框架例如 Flaskpip install flask openpyxlopenpyxl用于读取 Excel 收件人列表。收件人数据如果保存在 CSV 里甚至不需要这个库直接用 Python 内置的csv模块就够了。3.3 邮箱服务商 SMTP 参数主流邮箱服务商普遍支持 SMTP 发信但一般不允许直接用“账号密码”登录。你需要在邮箱设置里开启 SMTP 服务并生成一个专门的“授权码”代码里用这个授权码替代登录密码。配置前要准备五类信息参数含义示例SMTP_HOST邮件服务商提供的服务器地址smtp.qq.com、smtp.163.comSMTP_PORT发送端口465 或 587SMTP_USER邮箱账号yournameexample.comSMTP_PASS授权码不是邮箱登录密码服务商设置页生成USE_SSL是否使用 SMTP_SSL以服务商文档为准常见的端口规则是465端口通常要求全程 SSL587端口通常先普通连接再用starttls()升级为加密连接。有的服务商两种方式都支持有的只开放其中一个。写代码时最好把参数抽成配置不要硬编码在发信函数里。3.4 授权码获取流程以个人邮箱为例绝大多数服务商的做法是登录网页端邮箱 → 设置 → 账户 → 开启 SMTP 服务 → 验证手机号 → 生成授权码。授权码通常是一段 16 位左右的字符串生成后立刻保存因为部分平台只显示一次。需要注意的是授权码已经等同于邮箱的“发信权限”不适合写在公开代码仓库里。如果你们公司有自己的企业邮局那么 SMTP_HOST 通常是内网地址或企业邮箱域名具体参数需要问邮件系统管理员。这里强调一点优先让管理员提供“专门用来发系统通知的邮箱账号”而不是拿某个员工的个人邮箱去跑自动任务否则离职或改密码会导致所有自动化通知中断。4. 最小可运行的纯文本邮件先实现一个能跑通的最小示例。代码不依赖于任何第三方库只负责把一封纯文本邮件通过SMTP_SSL发送出去。项目里新建文件send_text_mail.pyimport os import smtplib from email.mime.text import MIMEText from email.utils import formataddr SMTP_HOST os.getenv(SMTP_HOST, smtp.example.com) SMTP_PORT int(os.getenv(SMTP_PORT, 465)) SMTP_USER os.getenv(SMTP_USER, userexample.com) SMTP_PASS os.getenv(SMTP_PASS, your-auth-code) def send_text_mail(to_email: str, subject: str, content: str) - None: msg MIMEText(content, plain, utf-8) msg[From] formataddr((通知机器人, SMTP_USER)) msg[To] formataddr((收件人, to_email)) msg[Subject] subject with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT, timeout10) as server: server.login(SMTP_USER, SMTP_PASS) server.sendmail(SMTP_USER, [to_email], msg.as_string()) if __name__ __main__: send_text_mail( to_emailreceiverexample.com, subjectPython 邮件发送测试, content这是一封来自 Python 办公自动化脚本的测试邮件。 ) print(邮件发送完成)这段代码有几个地方需要你按实际环境替换环境变量里的SMTP_HOST、SMTP_PORT、SMTP_USER、SMTP_PASS。receiverexample.com换成自己的测试收件箱。建议第一次测试不要直接用真实客户邮箱而是发给自己另一个邮箱或者在同一邮箱的收件箱中确认能正常收到。发送成功后终端会打印“邮件发送完成”收件箱里应该能收到标题为“Python 邮件发送测试”的邮件。如果你使用的服务商只开放587端口那么发送部分要改成这样with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout10) as server: server.starttls() server.login(SMTP_USER, SMTP_PASS) server.sendmail(SMTP_USER, [to_email], msg.as_string())这两种写法的差别只在于是否在建立连接后执行starttls()。其他逻辑不变。如果你不确定服务商支持哪种方式先查官方文档再用最小脚本分别测试。4.1 如何验证发送成功运行脚本之后不要只看终端输出。最好的验证方式是在收件箱找到该邮件。检查是否进入了垃圾箱如果进入了垃圾箱说明正文或发件域名可能触发了垃圾邮件规则。查看完整邮件头信息确认发送 IP、发送时间与 SMTP 服务器记录一致。如果终端报错常见的有三种连接超时、登录失败、被拒收。出现“SMTPAuthenticationError”基本可以判断是授权码或服务器地址不正确出现“Connection refused”则说明端口错误或者服务器不允许当前网络访问。4.2 运行参数抽取为配置项办公脚本长期使用后会发现一个问题邮箱账号可能变、收件人列表可能变、邮件正文模板可能变。如果全部写在代码里每次改动都要找人改代码非常不方便。所以建议把配置抽到环境变量或者独立文件里。在项目根目录放一个.env不一定是标准做法但确实比较直观。我们可以用更朴素的config.pySMTP_CONFIG { host: os.getenv(SMTP_HOST, smtp.example.com), port: int(os.getenv(SMTP_PORT, 465)), user: os.getenv(SMTP_USER, userexample.com), password: os.getenv(SMTP_PASS, your-auth-code), } DEFAULT_FROM_NAME 办公自动化通知 DEFAULT_SENDER SMTP_CONFIG[user]这样主业务代码调用时只获取配置对象不会把敏感变量散落到处都是。5. 升级HTML 邮件与附件纯文本邮件能满足最基本的通知需求但在办公场景里日报、图表、Excel 附件往往是刚需。Python 的email.mime模块可以轻松构造一封同时包含 HTML 正文和多个文件的邮件。5.1 发送 HTML 邮件HTML 邮件的正文可以包含表格、颜色、图片链接适合用来展示运营数据摘要。将上面示例里创建MIMEText的plain改成html即可把内容按 HTML 渲染import smtplib from email.mime.text import MIMEText from email.utils import formataddr html_content html body h3业务日报/h3 table border1 cellpadding6 styleborder-collapse:collapse; trth指标/thth今日值/th/tr trtd订单数/tdtd1024/td/tr trtd新增用户/tdtd256/td/tr /table p详情请查看附件中的 Excel 报表。/p /body /html msg MIMEText(html_content, html, utf-8) msg[From] formataddr((日报机器人, senderexample.com)) msg[To] formataddr((接收人, receiverexample.com)) msg[Subject] 07 月 25 日业务日报需要同时支持纯文本和 HTML 时通常的做法是构造MIMEMultipart(alternative)先添加纯文本版本再添加 HTML 版本。这样老旧邮件客户端可以显示纯文本新客户端可以显示 HTML。不过办公内网场景一般不会太在意这一点多数时候直接发 HTML 就够了。5.2 添加附件发送带附件的邮件时邮件对象要使用MIMEMultipart而不是直接创建MIMEText。下面演示发送一封包含 Excel 报表附件的邮件import os import smtplib from email import encoders from email.mime.base import MIMEBase from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.utils import formataddr SMTP_HOST smtp.example.com SMTP_PORT 465 SMTP_USER senderexample.com SMTP_PASS your-auth-code def send_mail_with_attachment(to_email: str, subject: str, html: str, attachment_path: str): msg MIMEMultipart() msg[From] formataddr((报表机器人, SMTP_USER)) msg[To] formataddr((收件人, to_email)) msg[Subject] subject msg.attach(MIMEText(html, html, utf-8)) if attachment_path and os.path.isfile(attachment_path): with open(attachment_path, rb) as fp: part MIMEBase(application, octet-stream) part.set_payload(fp.read()) encoders.encode_base64(part) filename os.path.basename(attachment_path) part.add_header( Content-Disposition, attachment, filename(utf-8, , filename), ) msg.attach(part) with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT, timeout15) as server: server.login(SMTP_USER, SMTP_PASS) server.sendmail(SMTP_USER, [to_email], msg.as_string()) if __name__ __main__: send_mail_with_attachment( to_emailreceiverexample.com, subject今日业务日报, htmlp今日日报详见附件请查收。/p, attachment_path./report_20250725.xlsx, )附件部分的核心是MIMEBase。先读取二进制文件再做 Base64 编码这样邮件数据才能安全传输。中文文件名需要通过filename(utf-8, , filename)方式处理否则部分客户端打开附件时会出现乱码。如果你的收件人一次需要多个附件可以在一个循环里读取多个文件并多次msg.attach(part)。要注意一点附件总大小过大会导致发送变慢也容易被邮箱服务商拦截。办公场景通常建议附件不超过 10MB超大文件应该走企业网盘并发送分享链接。5.3 在邮件正文里嵌入图片HTML 邮件如果需要嵌入图片而不是引用外链图片可以使用MIMEMultipart(related)容器再把图片作为独立MIMEImage对象添加并通过Content-ID引用from email.mime.image import MIMEImage msg MIMEMultipart(related) msg.attach(MIMEText(img srccid:chart001, html, utf-8)) with open(chart.png, rb) as fp: image MIMEImage(fp.read()) image.add_header(Content-ID, chart001) msg.attach(image)使用cid:方式嵌入的图片不会依赖外部网络地址适合邮件接收者对隐私比较敏感的办公场景。不过这种邮件整体体积也会变大不建议嵌太多大图。6. 把发送代码封装成可复用服务上面几节代码已经能发纯文本、HTML 和附件邮件但直接调用还是不够工程化。一个办公自动化系统里发邮件动作可能在很多地方被触发比如定时任务跑完要发报表、程序 catch 到异常要发告警、用户点击按钮后要发确认邮件。如果每个模块各自构造smtplib连接代码会非常冗余而且在连接管理上容易出问题。建议写一个独立模块mail_service.py把公共逻辑收拢成类import os import smtplib import time from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.utils import formataddr from typing import List, Optional class MailService: def __init__(self, host: str, port: int, user: str, password: str, use_ssl: bool True): self.host host self.port port self.user user self.password password self.use_ssl use_ssl def _build_message(self, to_list: List[str], subject: str, html: str , text: str ) - str: msg MIMEMultipart(alternative) msg[From] formataddr((系统通知, self.user)) msg[To] , .join(to_list) msg[Subject] subject if text: msg.attach(MIMEText(text, plain, utf-8)) if html: msg.attach(MIMEText(html, html, utf-8)) return msg.as_string() def _get_server(self): if self.use_ssl: return smtplib.SMTP_SSL(self.host, self.port, timeout15) server smtplib.SMTP(self.host, self.port, timeout15) server.starttls() return server def send(self, to_list: List[str], subject: str, html: str , text: str ) - None: message self._build_message(to_list, subject, html, text) server self._get_server() try: server.login(self.user, self.password) server.sendmail(self.user, to_list, message) finally: server.quit()这里为什么要用属性把 SMTP 连接参数放到实例里因为一个系统里可能有多套邮件账号业务通知用一个账号、运维告警用另一个账号。通过MailService(smtp1.example.com, ...)和MailService(smtp2.example.com, ...)可以轻松创建多个发送实例不会互相干扰。调用的时候也很简单service MailService( hostsmtp.example.com, port465, usersenderexample.com, passwordauth-code, ) service.send( to_list[managerexample.com], subject系统部署完成, htmlp新版本已于 b10:30/b 部署完成。/p, )这时再配合上一节提到的附件逻辑就可以把附件参数、图片参数逐步扩展到build_message里。先保住“能发出来”再做复杂度扩张这是封装邮件服务时比较稳妥的演进方式。7. REST API 与批量任务7.1 为什么需要暴露为 HTTP 接口很多办公系统不是 Python 单体程序。比如你有一个用 Java 写的工单系统业务人员点击按钮后希望自动给客户发邮件但团队不想在 Java 项目里再引入一套邮件依赖。这时候如果单独维护一个“邮件发送服务”提供 HTTP 接口其他语言都可以通过简单 POST 请求调用。这里用 Flask 写一个最小可用的 REST 接口。安装依赖pip install flask然后创建mail_api.pyfrom flask import Flask, request, jsonify from mail_service import MailService app Flask(__name__) service MailService( hostsmtp.example.com, port465, usersenderexample.com, passwordyour-auth-code, ) app.post(/api/mail/send) def send_mail(): payload request.get_json(forceTrue) to_list payload.get(to_list) subject payload.get(subject) html payload.get(html, ) text payload.get(text, ) if not to_list or not subject: return jsonify({code: 400, message: to_list 和 subject 不能为空}), 400 try: service.send(to_listto_list, subjectsubject, htmlhtml, texttext) return jsonify({code: 0, message: 发送成功}) except Exception as exc: return jsonify({code: 500, message: str(exc)}), 500 if __name__ __main__: app.run(host127.0.0.1, port8000)启动服务python mail_api.py用 curl 测试curl -X POST http://127.0.0.1:8000/api/mail/send \ -H Content-Type: application/json \ -d { to_list: [receiverexample.com], subject: 接口测试邮件, text: 如果收到这封邮件说明邮件发送服务接口已跑通。 }返回 JSON 里code为 0 时说明发送成功。另一个语言调用时只需要构造等价的 HTTP POST 请求即可。这个接口还有一个很重要的现实问题没有鉴权。如果服务监听在公网地址上任何人都可以请求你的服务器发垃圾邮件这会让你的 IP 被邮箱服务商加入黑名单。生产环境至少要加一个简单的 token 校验import os API_TOKEN os.getenv(API_TOKEN, change-me) app.before_request def check_token(): if request.headers.get(X-Token) ! API_TOKEN: return jsonify({code: 401, message: unauthorized}), 401启动时设置环境变量API_TOKEN调用方在请求头里携带X-Token。如果只是内网服务也建议限制监听地址为127.0.0.1或内网 IP并用防火墙控制访问来源。7.2 批量任务从 Excel 读取收件人并发送批量发送时最忌讳的是把收件人放到 Excel 里又用人工复制粘贴方式去发信。自动化批量发送应该由脚本统一读取名单、循环调用发送服务。准备一份recipients.xlsx列结构可以类似这样姓名邮箱部门张三zhangsanexample.com运营部李四lisiexample.com技术部批量发送脚本示例import openpyxl import time from mail_service import MailService service MailService( hostsmtp.example.com, port465, usersenderexample.com, passwordyour-auth-code, ) workbook openpyxl.load_workbook(recipients.xlsx) sheet workbook.active for index, row in enumerate(sheet.iter_rows(min_row2, values_onlyTrue), start2): name, email, department row[0], row[1], row[2] if not email: continue content f{name}你好{department}第三季度考核结果已发布请登录内部系统查看。 try: service.send( to_list[email], subject季度考核结果通知, textcontent, ) print(f[{index}] 发送成功: {email}) except Exception as exc: print(f[{index}] 发送失败: {email}, error: {exc}) time.sleep(2)这段代码每发完一封就休息 2 秒是为了避免触发邮箱服务商的频率限制。有些开发者为了追求速度会去掉sleep结果发到第几十封时被服务商临时限制导致后面全部失败。所以批量场景里频率控制比发送速度重要得多。不过需要注意真实办公项目中一次给几百人发个性化内容更适合先走邮件服务商提供的“批量模板”能力而不是用个人 SMTP 逐封发送。个人邮箱 SMTP 通常不适合大批量营销只适合内部人员数量可控的通知场景。7.3 批量任务加日志和失败重试批量发送最怕失败后没有办法追踪。简单做法是把每次发送结果写入本地日志文件import csv import time result_rows [] for index, row in enumerate(sheet.iter_rows(min_row2, values_onlyTrue), start2): name, email row[0], row[1] try: service.send(to_list[email], subject内部通知, textf{name} 您好见信好。) result_rows.append([name, email, success, ]) except Exception as exc: result_rows.append([name, email, failed, str(exc)]) finally: time.sleep(2) with open(send_result.csv, w, newline, encodingutf-8-sig) as fp: writer csv.writer(fp) writer.writerow([姓名, 邮箱, 状态, 错误信息]) writer.writerows(result_rows)把结果写成csv后哪怕中途脚本被中断也可以根据本地日志筛选出失败名单重跑时只处理失败项。如果需要自动重试可以在捕获异常后对单个邮箱尝试 2 到 3 次每次间隔递增比如第一次失败后等 5 秒第二次失败后等 15 秒。要避免失败后无脑立刻重试否则只是反复撞同一堵墙。8. 定时发送与稳定性设计办公自动化的很多邮件并不是“手动触发”而是在固定时间自动触发。比如每天早上 9 点发日报、每周一早上发周报、每月 1 号发上月汇总。实现定时任务有两种最常用的思路任选一种都行。8.1 使用系统计划任务在 Windows 上可以用“任务计划程序”定时执行 Python 脚本在 Linux 上使用 crontab# 每天早上 09:00 执行发送任务 0 9 * * * cd /path/to/project /usr/bin/python3 send_report.py send_log.log 21这种方案的好处是进程不需要常驻内存运行完就退出节省资源。缺点是任务数量多了以后管理分散在多个 cron 条目或计划任务里依赖关系不直观。8.2 使用 APScheduler 定时调度Python 服务内部可以用APScheduler执行定时逻辑。安装依赖pip install apscheduler写一个常驻进程的应用from apscheduler.schedulers.blocking import BlockingScheduler from apscheduler.triggers.cron import CronTrigger from services.report_service import send_daily_report scheduler BlockingScheduler() scheduler.add_job( send_daily_report, triggerCronTrigger(hour9, minute0), iddaily_report, replace_existingTrue, ) if __name__ __main__: scheduler.start()APScheduler 适合需要在同一进程内维护任务状态、或者任务之间有关联的场景比如先查数据库再发邮件一个函数里就能串起来。8.3 稳定性设计思路要让邮件发送服务在无人值守时也能稳定运行有几个点值得按优先级处理第一发送逻辑要设置 SMTP 连接超时。否则 SMTP 服务器无响应时脚本可能卡住很久。第二单次任务处理多条邮件时建议把任务拆成“读取数据”和“发送邮件”两个阶段。先完整读取收件人列表和报表数据再进入发送循环避免在读取阶段因邮件发送异常导致数据读取中断。第三重试机制要分级。网络闪断可以马上重试认证失败不要重试因为大概率是配置问题服务商明确返回限频错误时要等待一段时间后再继续。第四增加“任务执行结果”日志每次发送任务完成后输出一行包含任务名、成功条数、失败条数、耗时的结构化日志。告警时才能快速知道是哪个环节出了问题。9. 常见问题与排查方法问题现象可能原因排查方式解决方案SMTPAuthenticationError密码、授权码错误或邮箱未开启 SMTP检查配置的授权码和账号是否一致重新生成并配置 SMTP 授权码Connection refused / timeout端口错误或网络不通telnet 测试端口连通性换 465/587 端口确认服务商限制starttls 报错服务器不支持 STARTTLS或端口选错查看服务商 SMTP 文档改用 SMTP_SSL 方式邮件发送到垃圾箱正文含敏感词、发信频率过高、域名信誉低用另一邮箱检查垃圾箱优化正文措辞降频检查邮箱域名收不到邮件投递延迟或服务商拦截查看发件方日志与退信确认退信内容检查收件地址附件中文名乱码没有按 RFC 2231 处理附件名查看邮件头 Content-Disposition使用filename(utf-8, , filename)批量发送到一半失败触发频率限制或某收件地址无效查看本地失败日志增加 sleep重试失败列表接口返回 500授权码失效、收件人列表为空、附件路径错误查看服务端 traceback 日志根据异常信息修改参数这几种问题里出现频率最高的是“能连上但登录失败”。如果检查过授权码没有复制错可以再去邮箱服务商的设置页确认是否已经开启 SMTP 服务以及账户是否处于安全限制状态。频繁尝试登录失败也可能触发服务商的安全保护导致暂时无法登录。大批量发送后收件人收不到邮件则需要先找退信。大部分邮箱服务商发送失败后会把退信信息发到发件邮箱的“已删除”或“收件箱”里里面通常会给出类似 550、554 的 SMTP 错误码。550 一般代表收件人不存在或对方拒收554 多为发件方策略拒绝可以隔一段时间再试或者降低发送速度。10. 最佳实践与使用建议10.1 配置管理把邮箱账号、授权码、SMTP 地址、收件人默认名单都放到代码外部。最简单的做法是使用环境变量生产环境再引入配置中心。避免把真实授权码直接提交到 Git 仓库。Windows 临时设置环境变量set SMTP_HOSTsmtp.example.com set SMTP_PORT465 set SMTP_USERsenderexample.com set SMTP_PASSyour-auth-code python send_report.pyLinux / macOS 临时设置环境变量export SMTP_HOSTsmtp.example.com export SMTP_PORT465 export SMTP_USERsenderexample.com export SMTP_PASSyour-auth-code python send_report.py10.2 开发阶段使用假邮箱服务刚开始开发时建议先用本地调试工具mailhog、Mailpit或者smtp4dev这类假 SMTP 服务测试。它们的原理是在本地起一个 SMTP 服务端邮件不会真实发送到外网而是在网页里展示收到的邮件内容。这样调试邮件模板、附件和收件人列表时不会产生真实发送行为更不会误发给真实用户。安装这一类工具后把SMTP_HOST设置为127.0.0.1端口设为工具默认端口即可让程序把邮件发到本地调试界面。确认模板和逻辑没有问题时再切回企业邮箱 SMTP。10.3 设置 BCC 密送如果邮件要同时发给多人而每个人不需要看到其他收件人地址可以使用 BCC。在原来代码基础上增加msg[Bcc] , .join(bcc_list) # 发送时收件人地址应包含 BCC 地址 server.sendmail(SMTP_USER, to_list bcc_list, msg.as_string())这里需要理解msg[To]和sendmail的收件人参数不一致是正常的。邮件头里的地址给收件人查看sendmail的地址才是 SMTP 实际投递对象。直接把大量邮箱放到msg[To]会泄露收件人信息办公通知场景建议尽量使用 BCC。10.4 合规与授权提醒编写邮件发送服务前需要确保自己拥有发送对象的联系方式并取得合理授权。不要在未授权的情况下采集、批量发送邮件也不要用来做垃圾广告。内部系统在发送前还应该再次检查收件人名单避免因名单误配导致信息泄密。使用企业邮箱账号配置自动发送时也要遵守公司邮件使用规范不要用系统账号发送与工作无关的内容。如果有“退订”需求不要再发不带退订入口的批量邮件。面向外部用户的邮件服务通常需要完整的订阅管理和退订流程这不是一个简单的smtplib脚本能替代的。10.5 预留“测试模式”在邮件服务里增加开关TEST_MODE。测试模式下不管收件人列表填什么实际都只发送到预先设置的白名单邮箱。开启这个开关后开发调试时就不怕误发邮件给真实用户。TEST_MODE True TEST_RECEIVER qaexample.com if TEST_MODE: actual_to_list [TEST_RECEIVER] else: actual_to_list payload_to_list邮件标题可以统一加上前缀【测试】方便识别。上线前关闭测试模式然后做一轮真实发信验证即可。10.6 观察日志和运行状态一个没有日志的邮件服务等于随时会出问题但无法定位的服务。推荐在每次发送时记录任务 ID、收件人数、成功数、失败数、耗时、异常摘要。为了保险把最近一次任务的状态写到一个文件里再配合其他监控系统检查。这样“邮件服务是不是还在正常工作”这个问题就有了可观测的判断依据。办公自动化里的邮件发送服务做到这个程度已经可以支撑大多数内部通知和报表分发需求了。如果你手头正缺这样的模块建议先把纯文本最小示例跑通再按实际场景逐步加入 HTML 模板和附件第一次批量发送前先做 5 封以内的测试发送再扩大到完整名单。碰到报错不用慌绝大多数问题都出在授权码和端口配置这两步。
返回列表