发送邮件

虽然 Python 通过 smtplib 模块提供了邮件发送的接口,但是 Django 在其基础上提供了更简化的支持。这些封装意在加快邮件发送,在开发时测试发送邮件,在不支持 SMTP 的平台上支持发送邮件。

这些代码位于 django.core.mail 模块。

快速上手

仅需两行代码:

from django.core.mail import send_mail

send_mail(
    "Subject here",
    "Here is the message.",
    "from@example.com",
    ["to@example.com"],
    fail_silently=False,
)

邮件是通过 SMTP 主机和端口发送的,由配置项 EMAIL_HOSTEMAIL_PORT 指定。如果配置了 EMAIL_HOST_USEREMAIL_HOST_PASSWORD ,那么它们将被用来验证 SMTP 服务器。配置项 EMAIL_USE_TLSEMAIL_USE_SSL 控制是否使用安全连接。

备注

通过 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, html_message=None)

在大多数情况里,你可以使用 django.core.mail.send_mail() 来发送邮件。

参数 subject, message, from_emailrecipient_list 是必须的。

  • subject: 一个字符串。
  • message: 一个字符串。
  • from_email :字符串。如果为 None ,Django 将使用 DEFAULT_FROM_EMAIL 设置的值。
  • recipient_list: 一个字符串列表,每项都是一个邮箱地址。recipient_list 中的每个成员都可以在邮件的 "收件人:" 中看到其他的收件人。
  • fail_silently: 一个布尔值。若为 Falsesend_mail() 会在发生错误时抛出 smtplib.SMTPException 。可在 smtplib 文档找到一系列可能的异常,它们都是 SMTPException 的子类。
  • auth_user: 可选的用户名,用于验证登陆 SMTP 服务器。 若未提供,Django 会使用 EMAIL_HOST_USER 指定的值。
  • auth_password: 可选的密码,用于验证登陆 SMTP 服务器。若未提供, Django 会使用 EMAIL_HOST_PASSWORD 指定的值。
  • connection: 可选参数,发送邮件使用的后端。若未指定,则使用默认的后端。查询 邮件后端 文档获取更多细节。
  • html_message: 若提供了 html_message,会使邮件成为 multipart/alternative 的实例, message 的内容类型则是 text/plain ,并且 html_message 的内容类型是 text/html

返回值会是成功发送的信息的数量(只能是 01 ,因为同时只能发送一条消息)。

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_silentlyauth_userauth_password 拥有在 send_mail() 中一样的功能。

datatuple 参数的每个元素会生成一份独立的邮件内容。就像 send_mail() 中的一样, recipient_list 中的每个收件人会在邮件的 "收件人:" 中看到其他收件人的地址一样。

举个例子,以下代码会向两个不同的收件人列表发送两封不同的邮件,却复用了同一条连接:

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()

The main difference between send_mass_mail() and send_mail() is that send_mail() opens a connection to the mail server each time it's executed, while send_mass_mail() uses a single connection for all of its messages. This makes send_mass_mail() slightly more efficient.

mail_admins()

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

django.core.mail.mail_admins() 是定义在 ADMINS 配置项中,用于向网站所有者快速发送邮件。

mail_admins() 在主题前面添加 EMAIL_SUBJECT_PREFIX 指定的前缀,默认是 "[Django] "

邮件头的 "发件人:" 由 SERVER_EMAIL 配置项指定。

创建这个方法是为了方便和可读性。

若提供了 html_message,会使邮件成为 multipart/alternative 的实例, message 的内容类型则是 text/plain ,并且 html_message 的内容类型是 text/html

mail_managers()

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

django.core.mail.mail_managers() 类似 mail_admins(),但它向 MANAGERS 指定的管理员们发送邮件。

示例

以下发送了一封邮件给 john@example.comjane@example.com,他们都出现在 "收件人:":

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 是一个开发漏洞,攻击者可以利用它在邮件头插入额外信息,以控制脚本生成的邮件中的 "收件人:" 和 "发件人:" 内容。

Django 的邮件函数包含了以上所有的反头注入功能,通过在头中禁止新的行。如果 subjectfrom_emailrecipient_list 包含了新行(不管是 Unix,Windows 或 Mac 格式中的哪一种),邮件函数(比如 send_mail() )都会抛出一个 django.core.mail.BadHeaderErrorValueError 的子类),这会中断邮件发送。你需要在将参数传给邮件函数前确保数据的有效性和合法性。

如果邮件的 内容 的开始部分包含了邮件头信息,这些头信息只会作为邮件内容原样打印。

以下是一个实例视图,从请求的 POST 数据中获取 subjectmessagefrom_email,并将其发送至 admin@example.com ,成功后再重定向至 "/contact/thanks/"

from django.core.mail import BadHeaderError, send_mail
from django.http import HttpResponse, HttpResponseRedirect


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 的功能都是可用的。如果你想用进阶功能,比如密送收件人,附件,分段邮件,你需要直接创建 EmailMessage 的实例。

备注

这是一种设计方式。最初 Django 只提供了 send_mail() 和其它关联的函数。但是,随着时间的推移,它们的参数列表在慢慢地变长。所以,我们转向了更加面向对象的设计。在兼顾发送邮件的功能的同时,出于向后兼容的考虑,保留了这些函数。

EmailMessage 用于创建邮件消息。 邮件后端 用于发送邮件。

出于方便起见, EmailMessage 提供了一个 send() 方法,用于发送一封邮件。如果你需要发送多封邮件,邮件后端 API 提供了选择

EmailMessage 对象

class EmailMessage

EmailMessage 通过以下参数构造(可选参数要按指定顺序提供)。所以的参数都是可选的,且可在调用 send() 方法前设置。

  • subject: 邮件的主题。
  • body: 邮件内容,需要为纯文本格式。
  • from_email: 发件人地址。 fred@example.comFred <fred@example.com> 形式都是合法的。若省略,则使用 DEFAULT_FROM_EMAIL 配置的值。
  • to: 一个包含收件人地址的列表或元组。
  • bcc: 一个包含地址的列表或元组,指定“密送”对象。
  • connection: 一个邮件后端的实例。若在发送多份邮件时,若想复用连接,则设置此参数。如果省略,在调用 send() 时总会创建新连接。
  • attachments: 附加在邮件中的附件列表。 可以是 MIMEBase 的实例,或 (文件名,内容,mimetype) 的元组。
  • headers: 一个字典,包含邮件中额外的头信息。字典的关键字是头的名称,值为头的值。需要由调用者确保头名和值的正确性。对应的属性是 extra_headers
  • cc: 一个包含收件人地址的列表或元组,指定“抄送”对象。
  • reply_to: 一个包含收件人地址的列表或元组,指定“回复”对象。

例如:

from django.core.mail import EmailMessage

email = EmailMessage(
    "Hello",
    "Body goes here",
    "from@example.com",
    ["to1@example.com", "to2@example.com"],
    ["bcc@example.com"],
    reply_to=["another@example.com"],
    headers={"Message-ID": "foo"},
)

这个类拥有以下方法:

  • send(fail_silently=False) sends the message. If a connection was specified when the email was constructed, that connection will be used. Otherwise, an instance of the default backend will be instantiated and used. If the keyword argument fail_silently is True, exceptions raised while sending the message will be quashed. An empty list of recipients will not raise an exception. It will return 1 if the message was sent successfully, otherwise 0.

  • message() 构建了一个 django.core.mail.SafeMIMEText 对象( MIMEText 的子类)或一个 django.core.mail.SafeMIMEMultipart 对象用于存储邮件内容。如果你想继承 EmailMessage ,你可能期望重写这个方法,在 MIME 对象中放入你期望的内容。

  • recipients() 返回一个包含邮件所以收件人的列表,不管他们是收件人,抄送人,密送人中的哪一个。这可能是另一个你在创建子类时想复现的方法,因为 SMTP 服务器需要你在发送邮件时告诉它完整的收件人列表。如果你在子类中实现了另一个方法,指定收件人列表,这个方法必须也返回相同的结果。

  • attach() 创建一个新的附件,并加到邮件。有两种调用 attach() 的方式:

    • 可以仅传送一个 MIMEBase 的实例。这会被直接插入邮件。

    • 另一个可选的方案,你可以向 attach() 传递 3 个参数: filenamecontentmimetypefilename 是文件附件的名字,它会显示在邮件中, content 是附件包含的数据,而 mimetype 是一个可选参数,指定附件的 MIME 类型。如果你省略了 mimetype,MIME 类型将会参考附件的文件名。

      例如:

      message.attach("design.png", img_data, "image/png")
      

      如果你指定 message/rfc822mimetype,它也会接受:class:django.core.mail.EmailMessage 和 email.message.Message

      对于以 text/ 开头的 mimetype 类型,其内容应该是字符串。二进制数据将尝试以 UTF-8 解码,如果失败了,MIME 类型会被改为 application/octet-stream ,并不会修改数据内容。

      此外, message/rfc822 附件不再是 base64 编码,因为违反了 RFC 2046#section-5.2.1 。之前在 EvolutionThunderbird 会造成显示问题。

  • attach_file() 通过从本地文件系统中选择一个文件的方式创建附件。调用时,传入文件的路径。附件的 MIME 类型是可选的。如果省略了 MIME 类型,会参考文件名。你可以这样使用:

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

    对于 MIME 类型以 text/ 开头的,二进制数据的处理方式与 attach() 中的一样。

