news 2026/9/4 7:17:30

用Python搭建邮件自动发送服务:从smtplib脚本到Flask接口全攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用Python搭建邮件自动发送服务:从smtplib脚本到Flask接口全攻略

最近有人问:“能不能用一个 Python 脚本,把每天的库存报表定时发到指定邮箱?”

答案是可以,而且完全不需要引入太重的框架。Python 自带的smtplib配合标准库email,已经能把“发文本邮件、发 HTML 邮件、带附件、群发、按 CSV 名单批量发送”这些日常办公场景都覆盖掉。如果公司内部还需要把发邮件能力封装成服务,让其他业务系统调用,也能用 Flask 包的接口方式实现。

这次我们就从办公自动化的角度出发,把一套完整的Python 邮件发送服务拆开讲清楚。文章不会只停留在“能发一封邮件”这个层面,而是会带你走完这几件事:申请 SMTP 授权、封装发送函数、用 CSV 批量发送、用 Flask 提供 HTTP 接口、接入定时任务,以及对发送失败做日志和重试。内容偏实操,适合正在做 Python 办公自动化、想给团队或自己搭建定时报表通知能力的读者收藏。

1. 核心能力速览

在动手写代码前,先明确这套 Python 邮件发送服务能做到什么程度。

能力项说明
核心依赖Python 标准库smtplibemail,可选Flaskpython-dotenvschedule
通信方式SMTP 协议,兼容主流邮箱和企业邮箱
邮件类型纯文本、HTML 正文、带附件、多收件人
批量任务支持 CSV / Excel 名单批量发送,适合报表分发场景
接口能力可选封装为 HTTP 接口,方便其他系统调用
定时执行支持 schedule 或系统 cron / 计划任务
运行平台Windows、Linux、macOS 均可
最低环境Python 3.8 以上即可,无需 GPU
适合场景定时报表、任务完成通知、监控告警、批量发送内部通知

这个方案的核心不在于代码量多少,而在于把发送流程标准化。日常办公中,邮件内容无非是“固定模板 + 少量变量”,比如收件人姓名、日期、附件名。把可变部分单独抽出来,然后把函数封装成可以重复调用的模块,后面每次使用只需要传入几个参数。

2. 适用场景与使用边界

邮件发送服务在办公自动化里是比较稳的一类需求,因为它不依赖操作界面,也不需要打开浏览器。常见的几种落地场景如下:

  • 数据报表推送:每天定时统计数据库或 Excel 表,生成 PDF / Excel 附件后发送给管理层。
  • 任务状态通知:Python 脚本处理完一批文件后,自动把结果汇总并发送给负责人。
  • 异常告警:监控脚本定期检查网站、接口或服务器状态,发现异常时发送告警邮件。
  • 批量通知:从人员名单中批量发送培训通知、会议提醒等内部邮件。

不过有几个边界需要提前想清楚。

第一,收件人必须合法。无论是企业批量群发还是日常运营邮件,都要基于用户同意或内部工作授权。如果是面向外部用户的营销类邮件,不建议用个人 SMTP 邮箱裸发,一方面到达率不稳定,另一方面有垃圾邮件风险,应该使用专业的邮件发送平台。

第二,邮件内容要保留留痕。公司内部使用时不建议永久删除发件记录,建议在发送后把本地发送日志、附件列表都保存下来,方便后续追溯。

第三,账号安全是底线。发件邮箱的授权码、SMTP 密码不能硬编码在业务代码里,更不能上传到公开仓库。如果代码仓库泄露,邮箱很可能被拿来发垃圾邮件。

第四,不要拿该脚本做任何绕过平台限制的事情。比如频繁换发件人来规避邮件发送限制、伪造他人身份发送,这些都属于违规操作。

3. 环境准备与前置条件

动手之前要准备以下环境。

3.1 Python 版本与依赖

建议使用 Python 3.8 或更高版本。如果是纯标准库发送邮件,其实不需要安装任何第三方包;如果要做 HTTP 接口服务,再安装 Flask。

