使用Django发送邮件

来源:互联网 发布:网络不谢是什么意思 编辑:程序博客网 时间:2024/05/18 03:18

引言

在日常开发中,通过程序来实现对邮箱的操作是一个很常见的应用场景。比如:

  • 通过邮件来确认用户注册
  • 后台管理人员针对用户的反馈发送邮件
  • 通过邮箱来重置用户密码

那么,笔者简单介绍一下如何使用Django实现邮件的发送。
PS:笔者在这里推荐一篇掘金的文章是关于邮箱的登录注册的设计细节,其中一些细节考虑的挺好的,在设计的时候可以用来参考。
传送门:干嘛又要邮箱登录啊?—现代登录系统的结构设计

准备

电子邮件系统

在一个因特网电子邮件系统中,通常有三个组成部分:

  • 用户代理(User Agent)
    • 比如一些第三方客户端,如foxmail,以及常用的浏览器
  • 邮件服务器(Mail Server)
    • 比如QQ邮箱的邮箱服务器,域名为stmp.qq.com
  • 简单邮件传输协议(SMTP)
    • 在邮件服务器中互相通讯所采取的协议

那么,这三个组成部分是如何运行的呢?
假设用户A通过用户代理(QQ邮箱)向用户B(163邮箱)发出了一封电子邮件,这个过程可以描述为:

  • 用户A的用户代理通过SMTP协议登陆QQ邮箱的邮件服务器,并将要发送的内容放在了QQ邮箱的邮件服务器的属于用户A的邮箱中。
  • QQ邮箱的邮件服务器通过SMTP协议向163邮箱的邮件服务器发送这封电子邮件。
  • 163邮箱的邮件服务器在收到电子邮件之后根据邮件的头部得知接收者是用户B,将邮件放到了邮件服务器中属于用户B的邮箱中。
  • 用户B在用户代理中登录了163的邮件服务器,并通过POP3或者IMAP等“拉”协议拉取邮件服务器中自己的邮件到用户代理中查看。(若是浏览器,可以通过HTTP协议向邮件服务器传送邮件或者拉取邮件)

流程图如下:

Created with Raphaël 2.1.0用户A的用户代理QQ邮箱的邮件服务器163邮箱的邮件服务器用户B的用户代理

在这里有一个小小的问题,为什么不是用户A的用户代理直接向用户B的用户代理直接发送邮件,而要通过邮件服务器进行发送呢?
邮件服务器在邮件的传送与拉取中有两个重要作用:

  • 尽可能的保证在任何时刻维持开机状态,接受来自其他邮件服务器的邮件传输。
  • 当邮件发送失败后,继续尝试向对方邮件服务器发送,若邮件发送失败,通知用户代理。

SMTP协议与Python标准库:smtplib

SMTP协议出现的时期比HTTP协议还要早,因此,也有部分设计的不够完善的地方。比如SMTP协议要求传输的邮件内容以及邮件头部都需要使用ASCII编码,导致其他无法用ASCII编码的字符(如汉字,或二进制文件)必须通过base64等方法将字节转换为ascii码才可以传输,有兴趣的同学可以去了解SMTP的扩展协议MIME,MIME也同样广泛的使用在HTTP协议中。
对于传送一般文本文件与二进制文件有什么区别,可以去参考这个知乎问题:关于smtp协议和mime,困惑好久的问题:为什么一定要传送可见的字符?

在Python中,smtplib标准库主要负责邮件的发送,而邮件内容和头部的封装和解释主要通过email标准库负责。
简单了解一下stmplib中两个用于发送邮件的api:

  • sendmail(from_addr, to_addrs, msg, mail_options=[], rcpt_options=[])
  • send_message(msg, from_addr, to_addrs, mail_options=[], rcpt_options=[])

