发送邮件

尽管Python已经提供了相对易用的邮件发送模块 smtplib ,但Django仍对其做了轻度的封装。封装后的模块不仅发送邮件速度快,而且在开发环境下也很容易对邮件发送进行测试, 并对无法使用SMTP的平台也提供了支持。

封装代码在 django.core.mail 模块中。

入门例子

只有两行:

from django.core.mail import send_mail

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

Django发邮件功能要用到配置文件中的 EMAIL_HOSTEMAIL_PORT 配置项,分别用来指定发邮件服务器和端口。 如果SMTP服务器需要用户认证,还须设置 EMAIL_HOST_USEREMAIL_HOST_PASSWORD 配置项,指定用户名和密码。 而 EMAIL_USE_TLS 配置项则决定是否使用安全加密链接。

Note

django.core.mail 发送的邮件,其字符集都由 DEFAULT_CHARSET 配置项决定。

send_mail()

send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None, connection=None)

发邮件的最便捷方式就是使用 django.core.mail.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_mass_mail(datatuple, fail_silently=False, auth_user=None, auth_password=None, connection=None)

django.core.mail.send_mass_mail() 适合处理群发邮件。

datatuple 是一个元组,其中每个元素格式如下:

(subject, message, from_email, recipient_list)

fail_silently, auth_userauth_password 与上面 send_mail() 中提到的一样。

datatuple 中每个元素都对应一封单独的邮件。与上面 send_mail() 一样,出现在 recipient_list 中的收件人同样会在 “收件人/To:” 字段中看到该邮件的其他所有收件人。

举个例子,下面的代码会给双组不同的收件人发送两封不同的邮件;但仅仅打开一次邮件服务器的链接:

message1 = ('Subject here', 'Here is the message', 'from@example.com', ['first@example.com', 'other@example.com'])
message2 = ('Another Subject', 'Here is another message', 'from@example.com', ['second@test.com'])
send_mass_mail((message1, message2), fail_silently=False)

send_mass_mail() vs. send_mail()

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

mail_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] "

邮件的”From:”头的内容就是 SERVER_EMAIL 配置项的值。

该方法方便使用且易于理解。

如果提供了 html_message 参数,会导致邮件变成 multipart/alternativemessage 格式变成 text/plainhtml_message 格式变成 text/html

mail_managers()

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

django.core.mail.mail_managers() is just like mail_admins() ,不同之处在于该方法的邮件接收人是网站负责人(manager), 可以在 MANAGERS 配置项设置网站负责人。

例子

该例同时给 john@example.comjane@example.com 发送同一封邮件,这两个邮箱地址都会出现在 “收件人/To:” 一栏:

send_mail('Subject', 'Message.', 'from@example.com',
    ['john@example.com', 'jane@example.com'])

该例则是分别为 john@example.comjane@example.com 分别发送同一封邮件,每人收到的邮件只显示唯一一个收件人:

datatuple = (
    ('Subject', 'Message.', 'from@example.com', ['john@example.com']),
    ('Subject', 'Message.', 'from@example.com', ['jane@example.com']),
)
send_mass_mail(datatuple)

防止邮件头注入

Header injection (邮件头信息注入)是可以被骇客(黑客hacker是指伟大的程序员,骇客Cracker和Attacker是指无知无耻的狗盗之辈)利用的安全漏洞。 他们会利用该漏洞,在代码生成的邮件报文中添加额外的邮件头信息(header)以控制 “收件人/To:” 和 “发件人/From:” 。

之前所提及的Django邮件函式都禁止在头信息中添加换行,从而避免注入。只要 subject , from_emailrecipient_list 包含换行(不管是Windows格式,还是Unix或是Mac OS 格式) ,发送函式 (比如 send_mail()) 都会抛出 django.core.mail.BadHeaderError 异常( ValueError 的一个子类),也不会发送该邮件。 因此在给邮件函式传递参数之前,一定要对所有数据进行验证。

如果 message 在文本最开始处含有头信息,那么这些头信息就会被打印成邮件内容的第一个比特。

下面这个view例子, subjectmessagefrom_email 从request POST中取值,然后发送邮件到 admin@example.com, 发送成功后重定向到”/contact/thanks/”:

from django.core.mail import send_mail, BadHeaderError