# 只做标准库邮件发送,无需额外安装 # 如果要做 Web API 服务,需要安装 Flask pip install Flask python-dotenv # 如果要在 Python 内做定时调度 pip install schedule

检查 Python 版本:

python --version

3.2 发件邮箱准备

任何一种主流邮箱都可以,但需要先开启 SMTP 服务,并获取一个授权码而不是邮箱登录密码。绝大多数邮箱的官方客户端、SMTP 配置页面里都可以找到“开启服务”按钮。不同邮箱的 SMTP 服务器地址和端口不同:

发件邮箱类型SMTP 服务器地址不建议用端口建议用端口
QQ 邮箱smtp.qq.com25465(SSL)
163 邮箱smtp.163.com25465(SSL)
126 邮箱smtp.126.com25465(SSL)
Outlook 邮箱smtp-mail.outlook.com25587(STARTTLS)

这里要做一个重要提醒:不要使用 25 端口作为生产邮件发送端口。很多云服务器默认封禁 25 端口,这是因为该端口长期被大量垃圾邮件滥用。如果脚本部署在云服务器上,使用 465 或 587 会更稳。

获取授权码的过程一般是:登录邮箱官网,进入设置,找到账户或客户端授权,开启 SMTP 服务,生成授权码。授权码通常只会完整显示一次,生成后要立刻复制保存。

3.3 使用 .env 保存配置

不建议把邮箱账号、授权码直接写在代码里。这里使用.env文件保存发件基础信息,并让 Python 通过python-dotenv读取。

# .env 文件示例,不要提交到 Git SMTP_HOST=smtp.qq.com SMTP_PORT=465 SMTP_USER=你的邮箱地址 SMTP_AUTH_CODE=你的授权码

代码启动时加载:

import os from dotenv import load_dotenv load_dotenv() SMTP_HOST = os.getenv("SMTP_HOST") SMTP_PORT = int(os.getenv("SMTP_PORT", "465")) SMTP_USER = os.getenv("SMTP_USER") SMTP_AUTH_CODE = os.getenv("SMTP_AUTH_CODE")

同时建议在.gitignore中加入.env

.env

4. 核心实现:用 smtplib 发送邮件

先写一个最小的可运行版本。这段代码只做一件事:登录发件服务器,把一封纯文本邮件发送给指定收件人。

import os import smtplib from email.message import EmailMessage from dotenv import load_dotenv load_dotenv() def send_text_email(subject: str, content: str, receiver: str): msg = EmailMessage() msg["Subject"] = subject msg["From"] = os.getenv("SMTP_USER") msg["To"] = receiver msg.set_content(content) with smtplib.SMTP_SSL( os.getenv("SMTP_HOST"), int(os.getenv("SMTP_PORT", "465")), timeout=30 ) as server: server.login(os.getenv("SMTP_USER"), os.getenv("SMTP_AUTH_CODE")) server.send_message(msg) print(f"邮件已发送至: {receiver}") if __name__ == "__main__": send_text_email( subject="Python 邮件发送测试", content="这是一封来自 Python smtplib 的测试邮件。", receiver="接收方邮箱@example.com" )

这里的重点有三个:

  1. EmailMessage负责构建邮件对象,字段包含主题、发件人、收件人、正文。
  2. SMTP_SSL直接走 SSL 加密连接,适合 465 端口。
  3. server.login()登录时,第二个参数填的是授权码,不是邮箱密码。

如果使用的是 587 端口,需要把连接方式改成SMTP并调用starttls()

import smtplib with smtplib.SMTP(os.getenv("SMTP_HOST"), 587, timeout=30) as server: server.starttls() server.login(os.getenv("SMTP_USER"), os.getenv("SMTP_AUTH_CODE")) server.send_message(msg)

建议把这两种连接方式合并成一个函数,根据端口自动切换。这样代码适配性更强。

def get_smtp_server(host: str, port: int): if port == 465: return smtplib.SMTP_SSL(host, port, timeout=30) server = smtplib.SMTP(host, port, timeout=30) server.starttls() return server

5. 实战功能扩展

