发送邮件

虽然 Python 通过 smtplib 模块提供了一个邮件发送接口,但 Django 在其基础上提供了一些轻量级的包装器。这些包装器的作用是加快邮件发送速度,帮助在开发过程中测试邮件发送,以及支持无法使用 SMTP 的平台。

代码位于 django.core.mail 模块中。

快速示例

对于简单的邮件发送,请使用 send_mail()。例如,要发送纯文本消息

from django.core.mail import send_mail

send_mail(
    "Subject here",
    "Here is the message.",
    "[email protected]",
    ["[email protected]"],
    fail_silently=False,
)

当需要额外的邮件发送功能时,请使用 EmailMessageEmailMultiAlternatives。例如,要发送包含 HTML 和纯文本版本的邮件,并使用特定的模板和自定义头,可以使用以下方法

from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string

# First, render the plain text content.
text_content = render_to_string(
    "templates/emails/my_email.txt",
    context={"my_variable": 42},
)

# Secondly, render the HTML content.
html_content = render_to_string(
    "templates/emails/my_email.html",
    context={"my_variable": 42},
)

# Then, create a multipart email instance.
msg = EmailMultiAlternatives(
    "Subject here",
    text_content,
    "[email protected]",
    ["[email protected]"],
    headers={"List-Unsubscribe": "<mailto:[email protected]>"},
)

# Lastly, attach the HTML content to the email instance and send.
msg.attach_alternative(html_content, "text/html")
msg.send()

邮件使用在 EMAIL_HOSTEMAIL_PORT 设置中指定的 SMTP 主机和端口发送。如果设置了 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)[source]

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

subjectmessagefrom_emailrecipient_list 参数是必需的。

  • subject:字符串。

  • message:字符串。

  • from_email:字符串。如果为 None,Django 将使用 DEFAULT_FROM_EMAIL 设置的值。

  • recipient_list:字符串列表,每个字符串都是一个电子邮件地址。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:用于发送邮件的可选电子邮件后端。如果未指定,将使用默认后端的实例。有关详细信息,请参阅 电子邮件后端 的文档。

  • 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)[source]

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",
    "[email protected]",
    ["[email protected]", "[email protected]"],
)
message2 = (
    "Another Subject",
    "Here is another message",
    "[email protected]",
    ["[email protected]"],
)
send_mass_mail((message1, message2), fail_silently=False)

返回值将是成功发送的消息数。

send_mass_mail()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)[source]

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)[source]

django.core.mail.mail_managers()mail_admins() 类似,只是它将邮件发送给网站管理者,管理者信息定义在 MANAGERS 设置中。

示例

这会向 john@example.comjane@example.com 发送一封邮件,这两个地址都显示在“收件人”中。

send_mail(
    "Subject",
    "Message.",
    "[email protected]",
    ["[email protected]", "[email protected]"],
)

这会向 john@example.comjane@example.com 发送邮件,他们各自收到单独的邮件。

datatuple = (
    ("Subject", "Message.", "[email protected]", ["[email protected]"]),
    ("Subject", "Message.", "[email protected]", ["[email protected]"]),
)
send_mass_mail(datatuple)

防止邮件头注入

邮件头注入 是一种安全漏洞,攻击者可以通过它插入额外的邮件头,从而控制脚本生成的邮件中的“收件人”和“发件人”。

上面概述的 Django 邮件函数都通过禁止邮件头值中的换行符来防止邮件头注入。如果任何 subjectfrom_emailrecipient_list 包含换行符(Unix、Windows 或 Mac 风格),则邮件函数(例如 send_mail())将引发 django.core.mail.BadHeaderErrorValueError 的子类),因此不会发送邮件。您有责任在将数据传递给邮件函数之前验证所有数据。

如果 message 在字符串开头包含邮件头,则这些邮件头将作为邮件内容的第一部分打印出来。

这是一个示例视图,它从请求的 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, ["[email protected]"])
        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 类。

并非所有 EmailMessage 类的功能都可通过 send_mail() 和相关的包装函数使用。如果您希望使用高级功能,例如密件抄送收件人、文件附件或多部分邮件,则需要直接创建 EmailMessage 实例。

注意

这是一个设计特性。 send_mail() 和相关的函数最初是 Django 提供的唯一接口。但是,它们接受的参数列表随着时间的推移一直在缓慢增长。将邮件消息迁移到更面向对象的設計并仅保留原始函数以实现向后兼容性是有意义的。

EmailMessage 负责创建邮件消息本身。然后,邮件后端 负责发送邮件。