def send_email(request):
    subject = request.POST.get('subject', '')
    message = request.POST.get('message', '')
    from_email = request.POST.get('from_email', '')
    if subject and message and from_email:
        try:
            send_mail(subject, message, from_email, ['admin@example.com'])
        except BadHeaderError:
            return HttpResponse('Invalid header found.')
        return HttpResponseRedirect('/contact/thanks/')
    else:
        # In reality we'd use a form class
        # to get proper validation errors.
        return HttpResponse('Make sure all fields are entered and valid.')

EmailMessage类

Django的 send_mail()send_mass_mail() 函式事实上是对 EmailMessage 类使用方式 的一个轻度封装。

send_mail() 和相关的其他封装函式并没有充分使用 EmailMessage 类的所有特性。 要想使用更多特性,比如暗送(BCC)给收件人,加入附件,或是多用途格式(multi-part)邮件,都要直接创建 EmailMessage 实例。

Note

这是一个设计特性: send_mail() 和其他相关函式是最初只是由Django提供的一组接口。 但是其接收的参数数量却随着Django的发展而慢慢增长。这样就能理解为什么要将邮件报文使用面向对象设计重新设计,同时又保留原有函式以保证后端兼容,

EmailMessage 仅仅负责创建邮件报文,而 email backend 则负责邮件发送。

为了方便起见, EmailMessage 提供了一个简单的 send() 方法用以发送纯文本邮件。如果想发送多用途格式邮件,可以使用后端API 发送多用途邮件.

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”头信息的一系列列表或元组。

例如:

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内容类型。

      例如:

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

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

发送多用途邮件

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

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

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),例如:

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

Email backends 邮件发送后端

实际上发送邮件的工作是由邮件发送后端处理的。

邮件后端类有下列方法:

  • open() 实例化一个长生命周期的邮件发送链接。
  • close() 关闭当前邮件发送链接。
  • send_messages(email_messages) 发送一组 EmailMessage 对象的列表。如果链接(connection)还未打开,该调用会隐性地打开链接,发完邮件后就关闭该链接。 如果该链接已经打开,那么它会保持打开状态直至邮件发送完毕。

获取邮件发送后端的实例

django.core.mailget_connection() 函式返回你当前使用的邮件后端的实例。

get_connection(backend=None, fail_silently=False, *args, **kwargs)

默认情况下,对 get_connection() 的调用会返回一个邮件后端实例,具体是哪个后端由 EMAIL_BACKEND 配置项决定。 如果指定了``backend`` 参数,就会对该后端进行实例化。

fail_silently 参数决定后端如何处理错误。 如果 fail_silently 为 True ,发送邮件进程引发的异常将会被忽略。

其他的所有参数都会被直接传递给邮件后端的构造函式

Django提供了几种不同的邮件发送后端。除了SMTP backend之外(也是默认的邮件发送后端), 其他后端仅适用于测试和开发过程。如果有特珠的邮件发送要求,可以 编写自己的邮件发送后端.

SMTP backend

SMTP后端,这也是默认的后端(backend)。邮件会通过SMTP服务器进行发送。服务器地址和认证凭证都由配置文件中的 EMAIL_HOST, EMAIL_PORT, EMAIL_HOST_USER, EMAIL_HOST_PASSWORDEMAIL_USE_TLS 配置项来指定。

SMTP后端是Django默认的配置。若想明确指定,可配置文件中进行如下配置:

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'

SMTPConnection 对象

在Django1.2以前,Django提供了一个 SMTPConnection 类。该类提供了一套直接控制SMTP用以发送邮件的方式。 该类已经被弃用了,Django转而使用更通用的邮件后端API。

出于向上兼容性的考虑, SMTPConnection 仍然保留在 django.core.mail 中,做为SMTP backend的别名。 新代码应该使用 get_connection() 以代替。

Console backend

控制台后端。控制台后端并不会发送真实的邮件,而是仅仅将邮件内容发送到标准输出。默认情况下, 控制台后端会写到 stdout 。可以在构造链接时提供 stream 关键字参数以使用不同的类流(stream-like)对象

要指定该后端,只要在配置文件中设置:

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

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

File backend

文件后端。文件后端将邮件写到一个文件中,在该后端中,每打开一个新session,就会创建一个新文件。文件存放在哪个目录下是由 EMAIL_FILE_PATH 配置项或是在使用 get_connection() 创建链接(connection)时由 file_path 关键字决定。

要指定该后端,只要在配置文件中设置:

EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
EMAIL_FILE_PATH = '/tmp/app-messages' # 将其改为本地的存放目录

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