文本邮件只是基础。办公场景里,我们更常遇到的是这样几类需求:邮件正文需要带格式、邮件需要带附件、同一封邮件要发给多个人,以及同一批人收到的内容要各不相同。

5.1 发送 HTML 正文邮件

用 HTML 写正文,可以呈现表格、段落、高亮等样式,适合发数据简报。

html_content = """ <html> <body> <h3>库存日报</h3> <table border="1" cellpadding="5"> <tr> <th>商品</th> <th>库存</th> <th>状态</th> </tr> <tr> <td>商品A</td> <td>120</td> <td style="color: green;">充足</td> </tr> <tr> <td>商品B</td> <td>8</td> <td style="color: red;">不足</td> </tr> </table> <p>请相关负责人及时补货。</p> </body> </html> """

EmailMessage中同时设置set_contentadd_alternative,前者为纯文本降级展示,后者为 HTML 渲染展示:

from email.message import EmailMessage msg = EmailMessage() msg["Subject"] = "库存日报" msg["From"] = SMTP_USER msg["To"] = receiver msg.set_content("请使用支持 HTML 的邮箱客户端查看本邮件。") msg.add_alternative(html_content, subtype="html")

5.2 添加附件

附件是办公邮件的刚需。添加场景最常见的是 Excel、PDF、图片等文件。读取附件时务必使用二进制模式,否则文件内容会被破坏。直接在代码中写作'rb',不要写成'r'

import os from email.message import EmailMessage def attach_file(msg: EmailMessage, file_path: str): file_name = os.path.basename(file_path) with open(file_path, "rb") as f: msg.add_attachment( f.read(), maintype="application", subtype="octet-stream", filename=file_name )

如果你知道自己附件的 MIME 类型,可以更精确地指定。比如 PDF 文件是:

msg.add_attachment(data, maintype="application", subtype="pdf", filename="report.pdf")

Excel 文件可以指定为:

msg.add_attachment(data, maintype="application", subtype="vnd.openxmlformats-officedocument.spreadsheetml.sheet", filename="report.xlsx")

如果对文件的 MIME 类型不确定,直接用通用的application/octet-stream也能被大多数客户端识别。在 Python 3.7+ 中,add_attachment会优先使用文件名后缀猜测类型,所以多数文件无需手工指定maintypesubtype

5.3 一封邮件发送给多个收件人

处理“发给一批人”的场景有两种情况:

  • 所有人都在收件人列表里:直接用逗号拼接。
  • 有多个收件人和密送:分别设置。
msg["To"] = "a@example.com, b@example.com" msg["Cc"] = "manager@example.com" msg["Bcc"] = "secret@example.com"

调用send_message前,还要额外传一个参数来告诉 SMTP 服务器收件人地址列表。EmailMessage对象里的To只是展示给收件人看的头字段,真实的 RCPT TO 地址需要单独指定,否则会有部分地址收不到信。

from email.utils import getaddresses to_list = [addr for _, addr in getaddresses([msg["To"]])] cc_list = [addr for _, addr in getaddresses([msg["Cc"]])] bcc_list = [addr for _, addr in getaddresses([msg["Bcc"]])] server.send_message(msg, to_addrs=to_list + cc_list + bcc_list)

这个细节很容易踩坑。直接调用server.send_message(msg)时,Python 会从msg["To"]msg["Cc"]msg["Bcc"]中提取收件人,但也有一部分自定义场景下提取不全。稳妥做法是显式传to_addrs

5.4 带人名显示的发件人格式

如果希望收件人在邮箱里看到的发件人名称不是一串邮箱字符,需要使用formataddr

from email.utils import formataddr sender_name = "自动化运维系统" sender_addr = os.getenv("SMTP_USER") msg["From"] = formataddr((sender_name, sender_addr)) receiver_name = "张三" receiver_addr = "zhangsan@example.com" msg["To"] = formataddr((receiver_name, receiver_addr))

这里要注意中文名称在部分老客户端可能出现编码问题,建议配合email.header处理。不过当前主流 Python 版本中,直接使用formataddr通常不会出现问题。

6. 批量发送与模板渲染

