Python email邮件板块研究

本文深入介绍了MIME协议的基础知识,包括MIME协议的历史背景、基本结构、编码方式等内容,并提供了Python实现邮件读写的示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MIME协议

MIME(多用途互联网邮件扩展)指的是一系列电子邮件技术规范 ,主要包括 RFC 2045~2049

传统的电子邮件只能使用 ASCII 字符,导致非英文字符都不能在电子邮件中使用,而且电子邮件中不能插入二进制文件(图片/视频),也没有附件。

MIME 是对传统电子邮件的一个扩展,现在已经成为电子邮件实际上的标准。

MIME 标准浅析

下面是一封普通的电子邮件 Test Mail 的信件头:
在这里插入图片描述
从上面可以看出,这封信的发信人地址是 chen892704@163.com,收信人地址是 sqchen@coremail.cn ,邮件主题是 Test ,发送时间是 2018 年 7 月 19 日

从结构上,这封信分为三个部分:信件头 + 空行 + 信件体

MIME 对传统电子邮件的扩展表现在,它在信件头部分添加了几条语句,主要有:
在这里插入图片描述
这条语句标识了这封信使用了 MIME 规范,其中 1.0 的版本号是不变的,即使 MIME 本身已经升级好几次

在这里插入图片描述
这条语句表明了传递的信息类型,包含主要类型(primary type)和次要类型(subtype)两部分,两者之间用 “/” 分割

常见的简单类型有:
在这里插入图片描述
上例中出现的 multipart 类型是 MIME 邮件的重要内容,邮件体被分为多个段,每个段又包含段头和段体两部分,这两部分之间也以空行分隔

常见的 multipart 类型有三种:multipart/mixed、multipart/related 和 multipart/alternative,它们的层次关系可归纳为下图:

在这里插入图片描述
其中 boundary 表示不同信件内容的分割线,它通常是一个很长的随机字符串。

Test Mail 的邮件体:

在这里插入图片描述
从邮件体可以看出,这封邮件的内容包括四部分:

  • 纯文本:Hello world
  • 超文本:字体、字号等信息
  • 图片:cm.jpg
  • 附件:rfc2045.txt.pdf

由于电子邮件的传统格式不支持非 ASCII 编码和二进制数据,因此 MIME 规定了第三条语句:
在这里插入图片描述
这条语句指明了该块内容的编码转换方式,Content-Transfer-Encoding 的值有五种:7bit、8bit、binary、quote-printable 和 base64。其中 7bit 是缺省值,即不用转化的 ASCII 字符。

可以注意到在图片和附件块有一行特殊的语句:
在这里插入图片描述
Content-Disposition 是 MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件
Content-Disposition 可以控制用户请求所得的内容为一个文件的时候提供默认的文件名,文件直接在浏览器上显示或者在访问文件时弹出文件下载框。

content-disposition = "Content-Disposition" ":" disposition-type *( ";" disposition-parm ) 

本例中,cm.jpg 会直接在浏览器中显示(inline),而 rfc2045.txt.pdf 则会以附件形式下载(attachment)

Header

邮件头包含了发件人、收件人、主题、时 间、MIME版本、邮件内容的类型等重要信息。每条信息称为一个域,由域名后加“: ”和信息内容构成,可以是一行,较长的也可以占用多行。域的首行必须“顶头”写,即左边不能有空白字符(空格和制表符);续行则必须以空白字符打头,且第 一个空白字符不是信息本身固有的,解码时要过滤掉。

邮件头中不允许出现空行。有一些邮件不能被邮件客户端软件识别,显示的是原始码,就是因为首行是空行。

例如:

Date: Mon, 29 Jun 2009 18:39:03 +0800

From: "=?gb2312?B?26zQocHB?=" <gaoxl@legendsec.com>

To: "moreorless" <moreorless@live.cn>

Cc: "gxl0620" <gxl0620@163.com>

BCC: "=?gb2312?B?26zQocHB?=" <venus.oso@gmail.com>

Subject: attach

Message-ID: <200906291839032504254@legendsec.com>

X-mailer: Foxmail 6, 15, 201, 21 [cn]

Mime-Version: 1.0

在这里插入图片描述

body

在邮件体中,大致有如下一些域:
在这里插入图片描述
有的域除了值之外,还带有参数。值与参数、参数与参数之间以“;”分隔。参数名与参数值之间以“=”分隔。

邮件体包含邮件的内容,它的类型由邮件头的“Content-Type”域指出。常见的简单类型有text/plain(纯文本)和text/html(超文本)。

multipart类型,是MIME邮件的精髓。邮件体被分为多个段,每个段又包含段头和段体两部分,这两部分之间也以空行分隔。常见的multipart类型有三种:multipart/mixed, multipart/related和multipart/alternative。从它们的名称,不难推知这些类型各自的含义和用处。它们之间的层次关系可归纳为下图所示:

在这里插入图片描述
可以看出,如果在邮件中要添加附件,必须定义multipart/mixed段;如果存在内嵌资源,至少要定义multipart/related段;如果纯文本与超文本共存,至少要定义multipart/alternative段。

在这里插入图片描述

MIME编码

参考rfc2047,MIME Part Three:Message Header Extensions for Non-ASCII Text:http://tools.ietf.org/html/rfc2047

