Mrli
别装作很努力,
因为结局不会陪你演戏。
Contacts:
QQ博客园

Python中邮件的发送

2020/11/12 flask Python
Word count: 3,822 | Reading time: 17min

Python普通的邮件发送

需要一个安全的连接,例如SSL,因此接下来我们会使用SSL的方式去登录,但是在那之前,我们需要做一些准备,打开qq邮箱,点击设置->账户,找到POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务,开启IMAP/SMTP服务,然后根据要求使用手机发送到指定号码,获取授权码,这个授权码就是你接下来登录要使用的密码.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from email.mime.text import MIMEText
import smtplib

_user = '1063052964@qq.com'
_pwd = 'pykhotuhghdjbeci'
_to = '2035420834@qq.com'
msg = MIMEText(mail_body) //MIMEText中_subtype默认为plain,html格式,只需改这个参数
msg["Subject"] = '来自[不吐不快]网站的网友意见'
msg["From"] = _user
msg["To"] = _to
try:
s = smtplib.SMTP_SSL("smtp.qq.com", 465) # 或是使用s = smtplib.SMTP("smtp.qq.com",25)
s.login(_user, _pwd)
s.sendmail(_user, _to, msg.as_string())
s.quit()
message = '感谢你的来信'
except:
message = '发送失败'

▲smtplib.SMTP_SSL([host[, port[, local_hostname[, keyfile[, certfile[, timeout]]]]]])

这是一个派生自SMTP的子类,通过SSL加密的套接字连接(使用此类,您需要使用SSL支持编译的套接字模块)。如果未指定主机,则使用“(本地主机)”。如果省略端口,则使用标准的SMTP-over-SSL端口(465)

本机已安装了支持 SMTP 的服务,如:sendmail:

1
2
3
4
5
import smtplib
from email.mime.text import MIMEText
from email.header import Header

message['From'] = Header("菜鸟教程", 'utf-8')

第三方SMTP发送邮件:

1
2
3
4
5
6
7
import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr

msg['From']=formataddr(["FromRunoob",my_sender]) # 括号里的对应发件人邮箱昵称、发件人邮箱账号
msg['To']=formataddr(["FK",my_user]) # 括号里的对应收件人邮箱昵称、收件人邮箱账号
*没有formataddr的昵称默认为账号*

Python 发送带附件的邮件:

发送带附件的邮件,首先要创建MIMEMultipart()实例,然后构造附件,如果有多个附件,可依次构造,最后利用smtplib.smtp发送。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/usr/bin/python
# -*- coding: UTF-8 -*-

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