办公自动化最常用的是“一份名单,每个人收到不同正文”的场景。比如给销售团队每个成员发送周业绩通知,每个人的姓名、业绩数字都不一样。

6.1 准备名单 CSV

准备一个 CSV 文件receiver_list.csv

name,email,message 李四,libo@example.com,产品A本周销量很好,继续保持。 王五,wangwu@example.com,产品B存在库存积压,需要调整策略。 赵六,zhaoliu@example.com,本周客户回访完成率偏低,请关注。

6.2 批量发送代码

import csv import os import smtplib from email.message import EmailMessage from email.utils import formataddr from dotenv import load_dotenv load_dotenv() def render_html(name: str, message: str) -> str: return f""" <html> <body> <p>{name},你好:</p> <p>{message}</p> <p>本邮件由 Python 自动发送,请勿直接回复。</p> </body> </html> """ def send_batch(csv_path: str): with open(csv_path, "r", encoding="utf-8-sig") as f: reader = csv.DictReader(f) rows = list(reader) with smtplib.SMTP_SSL( os.getenv("SMTP_HOST"), int(os.getenv("SMTP_PORT", "465")), timeout=30 ) as server: server.login(os.getenv("SMTP_USER"), os.getenv("SMTP_AUTH_CODE")) for row in rows: msg = EmailMessage() msg["Subject"] = "本周业务通知" msg["From"] = formataddr(("自动化通知", os.getenv("SMTP_USER"))) msg["To"] = row["email"] msg.set_content(f"{row['name']},你好:{row['message']}") msg.add_alternative(render_html(row["name"], row["message"]), subtype="html") server.send_message(msg) print(f"已发送: {row['name']} -> {row['email']}") if __name__ == "__main__": send_batch("receiver_list.csv")

注意encoding="utf-8-sig"是为了兼容部分 Excel 保存 CSV 时带 BOM 头的情况。如果你是用 pandas 读取 Excel 生成 DataFrame,再循环发送,逻辑也类似,只是数据源从 CSV 变成了 DataFrame。

6.3 批量发送的稳定性优化

真实办公环境里,名单可能有几十人、几百人,此时不能只考虑发送成功这一件事,还要考虑失败重试、发送间隔和日志记录。建议每一行发送时单独 try-except,不要把整个循环包在一个 try 里,否则中间失败会导致后面全部中断。

import time fail_list = [] for index, row in enumerate(rows, 1): try: # 组装并发送邮件 server.send_message(msg) success_log.append(row["email"]) print(f"[{index}/{len(rows)}] 成功: {row['email']}") except Exception as exc: fail_list.append({"email": row["email"], "error": str(exc)}) print(f"[{index}/{len(rows)}] 失败: {row['email']}, 错误: {exc}") if index % 10 == 0: time.sleep(1) # 每发送 10 封暂停 1 秒,降低触发限流概率

发送节奏也很重要。不建议对几百封邮件做无间隔连续发送,这会很容易触发邮箱平台的临时限制。保持一定的发送频率阈值,并记录失败名单,结束后统一重试,比一次性无脑发完更可靠。

7. 把邮件发送封装成 HTTP 接口服务

当多个脚本或业务系统都要发邮件时,最合理的方案不是把邮箱账号和 SMTP 配置复制到每个项目里,而是单独起一个邮件发送服务,别人只需要调用 HTTP 接口。这里使用 Flask 来实现。

7.1 服务端代码

import os import smtplib from email.message import EmailMessage from email.utils import formataddr from flask import Flask, request, jsonify from dotenv import load_dotenv load_dotenv() app = Flask(__name__) def send_mail(subject: str, content: str, to_list: list, file_paths: list = None): msg = EmailMessage() msg["Subject"] = subject msg["From"] = formataddr(("自动化邮件服务", os.getenv("SMTP_USER"))) msg["To"] = ", ".join(to_list) msg.set_content(content) if file_paths and len(file_paths) > 0: for file_path in file_paths: with open(file_path, "rb") as f: msg.add_attachment( f.read(), maintype="application", subtype="octet-stream", filename=os.path.basename(file_path) ) server = smtplib.SMTP_SSL( os.getenv("SMTP_HOST"), int(os.getenv("SMTP_PORT", "465")), timeout=30 ) server.login(os.getenv("SMTP_USER"), os.getenv("SMTP_AUTH_CODE")) server.send_message(msg) server.quit() @app.route("/api/email/send", methods=["POST"]) def email_send_api(): data = request.get_json(force=True) subject = data.get("subject", "无主题") content = data.get("content", "") to_list = data.get("to_list", []) if not to_list: return jsonify({"success": False, "message": "to_list 不能为空"}), 400 try: send_mail(subject=subject, content=content, to_list=to_list) return jsonify({"success": True, "message": "邮件已发送"}) except Exception as exc: return jsonify({"success": False, "message": str(exc)}), 500 if __name__ == "__main__": app.run(host="127.0.0.1", port=8000, debug=False)

这里的服务默认只绑定127.0.0.1,也就是只有本机能访问。如果确实需要给局域网内其他机器提供服务,可以把 host 改成0.0.0.0,但建议同时加一层 Token 验证,避免接口被滥用。

API_TOKEN = os.getenv("API_TOKEN", "change-me") @app.route("/api/email/send", methods=["POST"]) def email_send_api(): token = request.headers.get("X-Token") if token != API_TOKEN: return jsonify({"success": False, "message": "无权限访问"}), 401 # 后续逻辑不变 ...

7.2 调用接口

启动服务后,在另一个终端里用 curl 测试:

curl -X POST http://127.0.0.1:8000/api/email/send \ -H "Content-Type: application/json" \ -H "X-Token: change-me" \ -d '{ "subject": "来自接口服务的测试邮件", "content": "接口调用成功", "to_list": ["zhangsan@example.com"] }'

Python 侧调用:

import requests url = "http://127.0.0.1:8000/api/email/send" payload = { "subject": "来自接口服务的测试邮件", "content": "接口调用成功", "to_list": ["zhangsan@example.com"] } headers = {"X-Token": "change-me"} resp = requests.post(url, json=payload, headers=headers, timeout=60) print(resp.status_code) print(resp.json())

这个封装方式非常灵活。业务部门的 Python 脚本、Java 后端、定时任务,都可以通过一次 HTTP POST 完成发信,统一由邮件服务集中处理账号配置、发送频率和失败日志。

8. 定时任务与自动化调度

邮件服务的另一半价值是“不需要人每天手动运行”。落地方式有三种:在 Python 进程内部用 schedule、在 Linux 上使用 cron、在 Windows 上使用计划任务。

8.1 Python 内部定时调度

如果不想依赖系统级调度,可以在主进程里使用schedule库。

import schedule import time from email_sender import send_daily_report # 每天 9 点发送库存日报 schedule.every().day.at("09:00").do(send_daily_report) # 每 30 分钟检查一次服务状态并发送告警 schedule.every(30).minutes.do(check_service_status) if __name__ == "__main__": while True: schedule.run_pending() time.sleep(1)

使用schedule的注意点是:定时任务进程必须长驻运行,不能在脚本执行完就退出。适合部署在服务器上或长开机的内网主机中。

8.2 Linux cron 调度

更适合生产环境的方式是把发邮件的功能封装成一个独立脚本,然后用crontab定时执行。

# 每天 9 点执行 0 9 * * * cd /opt/email-service && /usr/bin/python3 send_daily_report.py >> logs/send.log 2>&1

8.3 Windows 计划任务

在 Windows 上可以直接使用任务计划程序,设置每天固定时间运行:

python C:\work\email_service\send_daily_report.py

需要重点关注的是:如果用 Python 脚本读取相对路径的独立应用,请先把工作目录设为一个确定的路径。通过 crontab 执行时,用cd切换目录;通过 Windows 计划任务执行时,在“起始位置”字段填入脚本所在目录。

9. 资源占用与性能观察

邮件发送服务不像 AI 推理那样需要 GPU 显存,它属于轻量级网络 I/O 类任务,资源占用主要取决于三个因素:单封邮件大小、附件体积、发送频率。