MIME编码的两种方法:

  1. 对邮件进行编码最初的原因是因为Internet上的很多网关不能正确传输8bit内码的字符,比如汉字等。编码的原理就是把8bit的内容转换成7bit的形式以能正确传输,在接收方收到之后,再将其还原成8bit的内容。

  2. MIME是“多用途网际邮件扩充协议”的缩写,在MIME协议之前,邮件的编码曾经有过UUENCODE等编码方式,但是由于MIME协议算法简单,并且易于扩展,现在已经成为邮件编码方式的主流,不仅是用来传输8 bit的字符,也可以用来传送二进制的文件,如邮件附件中的图像、音频等信息,而且扩展了很多基于MIME的应用。

从编码方式来说,MIME 定义了两种编码方法Base64与QP(Quote-Printable):

Base64

Base64是一种通用的方法,其原理很简单,就是把三个Byte的数据用4个Byte表示,这样,这四个Byte中,实际用到的都只有前面6 bit,这样就不存在只能传输7bit的字符的问题了。Base64的缩写一般是“B”。

Base64将输入的字符串或一段数据编码成只含有{‘A’-‘Z’, ‘a’-‘z’, ‘0’-‘9’, ‘+’, ‘/’}这64个字符的串,'=‘用于填充。其编码的方法是,将输入数据流每次取6bit,用此6bit的值(0-63)作为索引去查表,输出相应字符。这样,每3个字节将编码为4个字符(3×8 → 4×6);不满4个字符的以’='填充。 Base64的算法很简单,它将字符流顺序放入一个24位的缓冲区,缺字符的地方补零。 然后将缓冲区截断成为4个部分,高位在先,每个部分6位,用64个字符重新表示。如果输入只有一个或两个字节,那么输出将用等号“=”补足。这可以隔断附加的信息造成编码的混乱。

QP

另一种方法是QP(Quote-Printable)方法,通常缩写为“Q”方法,其原理是把一个8 bit 的字符用两个16进制数值表示,然后在前面加“=”。所以我们看到经过QP编码后的文件通常是这个样子:=B3=C2=BF=A1=C7=E5=A3=AC=C4=FA=BA=C3=A3=A1。

QP编码要求编码后每行不能超过76个字符。当超过这个限制时,将使用软换行,用”=”表示编码行的断行,后接CRLF。(76的限制包括”=”)。

“=” 等号被编码为”=3D”。

tab和空格出现在行尾时,需要被编码为”=09”(tab) “=20”(space)

编码格式:encoded-word = “=?” charset “?” encoding “?” encoded-text “?=”

编码信息有"=?“和”?=“括起来,”=?“后是字符集名称,再一个”?“后是编码方式,再一个”?"后是编码后的字符串。字符集和编码方式都不区分大小写。

字符集可以是任意系统支持的字符集(iso-8859-1、utf-8、gb2312、gbk、gb18030…)

在这里插入图片描述
编码方式有两种:"B"或"b"代表base64编码;"Q"或"q"代表QP编码。

Generally, an “encoded-word” is a sequence of printable ASCII characters that begins with “=?”, ends with “?=”, and has two "?"s in between. It specifies a character set and an encoding method, and also includes the original text encoded as graphic ASCII characters, according to the rules for that encoding method.

SMTP与MIME的关系

在这里插入图片描述
从上图可以看出发件人、收件人地址都出现了两次,一次在smtp命令中(SMTP email address),一次在邮件正文中(MIME email address)。需要注意的是:

  1. 邮件正文中可以包含发件人、收件人的别名,smtp命令中不可以
  2. 密送人的地址不一定会出现在邮件正文中。不同客户端实现不同。

使用python email库实现eml文件的读写

花了2个多小时查阅了下MIME协议和python email官方文档,最终给出两个比较完善的email读写函数:

import os
from email import encoders
from email.policy import default
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.application import MIMEApplication
from email import message_from_binary_file
from email.mime.multipart import MIMEMultipart

"""
邮件头:

X-QQ-XMRINFO: X-QQ-XMRINFO
X-QQ-XMAILINFO: X-QQ-XMAILINFO

From: "=?utf-8?B?防止隐私泄露?=" <防止隐私泄露@qq.com>  邮件来源
To: "=?utf-8?B?防止隐私泄露?=" <防止隐私泄露@qq.com> 邮件去向
Subject: =?utf-8?B?防止隐私泄露?=
 =?utf-8?B?防止隐私泄露?=  邮件主题
格式: =?{字符集}?{编码}?被编码文本?=

Mime-Version: 1.0: MIME协议版本,固定为1.0
Date: Wed, 10 Jan 2024 16:58:42 +0800
X-Priority: 3
Message-ID: <防止隐私泄露@qq.com>
X-QQ-MIME: TCMime 1.0 by Tencent
X-Mailer: QQMail 2.x
X-QQ-Mailer: QQMail 2.x
X-QQ-mid: 防止隐私泄露

Content-Type: multipart/mixed; : 内容类型,枚举multipart/mixed(附件), multipart/related(内嵌资源)和multipart/alternative(超文本共存)。
boundary="----=_NextPart_659E5C42_3AA6BB90_53C51419" : 声明分割符
Content-Transfer-Encoding: 8Bit : 编码格式

+---------------------------------multipart/mixed-----------------------------------+
|
|  +-----------------------multipart/related-----------------------+     
|  |                                                               |     
|  |  +---------------multipart/alternative---------------+ +-----+|    +------+
|  |  |                                                   | | 内  | |   | 附件  |
|  |  |  +------------------+   +------------------+      | | 嵌  | |   +------+
|  |  |  |    普通文本内容    |   |   超文本内容       |      | | 资  | |
|  |  |  +------------------+   +------------------+      | | 源  | |
|  |  +---------------------------------------------------+ +-----+ |
"""

