Django 为 Python 的 email 和 smtplib 模块提供封装,简化邮件编写与发送。Django 的邮件框架还支持替换投递机制:开发时可以把邮件输出到控制台或文件,生产环境则可以使用 SMTP 服务器或邮件服务提供商。(email · smtplib)
这些代码位于 django.core.mail 模块。
快速示例
使用 send_mail() 进行简单的邮件发送。例如,发送纯文本消息:(send_mail())
from django.core.mail import send_mail send_mail( "Subject here", "Here is the message.", "from@example.com", ["to@example.com"], )
需要更多邮件发送功能时,请使用 EmailMessage 或 EmailMultiAlternatives 类。例如,要利用特定模板和自定义邮件头,发送同时包含 HTML 与纯文本版本的多部分邮件,可以采用下面的方法:(EmailMessage · EmailMultiAlternatives)
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="Subject here", body=text_content, from_email="from@example.com", to=["to@example.com"], headers={"List-Unsubscribe": "<mailto:unsub@example.com>"}, ) # Lastly, attach the HTML content to the email instance and send. msg.attach_alternative(html_content, "text/html") msg.send()
配置邮件
新建 Django 项目默认不配置真实邮件发送。通过 startproject 创建的项目会把邮件打印到控制台,方便开发;如果没有定义 MAILERS 设置,则会出现 MailerDoesNotExist 错误。(startproject · MAILERS)
使用 MAILERS 设置告诉 Django 如何发送邮件。例如,通过本机运行的 SMTP 服务器发送:(MAILERS)
MAILERS = { "default": { "BACKEND": "django.core.mail.backends.smtp.EmailBackend", "OPTIONS": { "host": "localhost", }, }, }
Django 将邮件发送过程抽象为“邮件后端”类。“邮件后端”章节列出了 Django 自带的后端。(邮件后端)
上例使用 Django 的 SMTP 邮件后端,通过标准 SMTP 协议发送。这一后端适用于许多生产配置,包括你自己基础设施中的 SMTP 服务器,以及大多数商业邮件服务提供商(ESP)。此外,还有第三方邮件后端,可以直接对接 ESP 的 API,或添加其他发送功能。(SMTP email backend · SMTP · third-party email backends)
开发或测试期间,通常根本不希望发送真实邮件。Django 的测试运行器会自动覆盖 MAILERS 配置,改用内存邮件后端。这既防止测试用例发送真实邮件,又让测试能够访问原本准备发送的消息。“为了开发配置邮件”章节讨论了其他方法。(automatically overrides · MAILERS · memory email
backend · 为了开发配置邮件)
Django 6.1 的变更:在较早版本中,Django 默认通过 localhost 上运行的 SMTP 服务器发送邮件,使用如今已经弃用的 EMAIL_BACKEND 及相关设置。(EMAIL_BACKEND)
自 6.1 版本起弃用:在 Django 2028 之前,如果没有定义 MAILERS,仍采用旧行为:Django 默认使用 localhost 上的 SMTP 服务器,但会发出弃用警告。从 Django 2028 开始,未定义 MAILERS 就尝试发送邮件会导致 MailerDoesNotExist 错误。(MAILERS)
现有项目可以在 settings.py 中添加 MAILERS,提前启用新行为。请参阅“将邮件迁移到 mailers”。(MAILERS · Migrating email to mailers)
多个 mailer
有时不同类型的邮件需要采用不同发送方式,例如内部与外部邮件;不同地区用户使用不同 SMTP 服务器;事务通知与批量营销邮件使用不同服务。
MAILERS 设置可以定义多个邮件配置。例如:(MAILERS)
import os MAILERS = { "default": { "BACKEND": "django.core.mail.backends.smtp.EmailBackend", "OPTIONS": { "host": "smtp.example.net", "use_tls": True, "username": os.environ["EMAIL_ACCOUNT_ID"], "password": os.environ["EMAIL_API_KEY"], }, }, "notifications": { "BACKEND": "example.third.party.EmailBackend", "OPTIONS": { "api_key": os.environ["THIRD_PARTY_API_KEY"], "region": "eu", }, }, "admin": { "BACKEND": "django.core.mail.backends.smtp.EmailBackend", "OPTIONS": { "host": "localhost", }, }, }
这里定义了三个 mailer 配置:
- default 通过 smtp.example.net 上的 SMTP 服务器发送,连接使用 TLS 保护。它从环境变量读取账号 ID 与 API 密钥,用作 SMTP 身份验证的用户名与密码。许多 SMTP 服务采用这类验证方式的变体。
- notifications 通过一个假设的商业邮件服务发送,使用直接连接该服务 API 的第三方 EmailBackend。如何查找真实的社区维护邮件后端包,请参阅“第三方后端”。(Third-party backends)
- admin 通过 localhost 上运行的 SMTP 服务器发送,无需其他选项。
采用此配置后,可以向 Django 的邮件发送函数传入 using 参数,指定某个 mailer 配置:(email sending functions)
from django.core.mail import send_mail send_mail( "Account activated", "Congratulations, you're all ready to use our Django app!", "from@example.com", ["user@example.com"], using="notifications", )
未指定 using 时,Django 使用 default 配置所定义的 mailer。
对于替你发送邮件的可复用应用或 Django 功能,可能会提供选择特定 mailer 的选项。例如,Django 日志系统的 AdminEmailHandler 允许在 using 选项中指定 mailer 配置。(AdminEmailHandler)
发送消息
django.core.mail 提供方便发送邮件的函数,也提供类来构建和发送包含附件及多种内容类型的复杂邮件。
注意
通过 django.core.mail 发送的邮件的字符编码由 DEFAULT_CHARSET 设置项指定。(DEFAULT_CHARSET)
send_mail()
django.core.mail.send_mail() 发送一封邮件。
参数 subject, message, from_email 和 recipient_list 是必须的。
-
subject: 一个字符串。 -
message: 一个字符串。 -
from_email:字符串。如果为None,Django 将使用DEFAULT_FROM_EMAIL设置的值。 -
recipient_list: 一个字符串列表,每项都是一个邮箱地址。recipient_list中的每个成员都可以在邮件的 "收件人:" 中看到其他的收件人。
以下参数可选,使用时必须作为关键字参数传入。
- fail_silently:布尔值,默认为 False。设置为 True 时,send_mail() 会抑制发送过程中的某些错误。具体忽略哪些异常,取决于所用邮件后端。
-
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 。 - using:可选的 MAILERS 别名,用于选择发送邮件的配置。未指定时使用默认 mailer 配置。(MAILERS)
使用 using 参数时,不允许同时传入 fail_silently、auth_user、auth_password 或 connection。
返回值会是成功发送的信息的数量(只能是 0 或 1 ,因为同时只能发送一条消息)。
自 6.0 版本起弃用:将 fail_silently 及其后的参数作为位置参数传入的方式已弃用。
自 6.1 版本起弃用:fail_silently、auth_user、auth_password 和 connection 参数已弃用。多数情况下,可以配置适当的 MAILERS 并使用 using 代替。请参阅“替换 fail_silently”“替换 auth_user 和 auth_password”及“替换 get_connection() 和 connection 参数”。(MAILERS · Replacing fail_silently · Replacing auth_user and auth_password · Replacing get_connection() and connection arguments)
增加了 using 参数。
旧版本在同时提供 connection 时会忽略 fail_silently=True、auth_user 和 auth_password。现在这种调用会抛出 TypeError。
send_mass_mail()
django.core.mail.send_mass_mail() 用于批量发送邮件。
datatuple 是一个元组,形式如下:
(subject, message, from_email, recipient_list)
fail_silently、auth_user、auth_password 和 connection 的作用与 send_mail() 中相同。使用时必须作为关键字参数传入,并且不能与 using 同时使用。(send_mail())
关键字参数 using 是可选的 MAILERS 别名,用于选择发送配置。未指定时使用默认 mailer。(MAILERS)
datatuple 中每个独立元素都会生成一封独立邮件。与 send_mail() 一样,同一 recipient_list 中的收件人都能在邮件的 To 字段中看到其他地址。(send_mail())
举个例子,以下代码会向两个不同的收件人列表发送两封不同的邮件,却复用了同一条连接:
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))
返回值是成功发送的消息的数量。
自 6.0 版本起弃用:将 fail_silently 及其后的参数作为位置参数传入的方式已弃用。
自 6.1 版本起弃用:fail_silently、auth_user、auth_password 和 connection 参数已弃用。多数情况下,可以配置适当的 MAILERS 并使用 using 代替。请参阅“替换 fail_silently”“替换 auth_user 和 auth_password”及“替换 get_connection() 和 connection 参数”。(MAILERS · Replacing fail_silently · Replacing auth_user and auth_password · Replacing get_connection() and connection arguments)
增加了 using 参数。
旧版本在同时提供 connection 时会忽略 fail_silently=True、auth_user 和 auth_password。现在这种调用会抛出 TypeError。
send_mass_mail() 与 send_mail() 的区别
send_mass_mail() 与反复调用 send_mail() 的主要区别在于:send_mail() 每次执行都会打开邮件服务器连接,而 send_mass_mail() 用同一条连接发送全部消息,因此效率略高。(send_mass_mail() · send_mail())
向 send_mail() 提供多个收件地址会发送一封邮件,john@example.com 和 jane@example.com 都会出现在 To 字段中:(send_mail())
send_mail( "Subject", "Message.", "from@example.com", ["john@example.com", "jane@example.com"], )
send_mass_mail() 为 datatuple 的每个元素分别发送一封消息,因此 john@example.com 和 jane@example.com 各自收到一封独立邮件:(send_mass_mail())
datatuple = ( ("Subject", "Message.", "from@example.com", ["john@example.com"]), ("Subject", "Message.", "from@example.com", ["jane@example.com"]), ) send_mass_mail(datatuple)
mail_admins()
django.core.mail.mail_admins() 是定义在 ADMINS 配置项中,用于向网站所有者快速发送邮件。(ADMINS)
mail_admins() 在主题前面添加 EMAIL_SUBJECT_PREFIX 指定的前缀,默认是 "[Django] " 。(EMAIL_SUBJECT_PREFIX)
邮件头的 "发件人:" 由 SERVER_EMAIL 配置项指定。(SERVER_EMAIL)
创建这个方法是为了方便和可读性。
若提供了 html_message,会使邮件成为 multipart/alternative 的实例, message 的内容类型则是 text/plain ,并且 html_message 的内容类型是 text/html 。
关键字参数 using 是可选的 MAILERS 别名,用于选择发送配置。未指定时使用默认 mailer。(MAILERS)
自 6.0 版本起弃用:将 fail_silently 及其后的参数作为位置参数传入的方式已弃用。
自 6.1 版本起弃用:fail_silently 和 connection 参数已弃用。多数情况下,可以配置适当的 MAILERS 并使用 using 代替。请参阅“替换 fail_silently”及“替换 get_connection() 和 connection 参数”。(MAILERS · Replacing fail_silently · Replacing get_connection() and connection arguments)
增加了 using 参数。
旧版本在同时提供 connection 时会忽略 fail_silently=True。现在这种调用会抛出 TypeError。
mail_managers()
django.core.mail.mail_managers() 类似 mail_admins(),但它向 MANAGERS 指定的管理员们发送邮件。(MANAGERS)
关键字参数 using 是可选的 MAILERS 别名,用于选择发送配置。未指定时使用默认 mailer。(MAILERS)
自 6.0 版本起弃用:将 fail_silently 及其后的参数作为位置参数传入的方式已弃用。
自 6.1 版本起弃用:fail_silently 和 connection 参数已弃用。多数情况下,可以配置适当的 MAILERS 并使用 using 代替。请参阅“替换 fail_silently”及“替换 get_connection() 和 connection 参数”。(MAILERS · Replacing fail_silently · Replacing get_connection() and connection arguments)
增加了 using 参数。
旧版本在同时提供 connection 时会忽略 fail_silently=True。现在这种调用会抛出 TypeError。
EmailMessage 类
Django 的 send_mail() 和 send_mass_mail() 实际上是利用 EmailMessage 类实现的轻量封装。(send_mail() · send_mass_mail() · EmailMessage)
send_mail() 及相关封装函数并未开放 EmailMessage 的全部功能。如果需要密送收件人、文件附件或多部分邮件等高级功能,就必须直接创建 EmailMessage 实例。(EmailMessage · send_mail())
注意
这是设计上的选择。send_mail() 及相关函数最初是 Django 提供的唯一接口,但它们接受的参数列表逐渐增长。改用更加面向对象的邮件设计,并仅为向后兼容保留原有函数,是合理的选择。(send_mail())
EmailMessage 负责创建邮件消息本身,邮件后端则负责发送。(EmailMessage · email backend)
为方便使用,EmailMessage 提供 send() 方法发送单封邮件。需要发送多封时,邮件后端 API 提供另一种方式。(EmailMessage · send() · provides an alternative)
EmailMessage 类使用以下参数初始化。所有参数都可选,而且可以在调用 send() 之前随时设置。(send())
前四个参数既可以按位置传入,也可以作为关键字参数传入;按位置传入时必须遵循给定顺序:
-
subject: 邮件的主题。 -
body: 邮件内容,需要为纯文本格式。 - from_email:发件地址。既支持 fred@example.com,也支持带显示名的形式,例如 "Fred" <fred@example.com>,详见“邮件地址格式”。省略时使用 DEFAULT_FROM_EMAIL 设置。(Formatting email addresses · DEFAULT_FROM_EMAIL)
-
to: 一个包含收件人地址的列表或元组。
以下参数使用时必须按关键字传入:
-
cc: 一个包含收件人地址的列表或元组,指定“抄送”对象。 - bcc:发送邮件时用作密送收件人的地址列表或元组。
-
reply_to: 一个包含收件人地址的列表或元组,指定“回复”对象。 - attachments:消息附件列表。每项可以是 MIMEPart 或 EmailAttachment 实例,也可以是包含 filename、content、mimetype 的元组。Django 6.0 的变更:新增了 attachments 列表对 MIMEPart 对象的支持。自 6.0 起弃用:attachments 对 Python 旧版 MIMEBase 对象的支持已弃用,请改用 MIMEPart。(MIMEPart · EmailAttachment · MIMEBase)
-
headers: 一个字典,包含邮件中额外的头信息。字典的关键字是头的名称,值为头的值。需要由调用者确保头名和值的正确性。对应的属性是extra_headers。 - connection:邮件后端实例。使用 send_messages() 时忽略此参数。自 6.1 起弃用:connection 参数已弃用。请改为定义具有所需连接选项的 MAILERS 配置,然后调用 EmailMessage.send(using="…"),传入该配置别名。参阅“将邮件迁移到 mailers”。(email backend · send_messages() · MAILERS · EmailMessage.send(using="…") · Migrating email to mailers)
自 6.0 版本起弃用:除前四个参数外,将其他参数作为位置参数传入的方式已弃用。
例如:
from django.core.mail import EmailMessage email = EmailMessage( subject="Hello", body="Body goes here", from_email="from@example.com", to=["to1@example.com", "to2@example.com"], bcc=["bcc@example.com"], reply_to=["another@example.com"], headers={"Message-ID": "foo"}, )
这个类拥有以下方法:
发送消息。成功发送返回 1,否则返回 0。收件人列表为空时返回 0,不会抛出异常。
可选关键字参数 using 指定发送邮件使用的 MAILERS 别名。不提供时使用默认 mailer 配置。(MAILERS)
如果构建邮件时指定了已经弃用的 connection,就使用该连接。同时提供 connection 和 using 会报错。
如果已经弃用的关键字参数 fail_silently 为 True,则忽略发送过程中某些依赖后端的异常。同时提供 fail_silently 和 using 会报错。
增加了 using 参数。
旧版本在同时提供 connection 时会忽略 fail_silently=True。现在这种调用会抛出 TypeError。
自 6.1 版本起弃用:fail_silently 参数已弃用。替代方式见“替换 fail_silently”。(Replacing fail_silently)
构建并返回表示待发送消息的 Python email.message.EmailMessage 对象。(email.message.EmailMessage)
关键字参数 policy 用于指定更新和序列化消息表示形式的规则集,必须是 email.policy.Policy 对象,默认为 email.policy.default。某些情况下可能需要 SMTP、SMTPUTF8 或自定义策略。例如,SMTP 邮件后端使用 SMTP 策略,保证采用 SMTP 协议要求的 \r\n 行结束符。(email.policy.Policy · email.policy.default · SMTP · SMTPUTF8 · SMTP email backend)
如果要扩展 Django 的 EmailMessage 类,通常需要重写此方法,将所需内容放入 Python EmailMessage 对象。(EmailMessage)
Django 6.0 的变更:新增了 policy 关键字参数,返回类型也更新为 EmailMessage 实例。(EmailMessage)
返回消息全部收件人的列表,无论他们记录在 to、cc 还是 bcc 属性中。子类化时可能需要重写这个方法,因为发送消息时必须把完整收件人列表告知 SMTP 服务器。如果你的类新增了指定收件人的方式,也必须通过这个方法返回这些收件人。
创建新附件并加入消息。attach() 有两种调用方式:
- 可以传入三个参数:filename、content 和 mimetype。filename 是附件在邮件中显示的文件名;content 是附件包含的数据;mimetype 是可选 MIME 类型。省略 mimetype 时,根据文件名猜测 MIME 内容类型。例如:
message.attach("design.png", img_data, "image/png")。如果指定 message/rfc822,content 可以是 django.core.mail.EmailMessage、Python 的 email.message.EmailMessage 或 email.message.Message。对于以 text/ 开头的 mimetype,内容应为字符串。二进制数据会尝试用 UTF-8 解码;如果失败,MIME 类型改为 application/octet-stream,数据本身不会修改。message.attach("design.png", img_data, "image/png")
(django.core.mail.EmailMessage · email.message.EmailMessage · email.message.Message)
- 对于需要额外邮件头或参数的附件,可以给 attach() 传入一个 Python MIMEPart 对象,它会直接附加到最终消息中。例如,下面附加带 Content-ID 的内联图片。Python 的 email.contentmanager.set_content() 文档说明了 MIMEPart.set_content() 支持的参数。Django 6.0 的变更:增加 MIMEPart 附件支持。自 6.0 起弃用:email.mime.base.MIMEBase 附件支持已弃用,请改用 MIMEPart。
import email.utils from email.message import MIMEPart from django.core.mail import EmailMultiAlternatives message = EmailMultiAlternatives(...) image_data_bytes = ... # Load image as bytes # Create a random Content-ID, including angle brackets cid = email.utils.make_msgid() inline_image = email.message.MIMEPart() inline_image.set_content( image_data_bytes, maintype="image", subtype="png", # or "jpeg", etc. depending on the image type disposition="inline", cid=cid, ) message.attach(inline_image) # Refer to Content-ID in HTML without angle brackets message.attach_alternative(f'… <img src="cid:{cid[1:-1]}"> …', "text/html")
(MIMEPart · email.contentmanager.set_content() · email.mime.base.MIMEBase)
利用文件系统中的文件创建附件。传入文件路径以及可选 MIME 类型。省略 MIME 类型时,根据文件名猜测。可以这样使用:
message.attach_file("/images/weather_map.png")
对于以 text/ 开头的 MIME 类型,二进制数据的处理与 attach() 相同。(attach())
用于存储邮件附件的具名元组。
具名元组包含以下字段:
-
filename -
content -
mimetype
发送可选的内容类型。
发送多个内容版本
在邮件中包含多个内容版本可能很有用,典型例子是同时发送纯文本与 HTML 版本。Django 的邮件库可以通过 EmailMultiAlternatives 类实现。(EmailMultiAlternatives)
这是 EmailMessage 的子类,允许通过 attach_alternative() 为邮件正文增加其他版本。它直接继承 EmailMessage 的所有方法,包括初始化方法。(EmailMessage · attach_alternative())
EmailAlternative 具名元组的列表,尤其适合在测试中使用:(EmailAlternative)
self.assertEqual(len(msg.alternatives), 1) self.assertEqual(msg.alternatives[0].content, html_content) self.assertEqual(msg.alternatives[0].mimetype, "text/html")
替代内容只能通过 attach_alternative() 添加,或传入构造函数。(attach_alternative())
在电子邮件中附加消息正文的替代表示。
例如,要发送文本和 HTML 组合,你可以这样写:
from django.core.mail import EmailMultiAlternatives subject = "hello" from_email = "from@example.com" to = "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()
返回布尔值,表示指定文本是否同时包含在邮件正文及所有附加的 text/* MIME 替代内容中。
测试邮件时这可能很有用。例如:
def test_contains_email_content(self): subject = "Hello World" from_email = "from@example.com" to = "to@example.com" msg = EmailMultiAlternatives(subject, "I am content.", from_email, [to]) msg.attach_alternative("<p>I am content.</p>", "text/html") self.assertIs(msg.body_contains("I am content"), True) self.assertIs(msg.body_contains("<p>I am content.</p>"), False)
用于存储邮件内容替代版本的具名元组。
具名元组包含以下字段:
-
content -
mimetype
更新默认内容类型
默认情况下,EmailMessage 的 body 参数使用 text/plain MIME 类型。保留默认值是良好做法,因为这样无论收件人使用什么邮件客户端,都能阅读邮件。但如果确信收件人可以处理其他内容类型,可以通过 EmailMessage 的 content_subtype 属性改变主内容类型。主类型始终为 text,但子类型可以改变。例如:(EmailMessage)
msg = EmailMessage(subject, html_content, from_email, [to]) msg.content_subtype = "html" # Main content is now text/html msg.send()
安全地发送邮件
任何能够发送邮件的公开网站,最终都会遇到利用它发送垃圾邮件、钓鱼邮件或其他恶意内容的企图。全面讨论邮件发送漏洞超出了 Django 文档范围,但网上有许多参考资料。以下两项适合作为起点:
- 普林斯顿大学关于防止网页表单邮件滥用的指南。虽然这是为校内 Drupal Site Builder 用户编写的参考资料,但几乎全部建议也适用于 Django 或其他 Web 框架构建的网站。(preventing email abuse in web forms)
- OWASP 的《身份系统中的电子邮件验证与核验速查表》。它主要讨论认证场景中的邮件使用。许多建议已由 django.contrib.auth 及其他 Django 功能处理,但速率限制、保障邮件地址更改流程等事项仍由开发者负责。(Email Validation and Verification in Identity Systems Cheat Sheet · django.contrib.auth)
如果网站可以向未经验证的地址发送邮件,尤其需要认真考虑邮件可能如何被滥用,并采取缓解措施。订阅通讯、抄送或自动回复发件人的联系表单,以及“分享本页”等功能,都是容易被攻击的目标。
邮件地址格式
邮件地址除了 user@domain 形式外,还可以带“友好”的显示名。例如,可以在 DEFAULT_FROM_EMAIL 中包含公司名称:(DEFAULT_FROM_EMAIL)
DEFAULT_FROM_EMAIL = '"Example, Inc." <contact@example.com>'
Example, Inc. 周围的双引号不可省略,否则逗号会被解释为两个地址的分隔符。像上例那样手工写出的固定地址是安全的,但从可变部分,尤其是不可信输入拼接地址,需要更加谨慎。
警告
绝不要使用字符串格式化,从可变部分构造邮件地址。例如 f'"{name}" <{email}>' 是不安全的。
邮件地址头与 HTML、SQL 类似,有复杂的语法规则。因此,拼接字符串构造邮件头会产生注入漏洞。即便已经验证 email 的格式,攻击者仍可能利用 name 部分注入更多地址。(validated)
应始终使用专门用于格式化邮件地址、经过充分测试的库,例如 Python 的 email.headerregistry.Address 类。它替代旧版 formataddr(),后者不支持国际化域名。(email.headerregistry.Address · formataddr())
例如,向用户发送邮件时包含用户全名,下面的 user 是默认 User 模型实例:(User)
from django.core.mail import send_mail from email.headerregistry import Address def send_mail_to_user(user, subject, body, from_email=None): # Safely create an email address with the user's name. # (addr_spec is the technical term for the user@domain address.) address = Address( display_name=user.get_full_name(), addr_spec=user.email, ) send_mail(subject, body, from_email, [address])
如示例所示,Django 内置邮件后端支持在任意地址字段直接使用 Address 对象。许多自定义及第三方后端也支持。如果某个特定后端因此产生 TypeError 或其他问题,可以调用 str(address),将其转换为安全且格式正确的字符串。(Address)
防止头注入
邮件头注入是一种安全攻击:攻击者操纵邮件头,改变原定发件人、收件人、主题,甚至可能改变整封可见邮件正文。(Email header injection)
一种邮件头注入利用地址头语法。根据用户输入构建邮件地址时,你必须负责防止它,详见上面的“邮件地址格式”。(Formatting email addresses)
另一种可能更熟悉的攻击是 CRLF 注入,利用回车和换行字符插入额外邮件头。Django 会在尝试发送消息时检查各邮件头字段:如果出现这些字符,则抛出 ValueError,从而防止这种攻击。(ValueError)
Django 6.0 的变更:旧版本对某些无效邮件头抛出 django.core.mail.BadHeaderError,现在已替换为 ValueError。
Django 的 CRLF 防护依赖于 EmailMessage.message() 使用 Python 的现代 EmailPolicy。未调用该函数,或者调用时使用旧版 compat32 策略的自定义后端,必须自行实现 CRLF 注入防护。(EmailPolicy · EmailMessage.message() · compat32)
高效发送多封消息
创建和关闭 SMTP 连接(或其它网络连接)是一项耗时的进程。如果你有很多封邮件要发送,复用连接就显得很有意义,而不是在每次发送邮件时创建和关闭连接。
有两种方式要求邮件后端复用连接。两者都需要从 mail.mailers 获取后端实例,并使用后端 API。(mail.mailers · backend's API)
第一种方式是使用后端的 send_messages()。该方法接受 EmailMessage 或其子类实例列表,并通过同一连接发送所有消息。(EmailMessage)
例如,get_notification_emails() 函数返回一个 EmailMessage 对象列表,代表要定期发送的邮件。可以一次调用 send_messages() 发送它们:(EmailMessage)
from django.core import mail email_list = get_notification_emails() # Use the default mailer. You could substitute # mail.mailers["alias"] for a specific mailer. backend = mail.mailers.default backend.send_messages(email_list)
此例中,send_messages() 打开后端连接,发送消息列表,然后关闭连接。send_mass_mail() 就是这样实现的。(send_mass_mail())
第二种方式是使用后端的 open() 和 close() 手工控制连接。如果连接已经打开,send_messages() 就不会打开或关闭它。因此,手工打开连接后,你可以自行控制何时关闭。例如:
from django.core import mail # Use the "notifications" mailer configuration. backend = mail.mailers["notifications"] # Manually open the connection. backend.open() # Construct an email message. (Passing None as the third argument # uses settings.DEFAULT_FROM_EMAIL as the "From:" address.) email1 = mail.EmailMessage("Hi", "Message", None, ["to1@example.com"]) # Send the email. The connection was already open, so send_messages() # leaves it open after sending. backend.send_messages([email1]) # Construct and send two more messages. The connection is still open. email2 = mail.EmailMessage("Hi", "Message", None, ["to2@example.com"]) email3 = mail.EmailMessage("Hi", "Message", None, ["to3@example.com"]) backend.send_messages([email2, email3]) # Because we opened it, we need to manually close the connection. backend.close()
手工打开后端连接时,你必须确保它被关闭。上例实际上有一个问题:发送邮件期间发生异常时,连接不会关闭。可以通过 try-finally 修复,但更好的方法是将后端实例用作上下文管理器,它会按需自动调用 open() 和 close()。
下面与前例等价,但使用后端作为上下文管理器,避免错误发生后连接仍保持打开:(context manager)
from django.core import mail # Use mail.mailers[...] as a context manager. with mail.mailers["notifications"] as backend: # The backend connection is automatically opened inside the context. email1 = mail.EmailMessage("Hi", "Message", None, ["to1@example.com"]) backend.send_messages([email1]) # The connection is still open, and is reused for the second send. email2 = mail.EmailMessage("Hi", "Message", None, ["to2@example.com"]) email3 = mail.EmailMessage("Hi", "Message", None, ["to3@example.com"]) backend.send_messages([email2, email3]) # After exiting the context (either normally or because of an error), # the backend connection is automatically closed.
邮件后端
发送邮件的动作是由邮件后端执行的。
Django 自带多个邮件后端。除 SMTP 后端外,它们主要用于测试和开发。如果内置后端不能满足需要,还可以使用第三方包。也可以继承某个内置后端修改行为,甚至自行编写后端。(third-party packages · write your own email
backend)
SMTP 后端
SMTP 邮件后端连接 SMTP 服务器发送邮件。使用时将 BACKEND 设为 django.core.mail.backends.smtp.EmailBackend。(BACKEND)
SMTP 后端支持以下 OPTIONS:(OPTIONS)
- host(必填):SMTP 服务器主机名或 IP 地址。
- port:SMTP 主机连接端口。省略时,根据 use_tls 和 use_ssl 使用相应协议标准端口:TLS 为 587,SSL 为 465,非加密连接为 25。
- username 和 password:服务器要求 SMTP 身份验证时设置,即 SMTP AUTH 凭据,有时称为 SMTP 登录。用户名虽然通常是邮件地址,但不能与默认 From 地址混淆;后者由 DEFAULT_FROM_EMAIL 和 SERVER_EMAIL 定义。(DEFAULT_FROM_EMAIL · SERVER_EMAIL)
- use_tls 或 use_ssl:将其中一个设为 True,以安全协议连接服务器。use_tls 使用显式 TLS;use_ssl 使用 SSL,即隐式 TLS。
- ssl_certfile 和 ssl_keyfile:如果 SMTP 服务器 SSL/TLS 连接需要客户端证书认证,用这些选项指定 PEM 格式证书链及私钥文件路径。如果证书文件包含私钥,可以省略独立密钥文件。这些选项不用于私有 CA 或自签名 SMTP 服务器证书,见下文对应章节。注意,它们不会执行证书有效性检查,而是传给底层 SSL 连接。证书链与私钥的处理详见 Python 的 ssl.SSLContext.wrap_socket() 文档。(Private and self-signed SMTP server certificates · ssl.SSLContext.wrap_socket())
- timeout:SMTP 连接及其他阻塞操作的超时时间,单位为秒。未指定时取 socket.getdefaulttimeout();默认无超时,即 None,意味着 SMTP 操作可能无限期阻塞。(socket.getdefaulttimeout())
- fail_silently:设为 True 后,发送时忽略某些错误。打开 SMTP 连接时忽略所有 OSError,与服务器通信时忽略 smtplib.SMTPException。它会同时抑制暂时网络故障及严重配置问题,但并非忽略全部错误;消息序列化问题不会被静默忽略。该选项用于向后兼容,不推荐用于典型场景。(OSError · smtplib.SMTPException)
举例:
MAILERS = { "default": { "BACKEND": "django.core.mail.backends.smtp.EmailBackend", "OPTIONS": { "host": "smtp.example.net", "use_tls": True, "username": "my-app", "password": os.environ["MY_APP_SMTP_PASSWORD"], "timeout": 10, }, }, }
自 6.1 版本起弃用:未定义 MAILERS 时,Django 将 SMTP 后端作为默认 mailer,即默认 EMAIL_BACKEND,连接 localhost 的 25 端口。Django 7.0 将移除此行为,不再提供默认 mailer 配置。(MAILERS · EMAIL_BACKEND)
未定义 MAILERS 而使用 SMTP 后端时,上述选项分别取自已经弃用的 EMAIL_HOST、EMAIL_PORT、EMAIL_HOST_USER、EMAIL_HOST_PASSWORD、EMAIL_USE_TLS、EMAIL_USE_SSL、EMAIL_SSL_KEYFILE、EMAIL_SSL_CERTFILE 和 EMAIL_TIMEOUT 设置。没有与 fail_silently 选项对应的设置。(MAILERS · EMAIL_HOST · EMAIL_PORT · EMAIL_HOST_USER · EMAIL_HOST_PASSWORD · EMAIL_USE_TLS · EMAIL_USE_SSL · EMAIL_SSL_KEYFILE · EMAIL_SSL_CERTFILE · EMAIL_TIMEOUT)
不建议直接实例化 EmailBackend 类,请通过 mailers 获取后端实例。(mailers)
不经过 mailers 而直接构造 SMTP EmailBackend 时,它以关键字参数接受上述选项,默认值取自对应的已弃用 EMAIL_* 设置。host 非必填,默认为 localhost;port 默认是 25,即使 use_tls 或 use_ssl 为 True 也如此。(mailers)
已经定义 MAILERS 时,尝试直接创建 SMTP EmailBackend 会抛出 AttributeError。(MAILERS)
自 6.1 版本起弃用:Django 2028 将不再支持直接构造 EmailBackend 实例。使用未记录的调用方式,默认参数处理会与较早版本不同。
私有及自签名 SMTP 服务器证书
如果 SMTP 服务器使用私有证书颁发机构(CA)的 SSL 证书,应把该 CA 根证书加入客户端,即运行 Django 的机器的系统 CA 证书包。服务器使用自签名证书时,也应将该证书加入客户端系统 CA 证书包,以便信任它。SMTP 后端的 ssl_certfile 选项不能用于 CA 根证书或自签名证书。
请按平台说明添加系统 CA 证书。如果不能或不想修改系统证书包,也可以使用 OpenSSL 的 SSL_CERT_FILE 或 SSL_CERT_DIR 环境变量指定自定义证书包。
对于更复杂场景,可以继承 SMTP 后端,通过 ssl.SSLContext.load_verify_locations() 向它的 ssl_context 添加根证书。(ssl.SSLContext.load_verify_locations())
控制台后端
控制台后端不会发送真实邮件,而是将原本要发送的邮件写入标准输出。将 BACKEND 设为 django.core.mail.backends.console.EmailBackend 即可使用。(BACKEND)
控制台后端支持以下 OPTIONS:(OPTIONS)
- stream:接收输出的类流对象,默认为 stdout。
- fail_silently:设为 True 后,忽略向流写入消息时的全部错误,包括序列化错误。该选项用于向后兼容,不推荐使用。
该后端不是为了在生产环境使用的——出于方便的目的,让你在开发阶段使用。
Django 6.1 的变更:startproject 创建的设置文件现在会定义 MAILERS,以控制台后端作为默认配置。(startproject · MAILERS)
文件后端
文件后端将邮件写入文件。每次在该后端打开新会话时都会新建文件。将 BACKEND 设为 django.core.mail.backends.filebased.EmailBackend 即可使用。(BACKEND)
文件后端支持以下 OPTIONS:(OPTIONS)
- file_path(必填):写入文件的目录,可以是字符串或 pathlib.Path 对象。如果目录不存在,文件后端会尝试创建。(pathlib.Path)
- fail_silently:设为 True 后,忽略向文件写入消息时的全部错误,包括序列化错误,但不忽略确保目标目录存在时的错误。该选项用于向后兼容,不推荐使用。
该后端不是为了在生产环境使用的——出于方便的目的,让你在开发阶段使用。
自 6.1 版本起弃用:未定义 MAILERS 而使用文件后端时,file_path 选项取自 EMAIL_FILE_PATH 设置。(MAILERS · EMAIL_FILE_PATH)
内存后端
locmem 后端把消息保存在 django.core.mail 的特殊属性中。首次发送消息时创建 outbox 属性;它是一个列表,每封原本要发送的消息对应一个 EmailMessage 实例。outbox 中的消息带有 sent_using 属性,标识发送所用的 MAILERS 别名。(EmailMessage · MAILERS)
使用内存后端时,将 BACKEND 设为 django.core.mail.backends.locmem.EmailBackend。它不支持任何 OPTIONS。(BACKEND · OPTIONS)
Django 测试运行器会自动切换到此后端进行测试。(automatically switches to this backend for testing)
该后端不是为了在生产环境使用的——出于方便的目的,让你在开发阶段使用。
Django 6.1 的变更:outbox 中的消息新增了 sent_using 属性。
虚拟后端
顾名思义,虚拟后端不对消息做任何处理。将 BACKEND 设为 django.core.mail.backends.dummy.EmailBackend 即可使用。它不支持任何 OPTIONS。(BACKEND · OPTIONS)
该后端不是为了在生产环境使用的——出于方便的目的,让你在开发阶段使用。
第三方后端
还有社区维护的解决方案!
Django 拥有活跃的生态。社区生态页面介绍了一些邮件后端,Django Packages 的 Email 列表中还有更多选择。(Community Ecosystem · Email grid)
可用的第三方邮件后端能够:
- 直接对接商业邮件服务提供商的 API,这些 API 经常具有 SMTP 不支持的额外功能。
- 把邮件发送转移到异步任务队列。
- 为其他邮件后端增加功能,例如强制执行禁止发送列表,或记录已发送消息。
- 提供开发与调试工具,例如沙箱捕获和浏览器内邮件预览。
自定义邮件后端
需要改变邮件发送方式时,可以编写自己的后端。使用时,将 BACKEND 设为后端类的 Python 导入路径,OPTIONS 设为该后端 __init__() 支持的关键字参数。(BACKEND · OPTIONS)
自定义后端应继承 django.core.mail.backends.base 模块中的 BaseEmailBackend,并实现 send_messages(email_messages)。该方法接收 EmailMessage 实例列表,返回成功投递的消息数量。如果后端具有持久会话或连接的概念,还应实现 open() 和 close()。参考实现见 smtp.EmailBackend。(EmailMessage)
获取邮件后端的一个实例
django.core.mail 中的 mailers 工厂返回邮件后端实例。
可以通过类似字典的 django.core.mail.mailers 对象,访问 MAILERS 中配置的 mailer:(MAILERS)
>>> from django.core.mail import mailers >>> mailers["notifications"]
指定键未定义时,抛出 MailerDoesNotExist;其他配置问题抛出 InvalidMailer。
默认 mailer 可以通过快捷属性 django.core.mail.mailers.default 访问:
>>> from django.core.mail import mailers >>> mailers.default
这等价于 mailers["default"]。如果未配置默认 mailer,会抛出 MailerDoesNotExist。
自 6.1 版本起弃用:未定义 MAILERS 时,mailers.default 会根据已弃用的 EMAIL_BACKEND 及相关设置创建后端实例,以兼容 Django 6.0 及更早版本。(MAILERS · EMAIL_BACKEND)
这一行为及相关设置将在 Django 2028 中移除。
Django 6.1 新增。
已弃用的 django.core.mail.get_connection() 创建并返回邮件后端实例。行为取决于 MAILERS 设置及调用方式。(MAILERS)
如果已经定义 MAILERS:(MAILERS)
- 不带参数调用 get_connection(),返回 mailers.default。(mailers.default)
- 只传入 fail_silently 或其他关键字参数调用 get_connection(…),会创建 MAILERS["default"] 的实例,并将这些关键字参数加入默认 mailer 的 OPTIONS。(OPTIONS)
- 传入后端导入路径调用 get_connection(backend, …) 会报错。
如果未定义 MAILERS:(MAILERS)
- 不带参数调用 get_connection(),返回 EMAIL_BACKEND 指定的后端实例。(EMAIL_BACKEND)
- 指定 backend 参数时,实例化该后端。
- 关键字参数 fail_silently 为 True 时,邮件发送过程中某些依赖后端的异常会被静默忽略。
- 其他关键字参数直接传给后端构造函数。
自 6.0 版本起弃用:将 fail_silently 按位置传入的方式已弃用。
自 6.1 版本起弃用:get_connection() 已弃用,将在 Django 7.0 中移除。请改用 mailers[alias]。迁移建议见“替换 get_connection() 和 connection 参数”。(mailers[alias] · Replacing get_connection() and connection arguments)
邮件后端 API
邮件后端实例具有以下方法:
-
open()创建一个发送邮件的长连接。 -
close()关闭当前发送邮件的连接。 - send_messages(email_messages) 发送 EmailMessage 对象列表。如果连接未打开,该调用会隐式打开连接,并在发送后关闭;如果连接已打开,发送后仍保持打开。(EmailMessage)
后端实例也可以作为上下文管理器,按需自动调用 open() 和 close()。示例见“高效发送多封消息”。(Sending many messages efficiently)
为了开发配置邮件
曾经有很多次,你并不想 Django 真的发送邮件。举个例子,在开发网站时,你可能并不期望发送成千上万封邮件——但你想要确保这些邮件将会在正确的时间,包含正确的内容,发送给正确的人。
在本地开发中配置电子邮件的最简单方法是使用 console 电子邮件后端。该后端将所有电子邮件重定向到 stdout,允许您查看邮件的内容。(console)
文件 邮件后端在开发时也很有用——这个后端将每次 SMTP 连接的内容输出至一个文件,你可以在你闲暇时查看这个文件。(文件)
另一种方法是使用模拟 SMTP 服务器,在本机接收邮件并显示到终端,但不真正发送。aiosmtpd 包可以实现:(aiosmtpd)
python -m pip install "aiosmtpd >= 1.4.5" python -m aiosmtpd -n -l localhost:8025
该命令启动一个最小 SMTP 服务器,监听 localhost 的 8025 端口。服务器把全部邮件头及正文打印到标准输出。然后只需相应设置 SMTP 后端 OPTIONS 中的 host 与 port。SMTP 服务器选项的详细讨论见 aiosmtpd 文档。(OPTIONS · aiosmtpd)
关于发送邮件的单元测试资料,参见测试文档中 邮件服务 章节。(邮件服务)
来源:Django 6.1:发送邮件。Django Software Foundation 与文档贡献者。依据 Django 文档 BSD 许可,保留6.1版本范围。 正文原已有中文,保留全文并汉化未译段落。











暂无评论内容