建议在实际部署时关注以下几个指标。

先看启动后的基础资源占用:

# Linux 查看占用内存 ps aux | grep email_service # Windows 任务管理器中查看 python.exe 的内存占用

纯文本邮件的每次发送内存占用很小,但加载大附件时会一次性把附件内容读入内存。如果附件达到几十 MB,要注意内存消耗。例如用f.read()读取 100 MB 附件时,内存峰值会明显上升。处理大附件建议分批读取:

with open(large_file_path, "rb") as f: chunks = [] while True: chunk = f.read(1024 * 1024) # 每次 1MB if not chunk: break chunks.append(chunk)

EmailMessage.add_attachment最终还是会整体拼接邮件对象,所以对超大附件场景,一次性发送并不是最优方案。更合理的做法是对附件先压缩或分片,或者改用企业网盘链接。

观察发送速率时,可以使用以下思路:

  • 记录每次发送耗时:time.perf_counter()
  • 记录每分钟发送数量。
  • 当连续多次出现超时或认证失败时,应该主动降低速率。

10. 常见问题与排查方法

这里整理了实际使用中最容易踩到的问题,按检查顺序排列。

问题现象可能原因排查方式解决方案
smtplib.SMTPAuthenticationError账号或授权码错误,或授权码已过期检查.env中账号和授权码重新生成新的授权码
连接超时或socket.gaierrorSMTP 主机名错误,或网络无法访问该地址ping 或nslookup smtp.xxx.com确认主机名,检查网络是否连通
25 端口连接失败云服务器或网络封禁 25 端口查看服务端返回的具体错误码和连接日志改用 465 / 587 端口
成功发送但收件人没收到进了垃圾箱,或收件人地址有误查看发送日志中的成功记录,但不要直接认为成功等于到达在邮箱中查看垃圾箱,调整邮件内容降低垃圾邮件特征
邮件中文乱码主题或正文编码设置不当查看邮件源码头信息使用EmailMessage而非手动拼接 MIME,避免自定义编码头
收到的 Excel / PDF 附件无法打开读取文件时用了文本模式检查代码中的open模式附件读取使用"rb"二进制模式
批量发送到一半中断某一封地址格式错误或触发限流查看异常堆栈中是否已经打印邮件地址每封邮件单独 try-except,失败记录后继续
Flask 服务启动但局域网无法访问绑定地址是127.0.0.1检查app.run的 host 参数和防火墙按需绑定0.0.0.0,同时加 Token 校验
定时任务执行后没有发送任何邮件crontab 或计划任务工作目录错误查看日志,或先手动执行一次脚本切换到固定工作目录,使用绝对路径加载配置和文件

排查邮件问题的一个通用调试点是看返回异常类型

  • socket.timeout:网络层问题,优先检查防火墙、端口、服务器可达状态。
  • SMTPAuthenticationError:认证层问题,优先检查授权码。
  • SMTPRecipientsRefused:接收方服务器拒绝,通常是该邮箱地址不存在或对方服务器策略。
  • ConnectionRefusedError:远程端口没有开放或本地被防火墙拦截。

建议在写代码时统一捕获异常并保存到日志文件,不要只print到控制台。生产环境里,控制台输出很容易在重启后丢失。

11. 最佳实践与落地建议

到这里,整个邮件发送服务的技术骨架已经完整。在真正用于办公环境前,还有几个工程化经验值得落实。

11.1 先小规模验证,再上量

第一次运行时,不要直接发几百封。先用一个测试收件人列表发 1 到 2 封,重点检查正文是否正常、附件是否能打开、会不会进垃圾箱。确认无误后再启动批量任务。

11.2 使用统一的模板系统

如果邮件形态比较固定,建议使用jinja2管理模板,而不是在 Python 脚本里用 f-string 拼接超长 HTML。模板独立成文件,改文案时不需要动代码。

from jinja2 import Environment, FileSystemLoader env = Environment(loader=FileSystemLoader("templates")) template = env.get_template("report.html") html_content = template.render( user_name="张三", report_date="2026-01-01", items=[ {"name": "商品A", "stock": 120}, {"name": "商品B", "stock": 8} ] )