为了方便起见,EmailMessage 提供了一个 send() 方法来发送单个邮件。如果您需要发送多封邮件,则 邮件后端 API 提供了替代方案

EmailMessage 对象

class EmailMessage[source]

EmailMessage 类使用以下参数初始化(如果使用位置参数,则按给定顺序)。所有参数都是可选的,可以在调用 send() 方法之前随时设置。

  • subject:邮件的主题行。

  • body:邮件正文。这应该是一条纯文本消息。

  • from_email:发件人的地址。 [email protected]"Fred" <[email protected]> 两种形式都是合法的。如果省略,则使用 DEFAULT_FROM_EMAIL 设置。

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

  • bcc: 发送电子邮件时在“Bcc”标头中使用的地址列表或元组。

  • connection: 一个 电子邮件后端 实例。如果您通过 send() 发送 EmailMessage 并且希望对多个邮件使用相同的连接,则使用此参数。如果省略,则在调用 send() 时会创建一个新的连接。在使用 send_messages() 时,此参数会被忽略。

  • attachments: 要添加到邮件中的附件列表。它们可以是 MIMEBase 实例,也可以是 (filename, content, mimetype) 三元组。

  • headers: 要添加到邮件的额外标头的字典。键是标头名称,值是标头值。调用方有责任确保标头名称和值符合电子邮件消息的正确格式。相应的属性是 extra_headers

  • cc: 发送电子邮件时在“Cc”标头中使用的收件人地址列表或元组。

  • reply_to: 发送电子邮件时在“Reply-To”标头中使用的收件人地址列表或元组。

例如

from django.core.mail import EmailMessage

email = EmailMessage(
    "Hello",
    "Body goes here",
    "[email protected]",
    ["[email protected]", "[email protected]"],
    ["[email protected]"],
    reply_to=["[email protected]"],
    headers={"Message-ID": "foo"},
)

该类具有以下方法

  • send(fail_silently=False) 发送邮件。如果在构建电子邮件时指定了连接,则将使用该连接。否则,将实例化并使用默认后端的实例。如果关键字参数 fail_silentlyTrue,则在发送邮件时引发的异常将被压制。空的收件人列表不会引发异常。如果邮件发送成功,则返回 1,否则返回 0

  • message() 构造一个 django.core.mail.SafeMIMEText 对象(Python 的 MIMEText 类的子类)或一个包含要发送邮件的 django.core.mail.SafeMIMEMultipart 对象。如果您需要扩展 EmailMessage 类,则可能需要重写此方法以将所需内容放入 MIME 对象中。

  • recipients() 返回邮件的所有收件人列表,无论它们是在 tocc 还是 bcc 属性中记录的。这是在子类化时可能需要重写的另一个方法,因为在发送邮件时,需要将收件人完整列表告知 SMTP 服务器。如果您在类中添加了其他指定收件人的方式,则也需要从此方法中返回它们。

  • attach() 创建一个新的文件附件并将其添加到邮件中。有两种方法可以调用 attach()

    • 您可以传递一个参数,该参数是 MIMEBase 实例。它将直接插入到生成的邮件中。

    • 或者,您可以向 attach() 传递三个参数:filenamecontentmimetypefilename 是文件附件在电子邮件中显示的名称,content 是将包含在附件中的数据,mimetype 是附件的可选 MIME 类型。如果您省略 mimetype,则 MIME 内容类型将根据附件的文件名猜测。

      例如

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

      如果您指定 mimetypemessage/rfc822,它还将接受 django.core.mail.EmailMessageemail.message.Message

      对于以 text/ 开头的 mimetype,内容应为字符串。二进制数据将使用 UTF-8 解码,如果解码失败,则 MIME 类型将更改为 application/octet-stream,并且数据将保持不变地附加。

      此外,message/rfc822 附件将不再违反 RFC 2046 第 5.2.1 节 进行 Base64 编码,这可能会导致在 EvolutionThunderbird 中显示附件时出现问题。

  • attach_file() 使用文件系统中的文件创建一个新的附件。使用要附加的文件的路径以及可选的用于附件的 MIME 类型来调用它。如果省略 MIME 类型,则将根据文件名猜测。您可以像这样使用它

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

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

发送备用内容类型

发送多个内容版本

在电子邮件中包含多个内容版本可能很有用;经典示例是发送邮件的文本和 HTML 版本。使用 Django 的电子邮件库,您可以使用 EmailMultiAlternatives 类来实现。

class EmailMultiAlternatives[source]