In-memory backend

内存后端。 'locmem' 后端将邮件报文保存到 django.core.mail 模块的一个特定属性 outbox 中。发送第一封邮件时, outbox 属性就会被创建。 该属性是一个列表,每个 EmailMessage 实例的邮件报文都会添加到该列表中。

要指定该后端,只要在配置文件中设置:

EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'

该后端并不建议在生产环境下使用–它仅仅是为开发和测试提供方便。

Dummy backend

空后端。如名所示,该后端不会做任何事情。要指定该后端,只要在配置文件中设置:

EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'

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

自定义邮件发送后端

如果要改变邮件的发送方式,可以自己写一个邮件发送后端。 将 EMAIL_BACKEND 配置项设为自定义后端的python引用路径。

自定义邮件后端应该继承 BaseEmailBackend ,该类位于 django.core.mail.backends.base 模块。 自定义邮件后端必须实现 send_messages(email_messages) 方法。该方法接收一个列表,列表元素是 EmailMessage 实例,并返回一组发送成功的消息。 如果自定义后端还涉及持久化session或connection,还应该实现 open()close() 方法。 smtp.EmailBackend 可以做为一个现成的参考。

发送多封邮件

建立和关闭一个SMTP链接(connection)是一个代价高昂的过程(对于这种情况,任何网络链接都是如此)。 因此要想发送更多邮件,重用SMTP链接就显得很有意义;而不是每次发送一封邮件,都要创建和销毁一个链接。

有两种方式可以重用链接。

第一种方式,可以使用 send_messages() 方法。 send_messages() 接收一个 EmailMessage (或是子类)实例的列表, 然后用单独一个链接来发送列表中的内容。

例如,你有一个名为 get_notification_email() 的函式,用以定时发送邮件,它返回一个 EmailMessage 对象的列表。 调用一次 send_messages 就可以发送完这些邮件:

from django.core import mail
connection = mail.get_connection()   # 使用默认邮件链接(connection)
messages = get_notification_email()
connection.send_messages(messages)

在这个例子中,调用 send_messages() 会在后端打开一个链接(connection),发送列表中的邮件报文,然后再关闭链接。

第二种方式是在邮件后端使用 open()close() 方法手动控制链接。 在已经打开链接的情况下, send_messages() 不会自动打开和关闭链接。因此在该情况下,要手动决定何时关闭链接。 举个例子:

from django.core import mail
connection = mail.get_connection()

# 手动打开链接(connection)
connection.open()

# 使用该链接构造一个邮件报文
email1 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
                          ['to1@example.com'], connection=connection)
email1.send() # 发送邮件

# 构造其他两个报文
email2 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
                          ['to2@example.com'])
email3 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
                          ['to3@example.com'])

# 在一个调用中发送两封邮件
connection.send_messages([email2, email3])
# 链接已打开,因此 send_messages() 不会关闭链接
# 要手动关闭链接
connection.close()

测试邮件发送

有几种情况下,我们并不想让Django发送邮件。比如:开发网站时,一般不想发送数以千计的邮件,但又想验证一下在正确环境下邮件是否会发送给目标人群, 以及这些邮件内容是否正确。

测试发送邮件的最简单方式就是使用 console 邮件发送后端。它直接将所有邮件输出到stdout,从而可以检查邮件内容是否正确。

file 后端在开发过程中也非常有用–它将每封邮件的内容都写到一个指定的文件中,从而方便检查。

还有一种方式是使用 “dumb” SMTP 服务器,它在本地接收所有邮件,并在终端显示出邮件,但事实上不能发送任何邮件。 Python有一个内置的方式可以实现该服务,只须一条命令行:

python -m smtpd -n -c DebuggingServer localhost:1025

该命令会启动一个简单的SMTP服务器,用以监听本地的1025端口。该服务器简单的将所有邮件的头信息和邮件主体打印到标准输出。 然后只须更改 EMAIL_HOSTEMAIL_PORT 即可。

更多关于本地测试和处理邮件的细节资料,请查看Python文档 smtpd 模块。

SMTPConnection

class SMTPConnection

Deprecated since version 1.2.

SMTPConnection 类已弃用,转而使用通用邮件后端(backend)API。

出于向上兼容性的考虑, SMTPConnection 仍然保留在 django.core.mail 中,且做为 SMTP backend. 的别名。新代码应该使用 get_connection() 取代。