sender = 'from@runoob.com'
receivers = ['429240967@qq.com'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱

#创建一个带附件的实例
message = MIMEMultipart()
message['From'] = Header("菜鸟教程", 'utf-8')
message['To'] = Header("测试", 'utf-8')
subject = 'Python SMTP 邮件测试'
message['Subject'] = Header(subject, 'utf-8')

#邮件正文内容
message.attach(MIMEText('这是菜鸟教程Python 邮件发送测试……', 'plain', 'utf-8'))

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

# 构造附件2,传送当前目录下的 runoob.txt 文件
att2 = MIMEText(open('runoob.txt', 'rb').read(), 'base64', 'utf-8')
att2["Content-Type"] = 'application/octet-stream'
att2["Content-Disposition"] = 'attachment; filename="runoob.txt"'
message.attach(att2)

try:
smtpObj = smtplib.SMTP('localhost')
smtpObj.sendmail(sender, receivers, message.as_string())
print "邮件发送成功"
except smtplib.SMTPException:
print "Error: 无法发送邮件"

在 HTML 文本中添加图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/usr/bin/python
# -*- coding: UTF-8 -*-

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

sender = 'from@runoob.com'
receivers = ['429240967@qq.com'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱

msgRoot = MIMEMultipart('related') //创建MIMEMultipart()实例
msgRoot['From'] = Header("菜鸟教程", 'utf-8')
msgRoot['To'] = Header("测试", 'utf-8')
subject = 'Python SMTP 邮件测试'
msgRoot['Subject'] = Header(subject, 'utf-8')

msgAlternative = MIMEMultipart('alternative')
msgRoot.attach(msgAlternative)


mail_msg = """
<p>Python 邮件发送测试...</p>
<p><a href="http://www.runoob.com">菜鸟教程链接</a></p>
<p>图片演示:</p>
<p><img src="cid:image1"></p>
"""
msgAlternative.attach(MIMEText(mail_msg, 'html', 'utf-8')) //内容

# 指定图片为当前目录
fp = open('test.png', 'rb')
msgImage = MIMEImage(fp.read())
fp.close()

# 定义图片 ID,在 HTML 文本中引用
msgImage.add_header('Content-ID', '<image1>')
msgRoot.attach(msgImage) //附件

try:
smtpObj = smtplib.SMTP('localhost')
smtpObj.sendmail(sender, receivers, msgRoot.as_string())
print "邮件发送成功"
except smtplib.SMTPException:
print "Error: 无法发送邮件"

Python SMTP发送邮件


flask-email

官方文档

阻塞发送

最简单的调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from flask import Flask
from flask_mail import Mail,Message

app = Flask(__name__)
app.config['MAIL_SERVER'] = 'smtp.qq.com'
app.config['MAIL_PORT'] = 587
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = '1063052964@qq.com'
app.config['MAIL_PASSWORD'] = '#'

mail = Mail(app)

msg = Message('邮件主题', sender='1063052964@qq.com', recipients=['2035420834@qq.com'])
msg.body = '邮件内容'
msg.html = "<h1>邮件的html模板<h1> body"

with app.app_context():
mail.send(msg)
读取配置+视图函数中调用

[config.py]

1
2
3
4
5
6
7
8
9
# 配置邮箱信息
MAIL_SERVER = 'smtp.qq.com'
MAIL_PORT = 465
MAIL_USE_TLS = False
MAIL_USE_SSL = True
# 注意此处,很多人配置发不出去和这个是有关系的
MAIL_PASSWORD = '**********'
MAIL_USERNAME = '********@qq.com'
# qq郵箱默認走ssl,所以創建的smtp對象必須要支持加密傳輸,且需要指定port=465

app.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from flask import Flask
from flask_mail import Message,Mail
from threading import Thread
import config


app = Flask(__name__)
app.config.from_object(config)
mail = Mail(app)


def sendMail():
msg = Message('test', sender = '1063052964@qq.com', recipients=["2035420834@qq.com"])
# recipients是个列表,包含所有收件人
# 此处的test是邮箱的主题,sender和config中的MAIL_USERNAME要一致哦
msg.body = '123'
msg.html = '<b>test</b>body'
mail.send(msg)

@app.route('/')
def hello_world():
sendMail()
return 'Hello World!'


if __name__ == '__main__':
app.run(debug=True)

tips:tip:具体工程中,配置可以写在单独一个文件如”.env”,然后利用python-envcfg来读取配置,如:
app.config.from_object(‘envcfg.raw’)

异步发送

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from flask import Flask
from flask_mail import Message,Mail
from threading import Thread
import config


app = Flask(__name__)
app.config.from_object(config)
mail = Mail(app)


def send_async_email(app,msg):
with app.app_context():
mail.send(message=msg)


def SendMail():
msg = Message('test',sender='106305964@qq.com',\
recipients=["870545361@qq.com"])
# recipients是个列表,包含所有收件人
# 此处的test是邮箱的主题,sender和config中的MAIL_USERNAME要一致哦
msg.body = 'testbody'
msg.html = '<b>test</b>body'
# 邮件发送给目标,可以有文本,两种方式呈现,你能看见怎样的取决于你的客户端
thr = Thread(target=send_async_email,args=[app,msg])
# 使用多线程,在实际开发中,若是不使用异步、多线程等方式,网页会卡住
thr.start()
return 'ok'


@app.route('/')
def hello_world():
return 'Hello World!'


if __name__ == '__main__':
app.run(debug=True)

▲. 许多Flask的扩展都是假定自己运行在一个活动的应用和请求上下文中,Flask-Mail的send函数使用到current_app 这个上下文了,所以当 mail.send()函数在一个线程中执行的时候需要人为的创建一个上下文。在示例 send_async_email 中使用了 app.app_context() 来创建一个上下文。

既然异步的邮件发送功能已经实现了,如果将来我们需要实现其它异步的函数,还有什么需要改进的吗?我们需要为每一个实现异步功能的函数拷贝多线程的代码吗?这并不好。

我们可以通过实现一个 装饰器 来解决这个问题。有了装饰器,上面的代码可以修改为:

1
2
3
4
5
6
7
8
9
10
11
12
from .decorators import async

@async
def send_async_email(app, msg):
with app.app_context():
mail.send(msg)

def send_email(subject, sender, recipients, text_body, html_body):
msg = Message(subject, sender=sender, recipients=recipients)
msg.body = text_body
msg.html = html_body
send_async_email(app, msg)

好的多了吧,对不对?

这个神奇的代码其实很简单。我们把它放入一个新文件(文件 app/decorators.py):

1
2
3
4
5
6
7
from threading import Thread

def async(f):
def wrapper(*args, **kwargs):
thr = Thread(target = f, args = args, kwargs = kwargs)
thr.start()
return wrapper

作为一个练习,大家可以考虑考虑如何用 *multiprocessing* 模块来实现上面的功能。

摘自flask文档


django.core.mail邮件

EmailMessage: 标题. 内容.发件人.收件人

1
2
3
4
from django.core.mail import send_mail

send_mail(u'邮件标题', u'邮件内容', 'from@example.com',
['to@example.com'], fail_silently=False)

send_mail()

subject, message, from_email and recipient_list 这四个参数是必须的。

  • subject: 字符串,表示邮件标题。
  • message: 字符串,表示邮件内容。
  • from_email: 字符串,表示发件邮箱。
  • recipient_list: 字符串列表,列表中每个成员都是一个邮箱地址,而且每个收件人都会在 “收件人/To:” 栏看到出现在 recipient_list 中的其他收件人。
  • fail_silently: (可选)布尔值。为 False 时, send_mail 会抛出 smtplib.SMTPException 异常。 smtplib 文档列出了所有可能的异常。 这些异常都是 SMTPException 的子类。
  • auth_user: (可选)SMTP服务器的认证用户名。没提供该参数的情况下,Django会使用 EMAIL_HOST_USER 配置项的设置。
  • auth_password: (可选)SMTP服务器的认证密码,没提供该参数的情况下,Django会使用 EMAIL_HOST_PASSWORD配置项的设置。
  • connection: (可选)发送邮件的后端。没提供该参数的情况下,Django会使用默认后端的实例。可查看 Email backends 了解更多细节。

send_mass_mail()send_mail() 的区别在于: send_mail() 每发送一封邮件就会打开一次邮件服务器链接,而send_mass_mail() 则是打开一次链接,发送所有的邮件。 send_mass_mail() 明显更高效。

main_admins()

mail_admins(subject, message, fail_silently=False, connection=None, html_message=None)

django.core.mail.mail_admins() 是一个给网站后台管理员(admin)发邮件的快捷方法,管理员设置放在 ADMINS 配置项。

mail_admins() 使用 EMAIL_SUBJECT_PREFIX 配置项的值做为邮件标题的前缀,默认情况下是 "[Django] "

mail_managers()

mail_managers`(subject, message, fail_silently=False, connection=None, html_message=None)

mail_managers(*subject*, *message*, *fail_silently=False*, *connection=None*, *html_message=None*)django.core.mail.mail_managers()is just likemail_admins(),不同之处在于该方法的邮件接收人是网站负责人(manager), 可以在 [MANAGERS`]配置项设置网站负责人

EmailMessage 对象

  • *class *EmailMessage

EmailMessage 类使用下列参数初始化(除非使用位置参数,否则默认顺序如下)。所有参数均可选,均可在调用 send()方法之前的任何时间对其赋值。

加入了 cc 参数(cc是抄送)

  • subject: 邮件的标题行

  • body: 邮件的主体内容文本,须是纯文本信息。

  • from_email: 发送者的地址。 fred@example.comFred <fred@example.com> 格式都是合法的。如果忽略该参数,Django就会使用 DEFAULT_FROM_EMAIL 配置项。

  • to: 收件人地址列表或元组。

  • bcc: 发送邮件时用于”Bcc”头信息的一组列表或元组,也就是暗送的收件人。

  • connection: 一个邮件后端实例。用同一个链接发送多封邮件就要用到该参数。忽略该参数时,会在调用 send() 时自动创建一个新链接。

  • attachments: 置于邮件报文内的附件列表。列表元素可以是 email.MIMEBase.MIMEBase 实例,也可以是 (filename, content, mimetype) 三部分构成的元组。

  • headers: 置于邮件报文内的其他头信息(header)的字典。字典的key是头信息的名称,字典的value是头信息的值。 这样做能确保头信息的名称和对应值会以正确的格式保存于邮件报文中。

  • cc: 发送邮件时放于”Cc”头信息的一系列列表或元组。

    例如:

    1
    2
    3
    email = EmailMessage('Hello', 'Body goes here', 'from@example.com',
    ['to1@example.com', 'to2@example.com'], ['bcc@example.com'],
    headers = {'Reply-To': 'another@example.com'})

    该类方法如下:

    • send(fail_silently=False) 发送邮件报文。如果在构造邮件时如果指定了某个链接(connection),就会使用该链接发邮件。 否则,就会使用默认后端的实例发邮件。如果关键字参数 fail_silentlyTrue ,就会忽略邮件发送时抛出的异常。

    • message() 构造了一个 django.core.mail.SafeMIMEText 对象 (Python的 email.MIMEText.MIMEText 类的子类) 或是 django.core.mail.SafeMIMEMultipart 对象(该对象保存即将发送出去邮件报文)。如需扩展 EmailMessage类,一般情况下要覆写该方法,将你所需的内容添加到MIME对象中。

    • recipients() 返回邮件中所有收件人的列表,不管收件人是在 to 还是 bcc 属性中。这是另一个经常被继承覆写的方法, 因为SMTP服务器在发送邮件报文时,要接收完整的收件人列表。即使你自己的类使用其他方式来指定收件人,也仍然需要使用该方法返回收件人列表。

    • attach() 创建一个新的文件附件,并把它添加到邮件报文中。 有两种方法调用 attach():

      • 传递一个单独的 email.MIMEBase.MIMEBase 实例做为参数。该实例会直接添加到最终的邮件报文中。

      • 或者,给 attach() 传递三个参数: filename, contentmimetype. filename 是出现在邮件中的附件文件的名称, content 是附件的内容,而 mimetype 是附件所使用的MIME类型。 如果忽略 mimetype, Django会自动根据附件文件名来推测MIME内容类型。

        例如:

        1
        message.attach('design.png', img_data, 'image/png')
    • attach_file() 使用当前文件系统下的某个文件做为附件。调用时,传入某个文件的完整路径,以及该附件的MIME类型(可选的)。 忽略MIME类型的话,Django会自动根据附件文件名来推测MIME类型。最简单的用法如下:

      1
      message.attach_file('/images/weather_map.png')

发送多用途邮件

在同一封邮件中包含多种版本的内容是非常有用的;典型的例子就是发送既有纯文本版本内容又有HTML版本内容的邮件。 在Django的邮件库中,可以使用 EmailMultiAlternatives 类来达到该目的。 EmailMessage 的子类有一个attach_alternative() 方法用来包含其他版本的邮件主体内容。所有其他方法(包括类的初始化方法)都直接继承自 EmailMessage

发送一封文本/HTML混合邮件,代码如下:

1
2
3
4
5
6
7
8
from django.core.mail import EmailMultiAlternatives

subject, from_email, to = 'hello', 'from@example.com', 'to@example.com'
text_content = 'This is an important message.'
html_content = '<p>This is an <strong>important</strong> message.</p>'
msg = EmailMultiAlternatives(subject, text_content, from_email, [to])
msg.attach_alternative(html_content, "text/html")
msg.send()

默认情况下,EmailMessage 类中的 body 参数的MIME类型是 "text/plain" 。 大多数情况下,没必要更改该MIME,因为这样能保证每个收件人能够阅读该邮件,而不论他们使用的是什么邮件客户端。 不过,在能确保收件人能处理多用途邮件的情况下,可以使用:class:~django.core.mail.EmailMessage 类的 content_subtype 属性 来更改邮件内容类型。主类型总是 "text" ,子类型可以设置为别的版本(比如html),例如:

1
2
3
msg = EmailMessage(subject, html_content, from_email, [to])
msg.content_subtype = "html" # 主内体现在变成 text/html
msg.send()

获取邮件发送后端的实例

1
2
3
django.core.mail 的 get_connection() 函式返回你当前使用的邮件后端的实例。

get_connection(backend=None, fail_silently=False, *args, **kwargs)
  • SMTP backend --默认的后端

  • Console backend

  • File backend --该后端并不建议在生产环境下使用–它仅仅是为开发提供方便

  • In-memory backend(内存后端)

  • Dummy backend(空后端)

需要在 settings.py中设置的东西:

1
2
3
4
5
6
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_SSL = True
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_HOST_USER = 'urusername@gmail.com'
EMAIL_HOST_PASSWORD = 'password'

Author: Mrli

Link: https://nymrli.top/2018/12/12/Python中邮件的发送/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
2018-12-15小记
NextPost >
使用Cerbot申请免费证书升级 http到https
CATALOG
  1. 1. Python普通的邮件发送
    1. 1.0.1. Python 发送带附件的邮件:
    2. 1.0.2. 在 HTML 文本中添加图片
  • 2. flask-email
    1. 2.0.1. 阻塞发送
      1. 2.0.1.1. 最简单的调用
      2. 2.0.1.2. 读取配置+视图函数中调用
    2. 2.0.2. 异步发送
  • 3. django.core.mail邮件
    1. 3.1. send_mail()
    2. 3.2. main_admins()
    3. 3.3. mail_managers()
      1. 3.3.1. EmailMessage 对象
      2. 3.3.2. 发送多用途邮件
      3. 3.3.3. 获取邮件发送后端的实例
      4. 3.3.4. SMTP backend --默认的后端
      5. 3.3.5. Console backend
      6. 3.3.6. File backend --该后端并不建议在生产环境下使用–它仅仅是为开发提供方便
      7. 3.3.7. In-memory backend(内存后端)
      8. 3.3.8. Dummy backend(空后端)
      9. 3.3.9. 需要在 settings.py中设置的东西: