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

如何用Python的socket库发送邮件?多媒体附件编码问题求助

问题与解决方案

问题背景

我正在开发一个纯socket实现的Python邮件客户端,不依赖任何第三方邮件库。目前能正常发送.txt和.docx格式附件,但发送图片(.png/.jpg)、.pdf、.zip时,服务器无法识别文件编码信息,代码运行无报错。

现有代码

import base64
import os
import time

def get_attachment_content(file_path):
    with open(file_path, "rb") as file:
        file_data = file.read()
        base64_encoded = base64.b64encode(file_data)
        base64_encoded_str = base64_encoded.decode("utf-8")
        return base64_encoded_str

def guess_mime_type(file_path):
    # 部分常见文件格式的默认MIME类型
    mime_types = {
        '.txt': 'text/plain',
        '.pdf': 'application/pdf',
        '.jpg': 'image/jpeg',
        '.jpeg': 'image/jpeg',
        '.png': 'image/png',
        '.doc': 'application/msword',
        '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        '.zip': 'application/zip',
    }

    _, file_extension = os.path.splitext(file_path)

    return mime_types.get(file_extension.lower(), 'application/octet-stream')

def generate_email_content(to, from_, subject, message, attachment_file_list, cc_recipients):
    boundary = ' ------ ' + str(time.time()) + ' ------ '
    email_content = ''
    email_content += f'Content-Type: multipart/mixed; boundary="{boundary}"\r\n'
    email_content += f"Message-ID: {generate_message_id()}\r\n"
    email_content += f'Date: {time.strftime("%a, %d %b %Y %H:%M:%S +0700", time.localtime())}\r\n'
    email_content += f'User-Agent: VMS\r\n'
    email_content += f'To: {to}\r\n'
    
    if len(cc_recipients) > 1:
        email_content += f'CC: {", ".join(cc_recipients)}\r\n'
    
    email_content += f'From: {from_}\r\n'
    email_content += f'Subject: {subject}\r\n'
    email_content += f'\r\n{boundary}\r\nContent-Type: text/plain\r\n\r\n{message}\r\n'

    if attachment_file_list:
        for attachment_path in attachment_file_list:
            attachment_content = get_attachment_content(attachment_path)
            mime_type = guess_mime_type(attachment_path)
            email_content += f'\r\n{boundary}\r\nContent-Type: {mime_type}\r\n'
            email_content += f'Content-Transfer-Encoding: base64\r\n'
            email_content += f'Content-Disposition: attachment; filename= "{os.path.basename(attachment_path)}\"\r\n\r\n{attachment_content}\r\n'

    email_content += f'{boundary}\r\n'

    return email_content

# 假设generate_message_id是已实现的函数
def generate_message_id():
    return f"<{time.time()}@example.com>"

问题原因与修复方案

1. Base64编码未按规范换行

MIME协议要求Base64编码内容每行不超过76个字符,否则邮件服务器无法正确解析。原代码直接返回完整的Base64字符串,会导致长内容超出限制。

修改get_attachment_content函数:

def get_attachment_content(file_path):
    with open(file_path, "rb") as file:
        file_data = file.read()
        base64_encoded = base64.b64encode(file_data)
        base64_encoded_str = base64_encoded.decode("utf-8")
        # 按每行76字符分割
        return '\r\n'.join([base64_encoded_str[i:i+76] for i in range(0, len(base64_encoded_str), 76)])

2. MIME边界格式错误

原边界包含多余空格,会导致邮件客户端/服务器无法正确识别分隔符。边界应使用无空格的格式,且结束边界需要添加--后缀。

修改generate_email_content中的边界定义:

# 生成标准MIME边界,无多余空格
boundary = f'----Boundary_{int(time.time()*1000)}'
# ...
# 结束边界需要添加--
email_content += f'\r\n--{boundary}--\r\n'

3. Content-Disposition参数格式错误

filename参数前后的多余空格会导致文件名解析异常,应修正为标准格式:

email_content += f'Content-Disposition: attachment; filename="{os.path.basename(attachment_path)}"\r\n'

4. CC字段判断逻辑错误

原代码判断len(cc_recipients) > 1,会导致只有多个收件人时才添加CC字段,即使有1个CC收件人也会被忽略,修正为:

if cc_recipients:
    email_content += f'CC: {", ".join(cc_recipients)}\r\n'

5. MIME部分边界前缀缺失

所有MIME部分的边界前必须添加--,原代码遗漏了该前缀,导致服务器无法识别多部分内容的分隔。

修正后的完整代码

import base64
import os
import time

def get_attachment_content(file_path):
    with open(file_path, "rb") as file:
        file_data = file.read()
        base64_encoded = base64.b64encode(file_data)
        base64_encoded_str = base64_encoded.decode("utf-8")
        # 按MIME规范分割Base64内容为每行76字符
        return '\r\n'.join([base64_encoded_str[i:i+76] for i in range(0, len(base64_encoded_str), 76)])

def guess_mime_type(file_path):
    mime_types = {
        '.txt': 'text/plain',
        '.pdf': 'application/pdf',
        '.jpg': 'image/jpeg',
        '.jpeg': 'image/jpeg',
        '.png': 'image/png',
        '.doc': 'application/msword',
        '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        '.zip': 'application/zip',
    }
    _, file_extension = os.path.splitext(file_path)
    return mime_types.get(file_extension.lower(), 'application/octet-stream')

def generate_message_id():
    return f"<{int(time.time()*1000)}@your-domain.com>"

def generate_email_content(to, from_, subject, message, attachment_file_list, cc_recipients):
    # 生成符合规范的MIME边界
    boundary = f'----Boundary_{int(time.time()*1000)}'
    email_content = ''
    
    email_content += f'Content-Type: multipart/mixed; boundary="{boundary}"\r\n'
    email_content += f"Message-ID: {generate_message_id()}\r\n"
    email_content += f'Date: {time.strftime("%a, %d %b %Y %H:%M:%S +0700", time.localtime())}\r\n'
    email_content += f'User-Agent: VMS\r\n'
    email_content += f'To: {to}\r\n'
    
    # 修正CC字段判断逻辑
    if cc_recipients:
        email_content += f'CC: {", ".join(cc_recipients)}\r\n'
    
    email_content += f'From: {from_}\r\n'
    email_content += f'Subject: {subject}\r\n'
    
    # 头部与正文的分隔空行,然后添加第一个MIME部分
    email_content += f'\r\n--{boundary}\r\nContent-Type: text/plain\r\n\r\n{message}\r\n'

    if attachment_file_list:
        for attachment_path in attachment_file_list:
            attachment_content = get_attachment_content(attachment_path)
            mime_type = guess_mime_type(attachment_path)
            # 附件部分的边界前缀要加--
            email_content += f'\r\n--{boundary}\r\nContent-Type: {mime_type}\r\n'
            email_content += f'Content-Transfer-Encoding: base64\r\n'
            # 修正filename参数格式
            email_content += f'Content-Disposition: attachment; filename="{os.path.basename(attachment_path)}"\r\n\r\n{attachment_content}\r\n'

    # 结束边界,必须添加--后缀
    email_content += f'\r\n--{boundary}--\r\n'

    return email_content

内容的提问来源于stack exchange,提问作者Quốc Huy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 05:35:54