使用subprocess调用msmtp发送长邮件时异常:邮件仅含主题无正文
使用subprocess调用msmtp发送长邮件时异常:邮件仅含主题无正文
我之前碰到过一模一样的问题!这大概率是subprocess的管道缓冲或者进程等待机制在搞鬼——直接用shell管道(cat ... | msmtp -t)没问题,但用Python的subprocess手动处理时,很容易因为没处理好数据传输的完整性,导致长邮件正文丢失。
问题根源
当你用Popen启动msmtp后,如果不等它完全读取完所有输入内容就关闭stdin或者让脚本退出,msmtp可能还没来得及处理缓冲区里的正文数据就被中断了:短邮件内容少,缓冲区一次性就传完了,所以没问题;但长邮件内容多,数据还在缓冲区里没被msmtp读取,就被你关闭了stdin,自然只剩主题(主题在邮件头部,是先被读取的)。而shell的管道会自动处理数据传输的完整性,直到所有内容都发送给msmtp才会结束进程。
解决方案
推荐两种可靠的处理方式:
方式一:用communicate()方法(最省心)
communicate()是subprocess专门用来处理进程输入输出的方法,它会一次性把所有数据传给进程,然后等待进程完全结束,自动处理缓冲和进程同步问题,代码简洁又可靠:
import subprocess with open("email_file.txt", "rb") as email_file: # 同时捕获stderr方便排查错误 p = subprocess.Popen( ('msmtp', '-t'), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) stdout, stderr = p.communicate(email_file.read()) # 检查发送是否成功 if p.returncode != 0: print(f"邮件发送失败,错误信息:{stderr.decode('utf-8')}") else: print("邮件发送成功")
方式二:逐行写入时强制刷新缓冲+等待进程结束
如果一定要保持逐行写入的逻辑,那必须手动刷新缓冲区,并且等待msmtp进程处理完所有数据再结束:
import subprocess p = subprocess.Popen( ('msmtp', '-t'), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) with open("email_file.txt", "r", encoding='utf-8') as email_file_handle: for line in email_file_handle: p.stdin.write(line.encode('utf-8')) p.stdin.flush() # 强制把缓冲区的内容立刻发送给msmtp,避免滞留 p.stdin.close() p.wait() # 等待msmtp进程完全处理完所有数据并退出 # 检查结果 if p.returncode != 0: print(f"邮件发送失败,错误信息:{p.stderr.read().decode('utf-8')}")
为什么你原来的代码不行?
你之前的两种写法都没处理好进程同步:
- 第一种直接用
stdin=open("email_file.txt"),虽然文件对象会在进程结束后关闭,但如果脚本没等待msmtp进程完成就退出,可能导致文件被提前关闭; - 第二种逐行写入后直接关闭stdin,但没刷新缓冲,也没等待进程结束,msmtp还没读完所有正文就被终止了。
备注:内容来源于stack exchange,提问作者MikeyMike
相关产品推荐
相关产品推荐