EmailMessage 的子类,它有一个额外的 attach_alternative() 方法,用于在电子邮件中包含邮件正文的其他版本。所有其他方法(包括类初始化)都是直接从 EmailMessage 继承的。

attach_alternative(content, mimetype)[source]

在电子邮件中附加邮件正文的备用表示形式。

例如,要发送文本和 HTML 组合,您可以编写

from django.core.mail import EmailMultiAlternatives

subject = "hello"
from_email = "[email protected]"
to = "[email protected]"
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) 发送 EmailMessage 对象列表。如果连接未打开,则此调用将隐式打开连接,并在邮件发送后关闭连接。如果连接已打开,则邮件发送后将保持打开状态。

它还可以用作上下文管理器,它将根据需要自动调用 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)[source]

默认情况下,调用 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 关键字参数来使用不同的流式对象。

要指定此后端,请在您的设置中添加以下内容

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

此后端不适用于生产环境 - 它作为开发期间可用的便利功能提供。

内存后端

'locmem' 后端将消息存储在 django.core.mail 模块的特殊属性中。当发送第一条消息时,将创建 outbox 属性。它是一个列表,其中包含每个要发送的消息的 EmailMessage 实例。

要指定此后端,请在您的设置中添加以下内容

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

此后端不适用于生产环境 - 它作为开发和测试期间可用的便利功能提供。

Django 的测试运行器 自动使用此后端进行测试

虚拟后端

顾名思义,虚拟后端对您的消息不做任何处理。要指定此后端,请在您的设置中添加以下内容

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

此后端不适用于生产环境 - 它作为开发期间可用的便利功能提供。

定义自定义邮件后端

如果您需要更改邮件的发送方式,您可以编写您自己的邮件后端。然后,您设置文件中的 EMAIL_BACKEND 设置将是您的后端类的 Python 导入路径。

自定义邮件后端应是位于 django.core.mail.backends.base 模块中的 BaseEmailBackend 的子类。自定义邮件后端必须实现 send_messages(email_messages) 方法。此方法接收一个 EmailMessage 实例列表,并返回成功传递的消息数量。如果您的后端具有持久会话或连接的概念,您还应该实现 open()close() 方法。请参考 smtp.EmailBackend 以获取参考实现。

发送多封邮件

建立和关闭 SMTP 连接(或任何其他网络连接)是一个昂贵的过程。如果您有很多邮件要发送,则重复使用 SMTP 连接是有意义的,而不是每次要发送邮件时都创建和销毁连接。

有两种方法可以告诉邮件后端重复使用连接。

首先,您可以对连接使用 send_messages() 方法。这将接收一个 EmailMessage(或子类)实例列表,并使用单个连接发送所有这些实例。因此,单个消息上设置的任何 connection 将被忽略。

例如,如果您有一个名为 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",
    "[email protected]",
    ["[email protected]"],
    connection=connection,
)
email1.send()  # Send the email

# Construct two more messages
email2 = mail.EmailMessage(
    "Hello",
    "Body goes here",
    "[email protected]",
    ["[email protected]"],
)
email3 = mail.EmailMessage(
    "Hello",
    "Body goes here",
    "[email protected]",
    ["[email protected]"],
)

# 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 发送电子邮件。例如,在开发网站时,您可能不希望发送数千封电子邮件 - 但您可能希望验证电子邮件是否会在正确的条件下发送给正确的人员,以及这些电子邮件是否包含正确的内容。

配置本地开发环境的电子邮件最简单的方法是使用 控制台 电子邮件后端。此后端将所有电子邮件重定向到 stdout,允许您检查邮件内容。

在开发过程中,文件 电子邮件后端也可能很有用 - 此后端将每个 SMTP 连接的内容转储到一个文件中,您可以随时检查该文件。

另一种方法是使用一个“哑”SMTP服务器,该服务器在本地接收电子邮件并将其显示到终端,但实际上并不发送任何内容。 aiosmtpd 包提供了一种实现此目的的方法。

python -m pip install aiosmtpd

python -m aiosmtpd -n -l localhost:8025

此命令将启动一个最小的 SMTP 服务器,侦听本地主机 8025 端口。此服务器将所有电子邮件标头和电子邮件正文打印到标准输出。然后,您只需相应地设置 EMAIL_HOSTEMAIL_PORT 即可。有关 SMTP 服务器选项的更详细讨论,请参阅 aiosmtpd 模块的文档。

有关在应用程序中对发送电子邮件进行单元测试的信息,请参阅测试文档的 电子邮件服务 部分。

返回顶部