"""
邮件正文(包含附件)

------=_NextPart_659E5C42_3AA6BB90_53C51419 : 相当于html中<p>标签开始
Content-Type: multipart/alternative; 下一段的类型
boundary="----=_NextPart_659E5C42_3AA6BB90_0DAC55D3"; 声明下一个缩进(段)的分割符

------=_NextPart_659E5C42_3AA6BB90_0DAC55D3 : 开始下一层嵌套
Content-Type: text/plain;  声明文本类型和字符集集和编码方式,用utf8读入用base64解码
charset="utf-8"
Content-Transfer-Encoding: base64

普通文本邮件正文

------=_NextPart_659E5C42_3AA6BB90_0DAC55D3
Content-Type: text/html; 
charset="utf-8"
Content-Transfer-Encoding: base64

html邮件正文

------=_NextPart_659E5C42_3AA6BB90_0DAC55D3-- 当前段结尾
Content-Type: application/octet-stream; 流
charset="utf-8";
name="=?utf-8?B?防止隐私泄露?="
Content-Disposition: attachment; filename="=?utf-8?B?防止隐私泄露?="
Content-Transfer-Encoding: base64
文件内容

------=_NextPart_659E5C42_3AA6BB90_53C51419--
"""


def reader(filepath):
    with open(filepath, "rb") as f:
        try:
            # 文件编码不一定全是utf-8,以2进制方法读
            message = message_from_binary_file(f, policy=default)
        except Exception as err:
            print(f"read {filepath} error: {str(err)}")
            return {}
        # multipart/mixed
        # 表示邮件中有html,图片,附件
        # 获取邮件主题、发件人、收件人等信息

        email_info = {
            "subject": message['subject'],
            "from": message['from'],
            "to": message['to'],
            # 附件
            "attachments": [],
            # 正文文件
            "content_files": [],
            # 文本正文
            "body": "",
            # 超文本正文
            "hyper_body": "",
        }

        # 解析邮件正文
        # 超过一种格式
        if message.is_multipart():
            attachments = []
            content_files = []
            for part in message.walk():
                # content-type 类型
                # content_type = part.get_content_maintype + "/" + part.get_content_subtype()
                content_type = part.get_content_type()
                # content-disposition 位置
                content_disposition = part.get_content_disposition() or ""
                # content_charset 字符集
                content_charset = part.get_content_charset()

                """
                当前层级的信息: multipart/mixed,,None
                当前层级的信息: multipart/alternative,,None
                当前层级的信息: text/plain,,utf-8
                当前层级的信息: text/html,,utf-8
                当前层级的信息: application/octet-stream,,None
                当前层级的信息: application/octet-stream,attachment,utf-8
                """
                # 只拿正文
                if "multipart" in content_type:
                    continue

                # 获取段内容
                payload = part.get_payload(decode=True)

                if content_type == "application/octet-stream":
                    # 文件
                    # 判断是否是附件
                    filename = part.get_filename()
                    if content_disposition:
                        attachments.append((filename, payload))
                    else:
                        # 正文嵌入文件
                        content_files.append((filename, payload))
                else:
                    payload = payload.decode(content_charset)
                    # 正文
                    if content_type == "text/plain":
                        email_info["body"] = payload
                    elif content_type == "text/html":
                        email_info["hyper_body"] = payload

            email_info["attachments"] = attachments
            email_info["content_files"] = content_files
        else:
            # 只有文本直接读取
            body = message.get_payload(decode=True).decode(message.get_content_charset())
            email_info["body"] = body

        return email_info


def writer(subject, from_addr, to_addr, body, hyper_body, attachments, content_files, eml_path):
    # 创建一个带附件的实例
    # 默认为mixed
    message = MIMEMultipart(_subtype="mixed")
    message['From'] = from_addr
    message['To'] = to_addr
    message['Subject'] = subject

    outer_message = MIMEMultipart(_subtype="related")
    # 邮件正文
    # html, text
    content_message = MIMEMultipart(_subtype="alternative")
    content_message.attach(MIMEText(body, "plain", "utf-8"))
    content_message.attach(MIMEText(hyper_body, "html", "utf-8"))

    # 包含正文
    outer_message.attach(content_message)

    # 添加正文文件
    for content_file in content_files:
        with open(content_file, "rb") as cf:
            part = MIMEImage(cf.read())
        encoders.encode_base64(part)
        part.add_header(_name="Content-ID", _value=os.path.basename(content_file))
        outer_message.attach(part)

    message.attach(outer_message)

    # 添加附件
    for attachment_path in attachments:
        with open(attachment_path, 'rb') as att_file:
            part = MIMEApplication(att_file.read())
            # b64编码
            encoders.encode_base64(part)
            part.set_charset("utf-8")
            # 设置位置为附件
            part.add_header('Content-Disposition', 'attachment',
                            filename=os.path.basename(attachment_path))
            # 追加入tree
            message.attach(part)

    # 保存.eml文件
    # charset = utf8是个好习惯
    with open(eml_path, 'w', encoding='utf-8') as outfile:
        outfile.write(message.as_string())


if __name__ == '__main__':
    data = reader("./带有图片.eml")
    writer(
        data["subject"],
        from_addr=data["from"],
        to_addr=data["to"],
        body=data["body"],
        hyper_body=data["hyper_body"],
        attachments=["./xxxx.rar"],
        content_files=["./xxx.png"],
        eml_path="./out.eml"
    )

python使用SMTP客户端发送邮件

发送普通邮件

import smtplib
from email.mime.text import MIMEText
from email.header import Header