发送可选的内容类型。

在邮件中包含不同类型的内容是很实用的技巧;典型的例子是在邮件中同时包含文本和 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()

默认情况下,EmailMessagebody 参数的 MIME 类型是 "text/plain"。经验告诉我们,不改它会更好。因为这样能确保不管收件人使用何种邮件客户端都可以正常的阅读邮件。不过,如果你能确保你的收件人都能处理可选的内容类型,你可以使用 EmailMessage 类的 content_subtype 属性改变主要内容的类型。主类型一般总是 "text",但你可以修改子类型。比如:

msg = EmailMessage(subject, html_content, from_email, [to])
msg.content_subtype = "html"  # Main content is now text/html
msg.send()

邮件后端

发送邮件的动作是由邮件后端执行的。

邮件后端类拥有以下方法:

  • open() 创建一个发送邮件的长连接。
  • close() 关闭当前发送邮件的连接。
  • send_messages(email_messages) sends a list of EmailMessage objects. If the connection is not open, this call will implicitly open the connection, and close the connection afterward. If the connection is already open, it will be left open after mail has been sent.

这也可以用作内容管理器,它会在需要的时候自动调用 open()close():

from django.core import mail

with mail.get_connection() as connection:
    mail.EmailMessage(
        subject1,
        body1,
        from1,
        [to1],
        connection=connection,
    ).send()
    mail.EmailMessage(
        subject2,
        body2,
        from2,
        [to2],
        connection=connection,
    ).send()

获取邮件后端的一个实例

django.core.mail 中的 get_connection() 函数返回一个你能使用的邮件后端实例。

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

默认情况下,调用 get_connection() 会返回配置项 EMAIL_BACKEND 指定的后端。如果你传入了 backend 参数,将会返回该后端的实例。

fail_silently 控制后端怎么处理错误。若 fail_silently 为 True,发送邮件过程中的异常都会被和谐掉。

剩余的参数将直接传给邮件后端的构造器。

Django 自带了几种邮件后端。除了 SMTP 后端(默认值)外,这些后端应仅在开发和测试阶段使用。如果对发送邮件有特殊的需求,你可以 编写自定义后端

SMTP 后端

class backends.smtp.EmailBackend(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)

这是默认的后端。邮件将会通过 SMTP 服务器发送。

若以下某个参数值为 None,则会从匹配的设置项中读取:

SMTP 后端是 Django 默认配置的。如果你想显示的指定,将以下内容放入你的配置中:

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

若未指定,timeout 的默认值将由 socket.getdefaulttimeout() 的返回值确定,其默认值为 None (无超时)。

控制台后端

控制台后端仅将邮件发送至标准输出,而不是真的发送。默认情况下,控制台后端输出至 stdout。在创建连接时,你可以提供 stream 关键字参数来使用另一个类似 stream 的对象。

为了使用该后端,将以下代码加入你的配置中:

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

该后端不是为了在生产环境使用的——出于方便的目的,让你在开发阶段使用。

文件后端

文件后端将邮件写入文件。对该后端的每次会话都会创建新文件。存储这些文件的目录可以从配置项 EMAIL_FILE_PATH 获取,也可在调用 get_connection() 时以关键字参数 file_path 指定。

