ARTICLE DETAIL

资讯详情

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

Python自动收发邮件实战:从SMTP到IMAP的完整指南

Python自动收发邮件实战:从SMTP到IMAP的完整指南 去年年初我维护的一台业务服务器频繁在深夜报警我连续一周半夜爬起来查日志、看报表、回消息整个人都快被拖垮了。后来我下定决心把整套流程交给 Python每日订单汇总早上八点自动发出服务异常时的告警邮件在几十秒内送达手机连一些格式固定的回复也交给了程序代劳。用 Python 自动收发邮件听起来像是老掉牙的话题但真正把它做对、做稳里面其实有不少门道。这篇文章写给所有想把重复性邮件工作交给程序的人——无论是定时发报表、批量发通知还是从收件箱里提取附件自动入库看完你都能直接动手。我默认你已经装好了 Python 环境至少是 3.8 以上的版本。如果没有装先去官网下载安装包把环境配好再回来看代码。下面我就按自己的实操路径把发件、收件、踩坑、落地的完整过程展开讲。1. 先想清楚你的自动化场景再决定怎么写代码很多人拿到自动收发邮件这个需求第一反应就是搜代码、抄下来、跑通结果跑了两天就放弃了。问题几乎都出在场景没想清楚你到底要让程序做什么做到什么程度失败的时候谁兜底。1.1 最常见的三种自动化邮件需求我接触过的自动化邮件项目基本可以归为下面三类你看自己属于哪一种再去对照后面的章节需求类型典型场景核心诉求定时发送报告每天早上把昨天的销售数据、访问量、服务器状态汇总成邮件发出去程序按时触发内容稳定负责人无需手工整理批量发送通知给一批客户发送活动通知、给团队成员发送任务分配收件人列表可维护发送过程有日志失败能重试自动处理入站邮件收到订单邮件后自动下载附件、收到退订邮件后自动更新名单程序能监听收件箱解析内容触发后续动作第一种最容易被 Python 取代因为它完全是定时 格式化内容的组合。第二种需要额外注意发送频率和收件人数据管理不能拿着个人邮箱硬发几千封。第三种技术含量最高涉及 IMAP 协议、邮件解析、附件处理也是本文后面要重点展开的。1.2 邮件为什么至今还是自动化的首选渠道你可能会问现在都用企业微信、钉钉、飞书了邮件是不是落伍了恰恰相反邮件在自动化这件事上有一个不可替代的优势它是完全开放的协议SMTP、IMAP、POP3 都是公开标准没有任何一家公司能垄断它。只要对方有一个合法的电子邮箱地址你就能把消息从你的程序直接送进对方的收件箱。相比之下IM 工具的消息推送往往依赖第三方接口审核、配额、回调签名都是成本短信一条一毛钱批量发送还要走内容审核流程。邮件免费、稳定、跨平台再加上本就支持 HTML 富文本和附件做业务通知和报表分发再合适不过。1.3 先给程序的工作范围画一条线这是我最想强调的一点自动化要有边界。我见过有人把自动回复写成了一句简单的 if 关键词匹配结果用户发来退款投诉也被自动回复将在 24 小时内处理最后客户被激怒问题升级。正确做法是先把范围定清楚——什么操作程序可以独立完成什么操作必须转人工。我的经验是只让程序处理高确定性、低风险的任务比如收到日报邮件就下载附件每天晚上清点未处理工单并催办。涉及钱、法律、投诉、复杂沟通的邮件程序只负责标记、分类、提醒最终决策留给人。把这个原则想明白了后面写代码会踏实很多。2. 发送邮件SMTP 协议与 smtplib 的实战组合发送是整个自动化的基础也是大多数人第一个要跑通的功能。写代码之前先把底层链路弄清楚。2.1 发一封邮件数据到底走了一条什么路你可以把邮件想象成纸质信你的 Python 程序是写信的人SMTP 服务器是最近的邮局对方的收件服务器是对方小区的收发室收件人的邮箱客户端是最终的收件箱。程序通过smtplib与发件人的 SMTP 服务器建立一个加密连接提交发件人、收件人、标题、正文、附件这封信的完整内容。发件服务器校验你的身份没问题后会把这封信转给收件人域名对应的收件服务器。双方服务器之间经过一系列投递、反垃圾检查最终落到收件人的邮箱里。这个链路里程序直接打交道的是发件服务器。所以你需要知道的是你自己的邮件服务商提供的 SMTP 地址和端口而不是对方服务器的地址。端口和加密方式也值得注意SSL 加密通常用 465 端口STARTTLS 通常用 587 端口。QQ 邮箱和 163 邮箱都支持这两种方式我习惯用 465连接逻辑最简单。2.2 准备账号授权码不是你的登录密码这里有一个百分之八十的新手都会踩的坑用邮箱的登录密码去调用 SMTP。直接后果就是报错SMTPAuthenticationError。主流的免费邮箱出于安全原因不允许第三方代码直接用账号密码做 SMTP 登录需要在邮箱设置里开通 SMTP 服务并生成一个独立授权码。以 QQ 邮箱为例进入设置选择账户找到 POP3/IMAP/SMTP 服务这一栏开启 SMTP 服务和 IMAP 服务按提示发送短信验证然后就能拿到一串授权码。163 邮箱的操作路径类似协议开通后同样能生成独立密码。Gmail 则需要开启两步验证后创建一个应用专用密码。拿到授权码后有一个很关键的习惯不要把授权码直接硬编码在源代码里。哪怕你只是自己用也建议放在环境变量里或者放在一个不提交到版本库的配置文件里。我自己的做法是写在.env文件里用python-dotenv加载这样既方便本机调试也不会因为代码传到公开仓库泄露密钥。2.3 用 smtplib 发送第一封纯文本邮件环境准备好了直接看代码。下面是我最常用来测试连通性的最小脚本import os import smtplib from email.mime.text import MIMEText from email.utils import formataddr SMTP_HOST os.getenv(SMTP_HOST, smtp.qq.com) SMTP_PORT int(os.getenv(SMTP_PORT, 465)) USERNAME os.getenv(SMTP_USER) AUTH_CODE os.getenv(SMTP_AUTH_CODE) TO_ADDR os.getenv(TO_ADDR) msg MIMEText(这是一封 Python 自动发送的测试邮件。, plain, utf-8) msg[From] formataddr((自动化小助手, USERNAME)) msg[To] TO_ADDR msg[Subject] SMTP 连通性测试 with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT) as server: server.login(USERNAME, AUTH_CODE) server.sendmail(USERNAME, [TO_ADDR], msg.as_string()) print(邮件发送成功)这段脚本里几个细节值得展开。MIMEText的第一个参数是正文第二个参数plain表示纯文本第三个参数显式指定 UTF-8 编码。这里务必带上utf-8不然中文正文在部分服务商的服务器上会出现编码问题。formataddr用来构造名称 邮箱地址格式的发件人信息这样收件人看到的是你定义的中文名而不是一串光秃秃的邮箱地址。SMTP_SSL会直接在 TCP 连接上启动 SSL 加密连接 465 端口时就是这么用的。由于我们用的是上下文管理器with发送完成后会自动关闭连接不需要手动调用quit()。server.sendmail(USERNAME, [TO_ADDR], msg.as_string())的第二个参数是收件人列表这就是批量发送的入口——你可以传入一个包含多个地址的列表。2.4 升级版发送 HTML 邮件和带附件的邮件运营活动通知、定期报表这类场景纯文本不够用需要 HTML 排版甚至携带附件。这时候要用到MIMEMultipartimport os import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.application import MIMEApplication from email.utils import formataddr SMTP_HOST os.getenv(SMTP_HOST, smtp.qq.com) SMTP_PORT int(os.getenv(SMTP_PORT, 465)) USERNAME os.getenv(SMTP_USER) AUTH_CODE os.getenv(SMTP_AUTH_CODE) TO_ADDR os.getenv(TO_ADDR) msg MIMEMultipart(related) msg[From] formataddr((运维报表系统, USERNAME)) msg[To] TO_ADDR msg[Subject] 8 月 15 日服务器监控报表 html_body html body p您好/p p以下是今日监控报表摘要完整数据请查收附件。/p table border1 cellpadding6 styleborder-collapse: collapse; trth指标/thth当前值/thth状态/th/tr trtdCPU 使用率/tdtd42%/tdtd正常/td/tr trtd内存使用率/tdtd71%/tdtd正常/td/tr trtd磁盘空间/tdtd83%/tdtd注意/td/tr trtd在线用户数/tdtd1520/tdtd正常/td/tr /table p报告生成时间2025-08-15 08:00:00/p /body /html msg.attach(MIMEText(html_body, html, utf-8)) # 附加一个 CSV 文件 with open(report_20250815.csv, rb) as f: attachment MIMEApplication(f.read()) attachment.add_header(Content-Disposition, attachment, filenamereport_20250815.csv) msg.attach(attachment) with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT) as server: server.login(USERNAME, AUTH_CODE) server.sendmail(USERNAME, [TO_ADDR], msg.as_string()) print(报表邮件已发送)这里有一个合入经验正文和附件都通过attach挂到MIMEMultipart对象上正文用 HTML MIME 类型附件用MIMEApplication把文件字节流包起来。Content-Disposition的attachment会告诉收件方这是一个附件filename指定了客户端显示的文件名。对于 CSV、Excel、PDF 这类文件MIMEApplication都能直接兼容。另外提醒一点HTML 邮件不要做成重图、复杂 CSS 的网页。很多邮件客户端的渲染引擎比较老对外部样式、JavaScript 的支持很差。内联样式是相对稳妥的方案越简单越不容易乱版。2.5 发送后的确认与错误处理邮件发送不是调用成功就等于投递成功这一点新手要尽早建立认知。sendmail返回时只代表发件服务器把信收下了后面的路由、投递、反垃圾过滤程序都看不到。真正能确认成功与否的只有两类反馈一类是发件服务器的同步异常另一类是收件人服务器返回的退信。代码层面需要捕获的常见异常包括SMTPAuthenticationError账号或授权码错误多半是授权码配置问题。SMTPRecipientsRefused收件人列表里有非法地址服务器直接拒绝。smtplib.SMTPServerDisconnected连接被服务器断开通常是发送太频繁触发风控。我在生产环境里的做法是给发送函数加一个简单的重试包装import logging import smtplib import time logging.basicConfig(levellogging.INFO, format%(asctime)s %(levelname)s %(message)s) def send_with_retry(msg, retries3, delay5): for attempt in range(1, retries 1): try: with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT) as server: server.login(USERNAME, AUTH_CODE) server.sendmail(USERNAME, [TO_ADDR], msg.as_string()) logging.info(邮件发送成功尝试次数%d, attempt) return True except (smtplib.SMTPServerDisconnected, OSError) as e: logging.warning(发送失败第 %d 次%s, attempt, e) if attempt retries: time.sleep(delay) return False注意重试只适合网络抖动和短时不可用的场景认证失败和收件人拒绝这类确定性错误不要重试重试只会浪费资源和加重封禁风险。3. 接收邮件IMAP 协议与读取路径发送解决了往外的路接收解决的是往内的路。自动处理入站邮件比如下载对账单、解析客户反馈、自动归档附件都需要从收件箱把邮件读回来。3.1 为什么我推荐用 IMAP 而不是 POP3接收邮件有两个主流协议POP3 和 IMAP把它们搞混会导致后面代码写歪。POP3 的设计思路是下载后删除客户端把邮件从服务器上拉下来然后邮件就从服务器消失了。这在只有一台电脑、一个客户端的年代没问题但对自动化程序来说是大坑——程序读走一封邮件你在手机上的邮件客户端就发现它不见了。IMAP 的设计思路是服务端同步邮件始终存放在服务器上客户端只相当于一个查看窗口可以标记已读、未读、移动文件夹但邮件本身不会消失。多个设备看到的是同一份数据这也意味着程序读走邮件后你的手机里还能看到它只是状态从未读变成了已读。自动化场景还有另一个关键好处IMAP 支持持久性的 UID 编号程序可以记住上次处理到哪封邮件下次只处理编号更大的新邮件。这在下一章会展开讲是自动化落地时最实用的一个能力。3.2 登录邮箱并扫描收件箱在调用之前需要先确认在邮箱服务商后台开通了 IMAP 服务获取授权码的方式和 SMTP 一样。QQ 邮箱开通后在设置里还能看到 IMAP/SMTP 服务状态顺手确认一下就好。与 SSH 服务器的套路一致IMAP 的默认端口是 993也需要走 SSLimport imaplib import email from email.header import decode_header IMAP_HOST imap.qq.com # 163 邮箱对应 imap.163.com IMAP_PORT 993 USERNAME os.getenv(SMTP_USER) AUTH_CODE os.getenv(SMTP_AUTH_CODE) server imaplib.IMAP4_SSL(IMAP_HOST, IMAP_PORT) server.login(USERNAME, AUTH_CODE) server.select(INBOX) # 搜索所有未读邮件 typ, data server.search(None, UNSEEN) mail_ids data[0].split() print(f未读邮件数量{len(mail_ids)})server.select(INBOX)表示进入收件箱文件夹。IMAP 的文件夹模型比 POP3 复杂除了 INBOX 还有已发送、已删除、草稿箱等内置目录企业邮箱还可能有自定义目录。在动手处理之前先server.list()看一眼有哪些目录能帮你避免在错误的文件夹上来回折腾。search(None, UNSEEN)返回的是服务端过滤后的邮件编号列表。IMAP 的搜索条件很丰富SINCE 01-Aug-2025按日期过滤FROM bossexample.com按发件人过滤SUBJECT 月度对账按标题过滤。多个条件可以组合比如search(None, UNSEEN, FROM, bossexample.com)。3.3 解析邮件正文邮件是一个树形结构很多初学者在拿到data之后会对里面的二进制内容直接decode(utf-8)期望一下拿到正文结果发现要么是空字符串要么是一堆乱码。原因在于 MIME 邮件是一棵嵌套树正文可能被拆成了纯文本、HTML、附件等多个部分。正确的解析姿势是拿到邮件对象后用msg.walk()遍历所有 MIME 节点def parse_msg(raw_email): msg email.message_from_bytes(raw_email) subject_parts decode_header(msg.get(Subject, )) subject for part, charset in subject_parts: if isinstance(part, bytes): subject part.decode(charset or utf-8, errorsreplace) else: subject part text_content html_content attachments [] for part in msg.walk(): content_type part.get_content_type() content_disposition str(part.get(Content-Disposition, )) if attachment in content_disposition: filename part.get_filename() if filename: filename_parts decode_header(filename) filename .join( p.decode(c or utf-8) if isinstance(p, bytes) else p for p, c in filename_parts ) attachments.append({ filename: filename, data: part.get_payload(decodeTrue) }) elif content_type text/plain: text_content decode_payload(part) elif content_type text/html: html_content decode_payload(part) return { subject: subject, from: msg.get(From, ), date: msg.get(Date, ), text: text_content, html: html_content, attachments: attachments } def decode_payload(part): payload part.get_payload(decodeTrue) if payload is None: return charset part.get_content_charset() or utf-8 return payload.decode(charset, errorsreplace)这段代码里有两个地方特别容易写错。第一个是get_payload(decodeTrue)与get_content_charset()搭配使用。传输过程中的邮件正文大多是 Base64 编码decodeTrue会把编码后的内容解成原始字节流然后你再用正确的字符集把它解码成字符串。如果字符集取错了中文就会乱码。第二个是Content-Disposition的判断。有的邮件客户端发送的附件没有标准attachment标识用inline内嵌图片这种也要识别。内嵌图片在 HTML 邮件里常见逻辑上不算业务附件你可以根据自己的需求决定是保存还是丢弃。3.4 自动下载邮件附件的完整片段解析完成后下载附件其实就是把字节流写到本地磁盘上def save_attachments(parsed, save_dirdownloads): os.makedirs(save_dir, exist_okTrue) for att in parsed[attachments]: # 做一层文件名清洗避免路径穿越 safe_name os.path.basename(att[filename]) file_path os.path.join(save_dir, safe_name) with open(file_path, wb) as f: f.write(att[data]) print(f已保存附件{file_path})这里我特意加了os.path.basename原因是攻击者可能通过构造文件名路径穿越把文件写到服务器的任意目录。自动化程序处理的是外部邮件安全上多留一个心眼总没错。另一个细节是不要信任附件里的可执行文件程序只负责把它落盘任何后续执行动作都不该由邮件内容自动触发。4. 踩坑实录授权码、编码和正文读取的三个教训这一节是我在自己项目的真实故障里总结出来的每一条都踩过、修过、验证过记录下来希望你能跳过这些坑。4.1 一直报 SMTPAuthenticationError问题出在授权码当时我一个新同事搭发送脚本始终报SMTPAuthenticationError我远程一看代码里的变量叫SMTP_PASSWORD传入的却是邮箱的登录密码。他把 QQ 邮箱的登录密码填进了授权码的位置当然过不了认证。授权码的正确使用方式我在前面写过去邮箱设置里开启 SMTP/IMAP 服务生成一把独立的授权码然后再写进配置文件。如果你已经生成过授权码又改了邮箱登录密码授权码一般是不会变的但有些服务商在检测到异地登录后会自动禁用旧的授权码这时旧授权码就会失效需要重新生成。另外排查这个报错时注意三个小细节授权码复制的时候别把空格带进去。账号要写成完整的邮箱地址不能只写用户名。环境变量里如果存了授权码确认没有被 shell 里的引号吞掉。4.2 中文主题乱码问题的根源是 RFC 2047 编码给邮件设置中文主题时不少人会直接写msg[Subject] 每日报表然后对方收到的主题显示一串?utf-8?B?XXXX?或者干脆乱码。根本原因是邮件头不像正文它默认是纯 ASCII 的。非 ASCII 文本必须经过 RFC 2047 编码转成?utf-8?B?Base64?这种形式邮件客户端接收到之后再解回中文。Python 的 email 库在大部分情况下会自动帮你做这件事但你手动构造时最好显式使用email.header.Headerfrom email.header import Header msg[Subject] Header(每日报表, utf-8)这样做的好处是编码过程完全可控不依赖具体版本的自动行为。我在解析收到的邮件时也遵守同样的规则用decode_header反向解码。如果发现某个主题解析出来是乱码多半就是编码字符集没取对或者对方发送端用了非标准编码这时候可以尝试降级用GB2312或GBK再解一次。4.3 读取邮件正文永远为空因为你没有遍历 MIME 树我见过一个很典型的 bug解析邮件时直接用msg.get_payload()发现纯文本邮件能正常读出来但 HTML 邮件读出来是 list 对象代码直接崩了。这就是没搞懂 MIME 树结构的表现。同一个RFC 2822格式的邮件可能包含multipart/alternative节点下面挂着text/plain和text/html两个节点也可能包含multipart/mixed节点下面挂正文和附件。get_payload()返回的可能是字符串也可能是节点列表——你需要遍历msg.walk()根据每个节点的content_type决定如何处理。再提醒一次解析出正文后不要立刻认为万事大吉。有些邮件既有纯文本又有 HTML推荐的做法是优先取纯文本纯文本不存在再取 HTML这样可以保证在纯文本环境比如终端工具、极简客户端里也能看到内容。4.4 免费邮箱的发送频率限制这个坑最容易被忽略免费邮箱的 SMTP 服务都有隐性频率限制。QQ 免费邮箱单次登录后连续发送大量邮件或者短时间内高频调用 SMTP 接口很容易触发风控轻则退信重则临时禁用 SMTP 功能。我建议控制在每分钟不超过 5 至 10 封。如果业务真的要群发几百上千封请换专业邮件发送服务而不是拿着自己的个人邮箱硬发。一个简单判断如果你在短时间内发出去的邮件大量进了垃圾箱不用怀疑就是频率和内容质量触发了反垃圾策略。5. 让邮件跑起来定时任务与自动处理的可靠性设计发送和接收都跑通了自动化才算开始。真正让人省心的是把整个流程挂起来定时执行并保证不丢邮件、不重复处理。5.1 三种定时方案怎么选Python 生态里定时任务方案不少我实际使用过这三个对比放在下面方案适用场景优点缺点schedule库轻量脚本进程内定时代码直观几行就能跑不支持 cron 表达式服务重启后任务丢失APScheduler中等规模自动化支持 cron 触发、持久化任务、多执行器配置稍重概念较多系统 crontab / 任务计划程序服务器运维级任务与操作系统绑定最稳定调试不便日志分散我个人推荐从APScheduler开始学它既能满足每天早上 8 点发报表这种 cron 场景又能在 Python 程序里统一管理异常和日志。schedule胜在简单但真要落地生产环境很多细节还是要自己补。5.2 用 APScheduler 实现每天早上八点发报表安装依赖pip install apscheduler然后写一个简单的入口脚本from apscheduler.schedulers.blocking import BlockingScheduler from apscheduler.triggers.cron import CronTrigger import logging logging.basicConfig(levellogging.INFO, format%(asctime)s %(message)s) def send_daily_report(): # 复用第 2 节的 send_with_retry 函数 send_with_retry(build_report_msg()) logging.info(每日报表任务执行完成) scheduler BlockingScheduler() scheduler.add_job( send_daily_report, triggerCronTrigger(hour8, minute0), iddaily_report, replace_existingTrue, ) scheduler.start()CronTrigger(hour8, minute0)就是每个自然日 8 点整执行一次。如果希望在每个工作日执行可以加day_of_weekmon-fri。这个任务进程必须常驻运行比如用nohup或 systemd 托管不要让它在某个终端里跑着就跑没了。有一个运维层面的细节定时任务不是配置完就一劳永逸。你要给它加一个成功/失败通知最简单的方式是在send_daily_report里执行成功记录日志失败时再往自己的另一个邮箱发一条告警——就当是监控着监控。5.3 处理邮件重复读取记录已处理 UID这是我做了很久之后才意识到的问题也是我认为最值得分享的实战经验之一。假设你写了一段程序每 5 分钟扫描一次未读邮件读到新邮件就解析、下载附件、存数据库。表面看没问题但一旦程序在处理到一半的时候崩溃重启后 IMAP 服务器上那封邮件仍然带着未读标记程序会再处理一次。如果这个动作是发一封回复邮件或者扣一次库存余额后果就很严重了。解决办法不是依赖未读标记而是记录每封邮件在 IMAP 服务器上的唯一编号 UID。大致逻辑import imaplib server imaplib.IMAP4_SSL(IMAP_HOST, IMAP_PORT) server.login(USERNAME, AUTH_CODE) server.select(INBOX) # 取出所有存在的 UID和本地已处理集合做差集 typ, data server.uid(search, None, ALL) uids data[0].split() processed_uids load_processed_uids() # 从本地文件或数据库读取 to_process [uid for uid in uids if uid not in processed_uids] for uid in to_process: typ, msg_data server.uid(fetch, uid, (RFC822)) parsed parse_msg(msg_data[0][1]) process_parsed(parsed) mark_processed(uid) # 先落库再标记已读 # 全部处理完后再统一把已读标记写上 for uid in to_process: server.uid(store, uid, FLAGS, \\Seen)顺序是关键先记录已处理再标记已读。因为标记已读本身也可能失败如果把标记已读放在前面一旦失败程序重启后就会重复处理。把已处理落库放在前面即使后续步骤失败重启后也会跳过这封邮件。处理记录存在哪小项目用一个文本文件就够了每行一个 UID大项目就放到 Redis 或数据库里还能顺带记录处理时间方便排查链路。5.4 日志、异常、重试自动化邮件的基本素养最后想强调的是自动化脚本必须能自我解释。你自己手工发邮件出了问题至少知道哪一步出了问题程序自动化发了几百封之后出了问题如果没有任何日志排查起来就是灾难。我建议每个邮件任务至少记录以下几类信息每次发送任务的触发时间、任务 ID。收件人列表的长度和摘要。发送成功、失败、重试的次数。接收任务扫描到的新邮件 UID 列表和处理结果。任何异常信息的完整堆栈。日志可以打在本地文件里也可以同时推到日志平台。我的习惯是本地mail_automation.log加上一个简单的摘要统计每天打开看一眼就知道而不是等用户来投诉了才去翻信息。另外一个容易忽略的环节是时区。你的服务器可能在东八区但收件方的服务器设置可能不同。发送定时报表时最好在邮件正文里显式标注时间所属时区避免收到的人误以为程序把时间算错了。写在最后的小建议我之所以说把背景场景想清楚再写代码是因为最近两年我越来越觉得自动收发邮件这件事难点从来不在 Python 语法而在稳定性和边界。你写的每一行代码都要回答三个问题如果今天这台服务器网络抽风了程序会怎样如果昨天程序崩了今天的数据还完整吗如果一封邮件触发了一个不该有的动作后果谁来承担我的答案很简单用日志回答前两个用范围约束回答第三个。你从最简单的每天早上八点发一封报表邮件开始跑上一周确认日志稳定、无重复、无乱码再逐步加入 HTML 美化、附件、收件解析、自动回复一步都不要跨太大。做了两年多的邮件自动化我最真实的体会是它不能替你判断消息是否重要但可以替你省下大量搬运消息的时间。当你半夜真的不用再爬起来的时候你就会理解这个项目值得做。
返回列表