# 发送方邮箱
msg_from = ''
# 授权码
passwd = ''
# 收件人邮箱
msg_to = ['','']  # 收件人邮箱

# 邮件主题
subject = "修改密码"

# 邮件内容
content = '''
点击下方链接修改密码:
<a href="https://www.baidu.com">修改密码</a>
'''

# 实例化(参数:邮件内容,文本格式,编码)
# msg = MIMEText(content, 'html', 'utf-8')
# 默认为文本
msg = MIMEText(content)

# 邮件主题
msg['Subject'] = Header(subject, 'utf-8')

# 发件人
msg['From'] = Header("Generalzy", 'utf-8')

# 接收人
msg['To'] = Header("爱吃饺子的西瓜", 'utf-8')

# 通过ssl方式发送,服务器地址,端口
s = smtplib.SMTP_SSL("smtp.qq.com", 465)

try:
    # 登录
    s.login(msg_from, passwd)

    # 发送邮件:发送方,收件方,要发送的消息
    s.sendmail(msg_from, msg_to, msg.as_string())

    print('成功')

except Exception as e:
    print(e)
finally:
    s.quit()

发送html格式邮件

import smtplib
from email.mime.text import MIMEText
from email.header import Header

# 发送方邮箱
msg_from = ''
# 授权码
passwd = ''
# 收件人邮箱
msg_to = ['','']  # 收件人邮箱

# 邮件主题
subject = "修改密码"

# 邮件内容
content = '''
点击下方链接修改密码:
<a href="https://www.baidu.com">修改密码</a>
'''

# 实例化(参数:邮件内容,文本格式,编码)
msg = MIMEText(content, 'html', 'utf-8')

# 邮件主题
msg['Subject'] = Header(subject, 'utf-8')

# 发件人
msg['From'] = Header("Generalzy", 'utf-8')

# 接收人
msg['To'] = Header("爱吃饺子的西瓜", 'utf-8')

# 通过ssl方式发送,服务器地址,端口
s = smtplib.SMTP_SSL("smtp.qq.com", 465)

try:
    # 登录
    s.login(msg_from, passwd)

    # 发送邮件:发送方,收件方,要发送的消息
    s.sendmail(msg_from, msg_to, msg.as_string())

    print('成功')

except Exception as e:
    print(e)
finally:
    s.quit()

发送带附件的邮件(一般不用)

import smtplib
from email.mime.text import MIMEText
from email.header import Header
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email import  encoders

msg_from = 'xxxxx@qq.com'  # 发送方邮箱
passwd = '***'				# 授权码
msg_to = ['xxxxx@qq.com']  # 收件人邮箱

subject = "邮件标题" 

# 创建一个带附件的实例
msg = MIMEMultipart()
# 放入邮件主题
msg['Subject'] = subject
msg['From'] = msg_from

# 邮件正文内容
msg.attach(MIMEText('Python 邮件发送测试……', 'plain', 'utf-8'))

# 附件1
att1 = MIMEText(open('test.txt', 'rb').read(), 'base64', 'utf-8')
att1["Content-Type"] = 'application/octet-stream'
# 这里的filename可以任意写,写什么名字,邮件中显示什么名字
att1["Content-Disposition"] = 'attachment; filename="test.txt"'
msg.attach(att1)

# 附件2,
with open('test.png', 'rb') as f:
    # 设置附件的MIME和文件名,这里是png类型:
    mime = MIMEBase('image', 'png', filename='test.png')
    # 加上必要的头信息:
    mime.add_header('Content-Disposition', 'attachment', filename='test.png')
    mime.add_header('Content-ID', '<0>')
    mime.add_header('X-Attachment-Id', '0')
    # 把附件的内容读进来:
    mime.set_payload(f.read())
    # 用Base64编码:
    encoders.encode_base64(mime)
    # 添加到MIMEMultipart:
    msg.attach(mime)
    
# 附件3,图片格式
fp = open('test.png', 'rb')
msgImage = MIMEImage(fp.read())
fp.close()
# 定义图片 ID,在 HTML 文本中引用
msgImage.add_header('Content-ID', '<image1>')
msg.attach(msgImage)
try:
    # 通过ssl方式发送
    s = smtplib.SMTP_SSL("smtp.qq.com", 465)
    # 登录到邮箱
    s.login(msg_from, passwd)
    # 发送邮件:发送方,收件方,要发送的消息
    s.sendmail(msg_from, msg_to, msg.as_string())
    print('成功')
except s.SMTPException as e:
    print(e)
finally:
    s.quit()

Django发送邮件

  1. setting配置

根据django的EmailBackend即可

from django.core.mail.backends.smtp import EmailBackend

class EmailBackend:
    def __init__(self, host=None, port=None, username=None, password=None,
                 use_tls=None, fail_silently=False, use_ssl=None, timeout=None,
                 ssl_keyfile=None, ssl_certfile=None,
                 **kwargs):
        super().__init__(fail_silently=fail_silently)
        self.host = host or settings.EMAIL_HOST
        self.port = port or settings.EMAIL_PORT
        self.username = settings.EMAIL_HOST_USER if username is None else username
        self.password = settings.EMAIL_HOST_PASSWORD if password is None else password
        self.use_tls = settings.EMAIL_USE_TLS if use_tls is None else use_tls
        self.use_ssl = settings.EMAIL_USE_SSL if use_ssl is None else use_ssl
        self.timeout = settings.EMAIL_TIMEOUT if timeout is None else timeout
        self.ssl_keyfile = settings.EMAIL_SSL_KEYFILE if ssl_keyfile is None else ssl_keyfile
        self.ssl_certfile = settings.EMAIL_SSL_CERTFILE if ssl_certfile is None else ssl_certfile
        if self.use_ssl and self.use_tls:
            raise ValueError(
                "EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive, so only set "
                "one of those settings to True.")
        self.connection = None
        self._lock = threading.RLock()