为了使用该后端,将以下代码加入你的配置中:

EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
EMAIL_FILE_PATH = "/tmp/app-messages"  # change this to a proper location

该后端不是为了在生产环境使用的——出于方便的目的,让你在开发阶段使用。

内存后端

该缓存式后端将内容存在 django.core.mail 模块的某个属性值中。outbox 属性会在第一条消息发送时创建。这是一个列表,每项都是一个 EmailMessage 实例,代表一条要被发送的消息。

为了使用该后端,将以下代码加入你的配置中:

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

该后端不是为了在生产环境使用的——出于方便的目的,让你在开发阶段使用。

Django 的测试器 自动为测试使用这个后端

虚拟后端

就像该后端的名字表示的一样,该后端对你发送的消息什么也不做。指定该后端,将以下代码加入你的配置中:

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

该后端不是为了在生产环境使用的——出于方便的目的,让你在开发阶段使用。

自定义邮件后端

若你需要修改邮件发送的方式,你可以编写自定义的邮件后端。后面要在 EMAIL_BACKEND 配置项中指定你的后端类的路径。

自定义邮件后端需继承 django.core.mail.backends.base 模块中的 BaseEmailBackend 类。自定义邮件后端类必须实现 send_messages(email_messages) 方法。该方法接受一个包含 EmailMessage 对象的列表。若你的后端还处理了持久性会话和连接,你还需要实现 open()close() 方法。可以参考 smtp.EmailBackend 的实现。

发送多封邮件

创建和关闭 SMTP 连接(或其它网络连接)是一项耗时的进程。如果你有很多封邮件要发送,复用连接就显得很有意义,而不是在每次发送邮件时创建和关闭连接。

有两种方式可以让邮件后端复用连接。

首先,你可以使用 send_messages() 方法。send_messages() 接受一个包含 EmailMessage (或其子类)实例的列表,并在发送它们时复用同一条连接。

举个例子,你有一个函数,叫做 get_notification_email() ,他会返回一个包含 EmailMessage 对象的列表。这些对象是你想要发送的定期邮件。你可以简单的调用一次 send_messages 来发送它们:

from django.core import mail

connection = mail.get_connection()  # Use default email connection
messages = get_notification_email()
connection.send_messages(messages)

在该例子中,调用 send_messages() 在后端创建了一条连接,发送完邮件列表后,关闭了这条连接。

第二种方式是在后端使用 open()close() 手动控制连接。send_messages() 在连接已经建立的情况下不会控制连接的开关,故此,若你手动打开了连接,你可以决定何时关闭它。比如:

from django.core import mail

connection = mail.get_connection()

# Manually open the connection
connection.open()

# Construct an email message that uses the connection
email1 = mail.EmailMessage(
    "Hello",
    "Body goes here",
    "from@example.com",
    ["to1@example.com"],
    connection=connection,
)
email1.send()  # Send the email

# Construct two more messages
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"],
)

# Send the two emails in a single call -
connection.send_messages([email2, email3])
# The connection was already open so send_messages() doesn't close it.
# We need to manually close the connection.
connection.close()

为了开发配置邮件

曾经有很多次,你并不想 Django 真的发送邮件。举个例子,在开发网站时,你可能并不期望发送成千上万封邮件——但你想要确保这些邮件将会在正确的时间,包含正确的内容,发送给正确的人。

The easiest way to configure email for local development is to use the console email backend. This backend redirects all email to stdout, allowing you to inspect the content of mail.

文件 邮件后端在开发时也很有用——这个后端将每次 SMTP 连接的内容输出至一个文件,你可以在你闲暇时查看这个文件。

Another approach is to use a "dumb" SMTP server that receives the emails locally and displays them to the terminal, but does not actually send anything. The aiosmtpd package provides a way to accomplish this:

python -m pip install aiosmtpd

python -m aiosmtpd -n -l localhost:8025

This command will start a minimal SMTP server listening on port 8025 of localhost. This server prints to standard output all email headers and the email body. You then only need to set the EMAIL_HOST and EMAIL_PORT accordingly. For a more detailed discussion of SMTP server options, see the documentation of the aiosmtpd module.

关于发送邮件的单元测试资料,参见测试文档中 邮件服务 章节。