安装依赖:

pip install jinja2

11.3 日志与失败重试

每封邮件的发送结果都应该记录。建议记录以下字段:时间、收件人、主题、是否成功、失败原因、重试次数。如果发送失败,采用指数退避策略重试,例如第一次等 5 秒、第二次等 25 秒、第三次等 125 秒,最多重试 3 次后进入失败队列。

11.4 外部系统调用接口要做鉴权

如果你的邮件服务不对公网开放,限制到内网访问即可;一旦有跨网段调用需求,必须加 Token 或基于内部服务网关的鉴权。不建议把邮件服务绑定到公网0.0.0.0且无鉴权。

11.5 合规与内容确认

在正式发送批量邮件前,一定要确认收件人来源合法且对收件人有合理预期。对外群发营销类邮件之前,先确认是否完成了公司的合规审核,避免一天发出大量投诉或退订。

11.6 避免硬编码路径

在实际业务中,发件脚本运行时会遇到路径问题。比如本地能运行,放到计划任务或 cron 里却找不到文件,这通常是因为脚本里使用了相对路径./data/xxx.xlsx。建议把项目根目录固定在脚本开头:

from pathlib import Path BASE_DIR = Path(__file__).resolve().parent CSV_PATH = BASE_DIR / "data" / "receiver_list.csv" ATTACH_PATH = BASE_DIR / "output" / "report.xlsx"

12. 总结与下一步

这套 Python 邮件发送服务最适合解决的,是“固定模板 + 批量名单 + 定时发送”这类重复劳动。核心代码量不多,标准库足以覆盖基础场景;引入 Flask 后则可以交给团队里多个系统统一调用,真正变成一个内部基础服务。

先验证的应当是基础邮件是否真的能发送成功。如果你使用的是 QQ 邮箱、163 邮箱这类个人账号,可以从授权码申请那一步开始测起;如果使用企业邮箱,优先确认 SMTP 服务器地址和端口策略,再开始编码。最容易踩的坑大概率会在授权码、25 端口封禁和批量中断三处。

下一步可以扩展的方向也很多:把发件记录写入 SQLite 或 MySQL,方便后续追踪;接入文件监控,新增报表即自动发送;把邮件模板改造成支持 Excel 数据填充的通用能力;再进一步,还可以把这套服务和公司内部已有的监控告警系统打通,让它成为任务通知的统一出口。先跑通一版最小可用服务,再随着使用场景慢慢加功能,这是比较稳的推进方式。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/4 7:17:13

下场直播了两周数字孪生之后,我看清了5点真相。

深耕数字孪生、我一直笃定一个最稳妥的团队增长逻辑&#xff1a;创始人先躬身入局、踩遍所有坑、跑通完整模式&#xff0c;沉淀出标准化方法论&#xff0c;再让团队全员复刻放大。这套逻辑&#xff0c;放在产品交付、业务谈单、项目管理上&#xff0c;百试百灵。我也一直带着这…

作者头像 李华
网站建设 2026/9/4 7:16:55

STM32电阻炉温度控制:工业级PID与热电偶测温实战

简介&#xff1a;本资源是一套基于STM32F103C8T6的电阻炉炉温控制系统完整设计工程&#xff0c;面向嵌入式初学者、课程设计学生及工业温控项目开发者&#xff0c;解决传统电阻炉温度响应滞后、人工干预频繁、缺乏远程监控等实际问题。工程已在Proteus中完成软硬件联合仿真&…

作者头像 李华
网站建设 2026/9/4 7:14:47

基于 PX4 6X 的无人机机载机械臂设计2:舵机控制板与电控系统设计

一、前言在上一篇中&#xff0c;我们分享了无人机机载机械臂的 3D 打印轻量化关节与碳管机械结构。然而&#xff0c;要让机械臂在空中平稳、可靠地工作&#xff0c;仅有机械骨架是远远不够的。在实际机载环境中&#xff0c;总线舵机大电流瞬变对飞控供电的冲击、半双工通信协议…

作者头像 李华