# 不配也可以,默认
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'

EMAIL_HOST = 'smtp.qq.com'

EMAIL_PORT = 465

EMAIL_HOST_USER = '1612613082@qq.com'

EMAIL_HOST_PASSWORD = '授权码'  # 密码

EMAIL_USE_SSL = True  # 使用ssl
# EMAIL_USE_TLS = False 使用tls
# EMAIL_USE_SSL 和 EMAIL_USE_TLS 是互斥的,即只能有一个为 True

# 配置邮件头基本信息
DEFAULT_FROM_EMAIL = '乾坤it <1612613082@qq.com>'
  1. 视图使用
from django.core.mail import send_mail
import threading
from mybbs import settings

html = """
    <p>点击下方链接修改密码</p>
    <a href='https://www.baidu.com'>修改密码</a>
    """
    
send_mail(subject='修改密码', message="点击修改密码", from_email=settings.DEFAULT_FROM_EMAIL,
                    recipient_list=['收件人列表'], html_message=html)
  1. 一次性发多封邮件
from django.core.mail import send_mass_mail

# message参数与发送单份邮件相同
message1 = ('第一封邮件标题', '这是邮件内容', 'from@example.com', ['first@example.com', 'other@example.com'])
message2 = ('第二封邮件标题', '这是邮件内容', 'from@example.com', ['second@test.com'])

'''
fail_silently: (可选)布尔值。为 False 时, send_mail 会抛出 smtplib.SMTPException 异常。
'''
send_mass_mail((message1, message2), fail_silently=False)
'''
send_mail 每次发邮件都会建立一个连接,发多封邮件时建立多个连接。 
send_mass_mail 是建立单个连接发送多封邮件。
所以一次性发送多封邮件时 send_mass_mail 要优于 send_mail。
'''

各大邮箱smtp服务器及端口

新浪免费邮箱
POP3:pop.sina.com
SMTP:smtp.sina.com
SMTP端口号:25
新浪VIP邮箱
POP3:pop3.vip.sina.com
SMTP:smtp.vip.sina.com
SMTP端口号:25
新浪企业邮箱
POP3:pop.sina.com
SMTP:smtp.sina.com
SMTP端口号:25
雅虎邮箱
POP3:pop.mail.yahoo.cn
SMTP:smtp.mail.yahoo.cn
SMTP端口号:25
搜狐邮箱
POP3:pop3.sohu.com
SMTP:smtp.sohu.com
SMTP端口号:25
TOM邮箱
POP3:pop.tom.com
SMTP:smtp.tom.com
SMTP端口号:25
Gmail邮箱
POP3:pop.gmail.com
SMTP:smtp.gmail.com
SMTP端口号:58725
QQ邮箱
POP3:pop.exmail.qq.com
SMTP:smtp.exmail.qq.com
SMTP端口号:25
263邮箱
域名:263.net
POP3:263.net
SMTP:smtp.263.net
SMTP端口号:25
域名:x263.net
POP3:pop.x263.net
SMTP:smtp.x263.net
SMTP端口号:25
域名:263.net.cn
POP3:263.net.cn
SMTP:263.net.cn
SMTP端口号:25
域名:炫我型
POP3:pop.263xmail.com
SMTP:smtp.263xmail.com
SMTP端口号:25
21CN 免费邮箱
POP3:pop.21cn.com
SMTP:smtp.21cn.com
IMAP:imap.21cn.com
SMTP端口号:25
21CN 经济邮邮箱
POP3:pop.21cn.com
SMTP:smtp.21cn.com
SMTP端口号:25
21CN 商务邮邮箱
POP3:pop.21cn.net
SMTP:smtp.21cn.net
SMTP端口号:25
21CN 快感邮箱
POP3:vip.21cn.com
SMTP:vip.21cn.com
SMTP端口号:25
21CN Y邮箱
POP3:pop.y.vip.21cn.com
SMTP:smtp.y.vip.21cn.com
SMTP端口号:25
中华网任我邮邮箱
POP3:rwpop.china.com
SMTP:rwsmtp.china.com
SMTP端口号:25
中华网时尚、商务邮箱
POP3:pop.china.com
SMTP:smtp.china.com
SMTP端口号:25

qq邮箱配置smtp

在这里插入图片描述
在这里插入图片描述
开启smtp服务,生成授权码。

扩展golang操作eml文件

简介:一个简单且强大的邮件发送库。

安装:go get gopkg.in/gomail.v2

示例代码:

package main

import (
    "gopkg.in/gomail.v2"
)

func main() {
    m := gomail.NewMessage()
    m.SetHeader("From", "sender@example.com")
    m.SetHeader("To", "recipient@example.com")
    m.SetHeader("Subject", "Hello!")
    m.SetBody("text/plain", "Hello, this is a test email!")

    d := gomail.NewDialer("smtp.example.com", 587, "user", "password")

    if err := d.DialAndSend(m); err != nil {
        panic(err)
    }
}

mail:

简介:一个用于解析和生成MIME邮件的库。
安装:go get github.com/emersion/go-imap

示例代码:

package main

import (
    "fmt"
    "log"
    "net/mail"
    "strings"
)

