You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Python批量发送邮件时附件文件名异常问题排查

批量发送邮件附件扩展名丢失问题排查与解决

我用Python脚本(作为Flask应用函数运行)给客户批量发邮件,遇到一个问题:部分客户反馈收到的附件是无扩展名的异常文件(比如邮箱为test@gmail.com时,附件名为test),但我自己测试收到的附件是正常的。附件文件名应该是「邮箱 - FS 带日期的文档编号.pdf」格式。我怀疑是MIMEBase相关代码导致的问题,相关代码如下:

import email, smtplib, ssl
import os
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText


def send_mail(): 

    subject = "YesNet Faktura"
    body = "Dzień dobry,\n\nprzesyłamy e-fakturę, którą znajdą Państwo w załączniku.\nDziękujemy za terminowe dokonanie płatności.\n\nPozdrawiamy,\nyesNET.pl\n\nTa wiadomość została wygenerowana automatycznie. Prosimy na nią nie odpowiadać. Zapytania można kierować na: EMAIL"
    sender_email = "EMAIL"
    receiver_email = ""
    password = "PASSWORD"
    # assign directory
    a = "uploads\ "
    a = a.strip()
    directory = 'uploads'
    # iterate over files in
    # that directory
    emailsLog = []

    if len(os.listdir(directory)) > 0:
        for filename in os.listdir(directory):
            f = os.path.join(directory, filename)
            # checking if it is a file
            print(f)
            print(filename)
            if os.path.isfile(f):
                filename = ""
                receiver_email = ""
                text = ""
                context = ""
                part = ""
                message = 0
                recievers = ""

                filename = f

                receiver_email = f.replace(a, "")
                recievers = receiver_email.split()
                # Create a multipart message and set headers

                # recipients = [receiver_email, "EMAIL"]

                recipients = ["EMAIL"]

                while recievers[1] != "FS":
                    recipients.append(recievers[0])
                    recievers.pop(0)


                message = MIMEMultipart()
                message["From"] = sender_email
                message["Subject"] = subject
                message["To"] = ", ".join(recipients)
                message["Bcc"] = ", ".join(recipients)  # Recommended for mass emails
                # Add body to email
                message.attach(MIMEText(body, "plain"))

                # Open PDF file in binary mode
                with open(filename, "rb") as attachment:
                    # Add file as application/octet-stream
                    # Email client can usually download this automatically as attachment
                    part = MIMEBase("application", "octet-stream")
                    part.set_payload(attachment.read())

                # Encode file in ASCII characters to send by email    
                encoders.encode_base64(part)

                # Add header as key/value pair to attachment part
                filename = filename.replace(a, "")
                part.add_header(
                    "Content-Disposition",
                    f"attachment; filename= {filename}",
                )

                message.attach(part)
                text = message.as_string()

                # Log in to server using secure context and send email
                context = ssl.create_default_context()
                with smtplib.SMTP_SSL("SMTP", 465, context=context) as server:
                    server.login(sender_email, password)
                    server.sendmail(sender_email, recipients, text)
                    print(receiver_email)
                    emailsLog.append(receiver_email)
        return emailsLog
    else:
        return False

我怀疑这段代码存在问题:

with open(filename, "rb") as attachment:
    # Add file as application/octet-stream
    # Email client can usually download this automatically as attachment
    part = MIMEBase("application", "octet-stream")
    part.set_payload(attachment.read())

排查方向与解决建议

1. 修复文件名头部的空格问题

代码中Content-Disposition头部的filename= {filename}里,等号后有个多余空格,部分邮件客户端(如Gmail)解析时会截断文件名或误判格式,导致扩展名丢失。去掉空格并给文件名加双引号,避免特殊字符解析异常:

part.add_header(
    "Content-Disposition",
    f"attachment; filename=\"{filename}\"",
)

2. 对文件名进行RFC2231编码

非ASCII字符在不同邮件客户端的解析规则不一致,用email.utils.encode_rfc2231编码文件名,确保跨客户端兼容性:

from email.utils import encode_rfc2231

# 替换原有的add_header部分
filename_encoded = encode_rfc2231(filename, 'utf-8')
part.add_header(
    "Content-Disposition",
    f"attachment; filename*=UTF-8''{filename_encoded}"
)

3. 使用正确的MIME类型处理PDF

PDF文件应指定application/pdf作为MIME类型,而非通用的octet-stream,这样邮件客户端能更准确识别文件类型,即使文件名异常也可能自动补全扩展名:

with open(filename, "rb") as attachment:
    part = MIMEBase("application", "pdf")
    part.set_payload(attachment.read())

更推荐使用MIMEApplication(专门处理二进制应用文件):

from email.mime.application import MIMEApplication

with open(filename, "rb") as attachment:
    part = MIMEApplication(attachment.read(), _subtype='pdf')

4. 修正文件名提取逻辑

原代码用字符串替换和split处理文件名的方式容易出错,比如文件名含空格时会破坏结构。改用os.path.basename直接获取上传目录下的文件名:

# 替换原有的filename赋值逻辑
filename = os.path.basename(f)
# 从文件名提取收件人邮箱(假设文件名格式为「邮箱 - FS ...pdf」)
receiver_email = filename.split(" - ")[0]

5. 多客户端测试验证

用Gmail、Outlook、本地邮件客户端等不同平台测试,确认问题是否仅出现在特定客户端,帮助定位是编码问题还是解析差异。


内容的提问来源于stack exchange,提问作者ItsJust PaperMan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 13:00:53