这两个函数的区别是在sendmail函数中的msg参数是一个string,这意味着在发送的时候必须是符合SMTP协议的完整报文字符串(比如如果为邮件头部,则使用\r\n作为一行的结束,邮件头部和邮件内容的区分即一个空行,即以\r\n\r\n作为区分)。
而对于send_message函数,msg参数传入的是email标准库中的Message对象,Message对象提供了对邮件头部和内容的api,而无需开发者担心SMTP协议报文的具体格式。(可以调用Message.as_string()查看输出的SMTP报文格式)
这里还有一点稍微需要注意的是,当设置了这两个函数的from_addr和to_addrs参数之后,在msg参数中还需要再设置一遍,因为当查看邮件的时候,邮件显示的发送方和接收方其实是由邮件头部来决定的,即在msg参数中。

下面笔者给出一个使用smtplib登录QQ邮箱并发送邮件的例子:

# SSL port for QQ emailsmtp_ssl_port = 465# QQ email server domainsmtp_server = "smtp.qq.com"smtp_from_user = "from_user@qq.com"# not your QQ passwordsmtp_password = "password"smtp_to_user = 'to_user@qq.com'smtp_message_string = """From: %s\r\nSubject: Hello\r\n\r\nHello, %s\r\n. \r\n""" % (smtp_from_user, smtp_to_user)import smtplib# construct a connection to QQ email serverserver = smtplib.SMTP_SSL(smtp_server, smtp_ssl_port)# login with your QQ account and passwordserver.login(smtp_from_user, smtp_password)# message is a stringserver.sendmail(smtp_from_user, smtp_to_user, smtp_message)# message is a objectfrom email.message import Messagemes = Message()mes.add_header('From', smtp_from_user)mes.add_header('Subject', "Hello")mes.set_payload("Hello, %s" % smtp_to_user)server.send_message(mes, smtp_from_user, smtp_to_user)server.quit()

对于QQ邮箱的登录还有一个值得注意的地方,你需要在QQ邮箱的用户设置中开启SMTP服务,然后获取一个授权码,通过授权码来登录SMTP服务器,不要使用QQ密码来登录服务器。

如果需要在邮件中传送其他类型的文件,则需要使用MIME类型,在email标准库中也提供了封装的子类。
Python官方参考文档:
19.1. email — An email and MIME handling package
21.17. smtplib — SMTP protocol client

风险:邮件头部注入

在前面笔者已经提过SMTP协议中使用\r\n作为换行分隔符,因此,如果攻击者在表单上输入邮件地址时加上换行符就能起到执行其他命令的效果,所以,在检查提交表单的相关邮件头部的时候需要禁止换行符的输入。
关于Django应对邮件头部的做法在Django book中也有提及:Django book 第二十章:安全

Django邮件服务组成

Django的邮件服务由三个部分组成:

  • send_mail以及send_mass_mail两个简易的函数接口
  • EmailMessage对象负责对邮件的头部和内容构造
  • Email backends负责发送邮件

Django send_mail()

Django的发送邮件邮件服务是基于smtplib封装的,并提供了两个简单的api接口:

  • send_mail()
  • send_mass_mail()

这两个函数的区别主要是在接收方看到的接收列表是否会显示其余接收者,以及前者每发送一次邮件就需要建立一次连接,后者发送所有的邮件都只需建立一次连接。

然而查看这两个函数的源码,可以看到如下注释:

"""Note: The API for this method is frozen. New code wanting to extend the    functionality should use the EmailMessage class directly."""

Django官方更为推荐的是使用EmailMessage对象:

Note

This is a design feature. send_mail() and related functions were originally the only interface Django provided. However, the list of parameters they accepted was slowly growing over time. It made sense to move to a more object-oriented design for email messages and retain the original functions only for backwards compatibility.

Django EmailMessage

Django的EmailMessage对象提供了更为强大的服务,其中包括了cc以及bcc的相关邮件服务(不知道什么是cc和bcc?这里有知乎传送门:电子邮件的抄送和密送的有什么作用?)。

Django email backends

Django提供了四个backend,分别用于各种场景,如开发,实际生产:

  • SMTP backend
  • Console backend
  • File backend
  • In-memory backend
  • Dummy backend

在这里,值得注意的是,如果是在django test的环境下测试邮件的发送是不会真正的把邮件发送出去的,因为在test模式下使用的并非是SMTP backend,但是会返回正常的邮箱发送成功的标志。
参考文档:
Django Sending email
Django testing email

0 0