func main() {
    rawEmail := `From: sender@example.com
To: recipient@example.com
Subject: Hello!

Hello, this is a test email!`

     msg, err := mail.ReadMessage(strings.NewReader(rawEmail))
     if err != nil {
         log.Fatal(err)
     }

     fmt.Println("From:", msg.Header.Get("From"))
     fmt.Println("To:", msg.Header.Get("To"))
     fmt.Println("Subject:", msg.Header.Get("Subject"))

     body, err := io.ReadAll(msg.Body)
     if err != nil {
         log.Fatal(err)
     }

     fmt.Println("Body:", string(body))
 }

go-imap:
简介:一个用于与IMAP服务器交互的库。
安装:go get github.com/emersion/go-imap
示例代码:

package main

import (
    "log"
    "github.com/emersion/go-imap"
    "github.com/emersion/go-imap/client"
)

func main() {
    // Connect to server
    c, err := client.DialTLS("imap.example.com:993", nil)
    if err != nil {
        log.Fatal(err)
    }
    log.Println("Connected")

    // Login
    if err := c.Login("username", "password"); err != nil {
        log.Fatal(err)
    }
    log.Println("Logged in")

    // Select INBOX
    mbox, err := c.Select("INBOX", false)
    if err != nil {
        log.Fatal(err)
    }
    log.Println("Flags for INBOX:", mbox.Flags)

    // Logout
    if err := c.Logout(); err != nil {
        log.Fatal(err)
    }
}

这些包可以帮助你在Go语言中方便地处理电子邮件的读写操作。根据你的具体需求选择合适的包进行使用。

如何用这些库去设置附件到.eml文件中

要在Go语言中使用这些库设置附件并生成.eml文件,可以使用gomail库。以下是一个示例代码,展示如何创建一个包含附件的电子邮件并将其保存为.eml文件:

package main

import (
    "gopkg.in/gomail.v2"
    "os"
)

func main() {
    // 创建一个新的邮件消息
    m := gomail.NewMessage()
    m.SetHeader("From", "sender@example.com")
    m.SetHeader("To", "recipient@example.com")
    m.SetHeader("Subject", "Hello with attachment!")
    m.SetBody("text/plain", "Hello, this is a test email with an attachment!")

    // 添加附件
    m.Attach("/path/to/your/file.txt")

    // 创建一个文件来保存.eml内容
    f, err := os.Create("email.eml")
    if err != nil {
        panic(err)
    }
    defer f.Close()

    // 将邮件内容写入文件
    _, err = m.WriteTo(f)
    if err != nil {
        panic(err)
    }

    println("Email saved to email.eml")
}

代码解释

创建邮件消息:

m := gomail.NewMessage()
m.SetHeader("From", "sender@example.com")
m.SetHeader("To", "recipient@example.com")
m.SetHeader("Subject", "Hello with attachment!")
m.SetBody("text/plain", "Hello, this is a test email with an attachment!")

添加附件:

m.Attach("/path/to/your/file.txt")

创建并写入.eml文件:

f, err := os.Create("email.eml")
if err != nil {
    panic(err)
}
defer f.Close()

_, err = m.WriteTo(f)
if err != nil {
    panic(err)
}

通过以上步骤,你可以创建一个包含附件的电子邮件并将其保存为.eml文件。请确保将/path/to/your/file.txt替换为你实际的附件文件路径。

gomail.v2 如何读取.eml文件呢?

gomail.v2库主要用于创建和发送电子邮件,而不是用于读取.eml文件。如果你需要读取.eml文件,可以使用net/mail包来解析邮件内容。以下是一个示例代码,展示如何使用net/mail包读取.eml文件并提取附件信息:

使用net/mail包读取.eml文件

package main

import (
    "fmt"
    "io"
    "log"
    "mime"
    "mime/multipart"
    "net/mail"
    "os"
    "strings"
)

func main() {
    // 打开.eml文件
    file, err := os.Open("email.eml")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    // 读取邮件
    msg, err := mail.ReadMessage(file)
    if err != nil {
        log.Fatal(err)
    }

    // 打印邮件头信息
    fmt.Println("From:", msg.Header.Get("From"))
    fmt.Println("To:", msg.Header.Get("To"))
    fmt.Println("Subject:", msg.Header.Get("Subject"))

    // 解析邮件主体
    mediaType, params, err := mime.ParseMediaType(msg.Header.Get("Content-Type"))
    if err != nil {
        log.Fatal(err)
    }

    if strings.HasPrefix(mediaType, "multipart/") {
        multipartReader := multipart.NewReader(msg.Body, params["boundary"])
        for {
            part, err := multipartReader.NextPart()
            if err == io.EOF {
                break
            }
            if err != nil {
                log.Fatal(err)
            }

            // 打印每个部分的头信息
            fmt.Println("Part Header:", part.Header)

            // 检查是否为附件
            if part.FileName() != "" {
                fmt.Println("Found attachment:", part.FileName())
                // 读取附件内容
                attachmentData, err := io.ReadAll(part)
                if err != nil {
                    log.Fatal(err)
                }
                // 保存附件到文件
                err = os.WriteFile(part.FileName(), attachmentData, 0644)
                if err != nil {
                    log.Fatal(err)
                }
                fmt.Println("Attachment saved:", part.FileName())
            } else {
                // 读取邮件正文
                body, err := io.ReadAll(part)
                if err != nil {
                    log.Fatal(err)
                }
                fmt.Println("Body:", string(body))
            }
        }
    } else {
        // 处理非multipart邮件
        body, err := io.ReadAll(msg.Body)
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println("Body:", string(body))
    }
}

代码解释

打开并读取.eml文件:

file, err := os.Open("email.eml")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

msg, err := mail.ReadMessage(file)
if err != nil {
    log.Fatal(err)
}

打印邮件头信息:

fmt.Println("From:", msg.Header.Get("From"))
fmt.Println("To:", msg.Header.Get("To"))
fmt.Println("Subject:", msg.Header.Get("Subject"))

解析邮件主体:

mediaType, params, err := mime.ParseMediaType(msg.Header.Get("Content-Type"))
if err != nil {
    log.Fatal(err)
}

if strings.HasPrefix(mediaType, "multipart/") {
    multipartReader := multipart.NewReader(msg.Body, params["boundary"])
    for {
        part, err := multipartReader.NextPart()
        if err == io.EOF {
            break
        }
        if err != nil {
            log.Fatal(err)
        }

        // 打印每个部分的头信息
        fmt.Println("Part Header:", part.Header)

        // 检查是否为附件
        if part.FileName() != "" {
            fmt.Println("Found attachment:", part.FileName())
            // 读取附件内容
            attachmentData, err := io.ReadAll(part)
            if err != nil {
                log.Fatal(err)
            }
            // 保存附件到文件
            err = os.WriteFile(part.FileName(), attachmentData, 0644)
            if err != nil {
                log.Fatal(err)
            }
            fmt.Println("Attachment saved:", part.FileName())
        } else {
            // 读取邮件正文
            body, err := io.ReadAll(part)
            if err != nil {
                log.Fatal(err)
            }
            fmt.Println("Body:", string(body))
        }
    }
} else {
    // 处理非multipart邮件
    body, err := io.ReadAll(msg.Body)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Body:", string(body))
}

通过以上步骤,你可以读取.eml文件并提取附件信息。请确保将email.eml替换为你实际的.eml文件路径。

二次封装goemail库

在电子邮件的上下文中,各个字段有特定的含义和用途。以下是对这些字段的详细解释:

Sender (*mail.Address)

解释:发送者的电子邮件地址。这个字段表示实际发送邮件的人的地址,通常用于标识邮件的发件人。
示例:Sender: “John Doe john.doe@example.com
From ([]*mail.Address)

解释:发件人的电子邮件地址列表。这个字段表示邮件的作者,可以包含多个地址。
示例:From: “John Doe john.doe@example.com”, “Jane Doe jane.doe@example.com
ReplyTo ([]*mail.Address)

解释:回复地址列表。这个字段指定了收件人回复邮件时应该使用的地址。如果未设置,回复将发送到 From 地址。
示例:ReplyTo: “Support support@example.com
To ([]*mail.Address)

解释:主要收件人的电子邮件地址列表。这个字段包含邮件的主要接收者。
示例:To: “Alice alice@example.com”, “Bob bob@example.com
Cc ([]*mail.Address)

解释:抄送(Carbon Copy)收件人的电子邮件地址列表。这个字段包含邮件的抄送接收者,所有收件人都能看到这些地址。
示例:Cc: “Charlie charlie@example.com”, “Dave dave@example.com
Bcc ([]*mail.Address)

解释:密送(Blind Carbon Copy)收件人的电子邮件地址列表。这个字段包含邮件的密送接收者,其他收件人看不到这些地址。
示例:Bcc: “Eve eve@example.com”, “Frank frank@example.com
Date (time.Time)

解释:邮件发送的日期和时间。
示例:Date: Mon, 12 Sep 2024 10:00:00 +0000
MessageID (string)

解释:邮件的唯一标识符。通常由邮件服务器生成,用于标识和追踪邮件。
示例:MessageID: “unique-id@example.com
InReplyTo ([]string)

解释:引用的邮件标识符列表。这个字段用于标识当前邮件是对哪些邮件的回复。
示例:InReplyTo: “previous-message-id@example.com
References ([]string)

解释:引用的邮件标识符列表。这个字段用于标识当前邮件引用了哪些邮件,通常用于邮件线程。
示例:References: “first-message-id@example.com”, “second-message-id@example.com

特别说明:ReplyTo 和 To
ReplyTo:指定了收件人回复邮件时应该使用的地址。如果 ReplyTo 字段存在,收件人点击“回复”按钮时,邮件将发送到 ReplyTo 地址,而不是 From 地址。这在需要将回复邮件发送到不同于发件人的地址时非常有用,例如支持邮箱或特定的联系人。

示例:如果 From 是 john.doe@example.com,但希望回复发送到 support@example.com,则设置 ReplyTo 为 support@example.com。
To:指定了邮件的主要收件人。这个字段包含邮件的直接接收者,所有收件人都能看到这些地址。

示例:如果邮件是发送给 alice@example.com 和 bob@example.com,则 To 字段包含这两个地址。

假设你要发送一封邮件,发件人是 john.doe@example.com,主要收件人是 alice@example.com 和 bob@example.com,抄送给 charlie@example.com,密送给 eve@example.com,并希望回复发送到 support@example.com,则邮件头可能如下:

plaintext
Sender: “John Doe john.doe@example.com
From: “John Doe john.doe@example.com
ReplyTo: “Support support@example.com
To: “Alice alice@example.com”, “Bob bob@example.com
Cc: “Charlie charlie@example.com
Bcc: “Eve eve@example.com
Date: Mon, 12 Sep 2024 10:00:00 +0000
MessageID: “unique-id@example.com

package mailx

import (
	"bytes"
	"email/helper"
	"github.com/DusanKasan/parsemail"
	"gopkg.in/gomail.v2"
	"io"
	"os"
	"regexp"
	"strings"
)

const (
	// ContentText 纯文本内容,不包含任何格式
	ContentText = "text/plain"
	// ContentHtml HTML格式的文本内容,可以包含HTML标签和样式
	ContentHtml = "text/html"
	// ContentMixed 混合内容类型,通常用于包含多个部分(如文本和附件)
	ContentMixed = "multipart/mixed"
	// ContentAlternative 包含多个版本的同一内容,例如纯文本和HTML版本,邮件客户端可以选择显示其中一种
	ContentAlternative = "multipart/alternative"
	// ContentRelated 包含相关内容,通常用于HTML邮件,其中包含内嵌的图像或样式
	ContentRelated = "multipart/related"
	// ContentParallel 包含并行显示的多个部分,通常用于多媒体邮件
	ContentParallel = "multipart/parallel"
	// ContentOctet 二进制数据,通常用于附件
	ContentOctet = "application/octet-stream"
	// ContentPdf PDF文档
	ContentPdf = "application/pdf"
	// ContentJpeg JPEG格式的图像
	ContentJpeg = "image/jpeg"
	// ContentPng PNG格式的图像
	ContentPng = "image/png"
	// ContentMpeg MPEG音频文件
	ContentMpeg = "audio/mpeg"
	// ContentMp4 MP4视频文件
	ContentMp4 = "video/mp4"
)

type MailBuilder struct {
	m *gomail.Message
}

func Builder() *MailBuilder {
	return &MailBuilder{m: gomail.NewMessage()}
}

func (builder *MailBuilder) WithHeader(headerKey string, headerVal ...string) *MailBuilder {
	builder.m.SetHeader(headerKey, headerVal...)
	return builder
}

func (builder *MailBuilder) WithHeaders(headers map[string][]string) *MailBuilder {
	for headKey, headVals := range headers {
		builder.m.SetHeader(headKey, headVals...)
	}
	return builder
}

func (builder *MailBuilder) WithBody(mimeType string, content string) *MailBuilder {
	builder.m.SetBody(mimeType, content)
	return builder
}

func (builder *MailBuilder) WithAttachment(filePath string, filename string) *MailBuilder {
	if filename == "" {
		builder.m.Attach(filePath)
	} else {
		builder.m.Attach(filePath, gomail.Rename(filename))
	}

	return builder
}

func (builder *MailBuilder) WithPicture(filePath string) *MailBuilder {
	builder.m.Embed(filePath)
	return builder
}

func (builder *MailBuilder) Save(dstPath string) error {
	f, err := os.Create(dstPath)
	if err != nil {
		return err
	}

	defer f.Close()

	_, err = builder.m.WriteTo(f)
	if err != nil {
		return err
	}
	return nil
}

type MailParser struct {
	parsemail.Email
}

var attachmentReg = regexp.MustCompile(`(?i)Content-Disposition:.*?filename="([^"]+)"`)

func Parser(filePath string) (*MailParser, error) {
	content, err := os.ReadFile(filePath)
	if err != nil {
		return nil, err
	}

	return ParseFromReader(bytes.NewReader(content))
}

func ParseFromFile(filePath string) (*MailParser, error) {
	return Parser(filePath)
}

func (parser *MailParser) Body() string {
	var body string

	if parser.HTMLBody != "" {
		body = parser.HTMLBody
	}
	if parser.TextBody != "" {
		body = parser.TextBody
	}

	if body == "" {
		return body
	}

	return helper.B64Decode(body)
}

func (parser *MailParser) DateString() string {
	return parser.Date.String()
}

func (parser *MailParser) FromString() []string {
	froms := make([]string, 0, len(parser.From))
	for _, from := range parser.From {
		froms = append(froms, from.String())
	}
	return froms
}

func (parser *MailParser) SubjectString() string {
	return parser.Subject
}

func (parser *MailParser) ToString() []string {
	tos := make([]string, 0, len(parser.To))
	for _, to := range parser.To {
		tos = append(tos, to.String())
	}
	return tos
}

func (parser *MailParser) HasAttachments() bool {
	if parser.Attachments == nil || len(parser.Attachments) == 0 {
		return false
	}
	return true
}

func (parser *MailParser) HasEmbed() bool {
	if parser.EmbeddedFiles == nil || len(parser.EmbeddedFiles) == 0 {
		return false
	}
	return true
}

// ScanBodyAttachments 用于扫描Body中没有正确解析到Attachments的文件信息
//
// 如果邮件内容是从字符串加载则会有可能出现这个情况
//
// 例如,"开户行 --boundary-- Content-Disposition: attachment; filename="投标保证金付款凭证.png"
//
// 可以提取到“投标保证金付款凭证.png”,此操作慎用,建议在判断HasAttachments为false后调用
func (parser *MailParser) ScanBodyAttachments() string {
	s := attachmentReg.FindStringSubmatch(parser.Body())
	l := len(s)
	if l > 0 {
		return s[len(s)-1]
	}

	return ""
}

func ParseFromReader(reader io.Reader) (*MailParser, error) {
	e, err := parsemail.Parse(reader)
	if err != nil {
		return nil, err
	}
	return &MailParser{Email: e}, nil
}

func ParseFromBytes(content []byte) (*MailParser, error) {
	return ParseFromReader(bytes.NewReader(content))
}

func ParseFromString(content string) (*MailParser, error) {
	return ParseFromReader(strings.NewReader(content))
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Generalzy

文章对您有帮助,倍感荣幸

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值