寄送電子郵件

雖然 Python 通過 smtplib 模組提供電子郵件寄送介面,但 Django 在其之上提供了幾個輕量級的包裝函式。提供這些包裝函式是為了使電子郵件寄送更快速,以幫助在開發期間測試電子郵件寄送,並為無法使用 SMTP 的平台提供支援。

程式碼位於 django.core.mail 模組中。

快速範例

使用 send_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,
)

當需要額外的電子郵件寄送功能時,請使用 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,
    "from@example.com",
    ["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()

郵件是使用 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)[原始碼]

在大多數情況下,您可以使用 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)[原始碼]

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()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() 是一個快捷方式,用於發送電子郵件給網站管理員,這些管理員定義在 ADMINS 設定中。

mail_admins() 會在主旨前加上 EMAIL_SUBJECT_PREFIX 設定的值,預設為 "[Django] "

電子郵件的「From:」標頭將會是 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,他們都會出現在「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)

防止標頭注入

標頭注入 是一種安全漏洞,攻擊者會插入額外的電子郵件標頭,以控制您的腳本產生的電子郵件訊息中的「To:」和「From:」。

上述的 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, ["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 類別的輕薄包裝器。

並非 EmailMessage 類別的所有功能都可以透過 send_mail() 和相關的包裝函數使用。如果您想使用進階功能,例如密件副本收件人、檔案附件或多部分電子郵件,您需要直接建立 EmailMessage 實例。

注意

這是一個設計上的考量。send_mail() 和相關函數原本是 Django 提供的唯一介面。然而,它們接受的參數列表隨著時間推移而慢慢增加。為電子郵件訊息採用更物件導向的設計,並僅為了向後相容性而保留原始函數,是合理的做法。

EmailMessage 負責建立電子郵件訊息本身。然後 電子郵件後端 負責發送電子郵件。

為了方便起見,EmailMessage 提供了一個 send() 方法來發送單一電子郵件。如果您需要發送多則訊息,電子郵件後端 API 提供了替代方案

EmailMessage 物件

class EmailMessage[原始碼]

EmailMessage 類別會使用以下參數初始化(如果使用位置引數,則會按照給定的順序)。所有參數都是選用的,可以在呼叫 send() 方法之前的任何時間設定。

  • subject:電子郵件的主旨行。

  • body:內文文字。這應該是純文字訊息。

  • from_email:寄件人的地址。 fred@example.com"Fred" <fred@example.com> 形式都是合法的。如果省略,則會使用 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",
    "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) 會傳送訊息。如果在建立電子郵件時指定了連線,則會使用該連線。否則,將會建立預設後端的實例並使用它。如果關鍵字參數 fail_silentlyTrue,則在傳送訊息時引發的例外將會被抑制。空的收件人清單不會引發例外。如果訊息成功傳送,則會返回 1,否則會返回 0

  • message() 會建構一個 django.core.mail.SafeMIMEText 物件(Python 的 MIMEText 類別的子類別)或一個 django.core.mail.SafeMIMEMultipart 物件,其中包含要傳送的訊息。如果您需要擴展 EmailMessage 類別,您可能需要覆寫此方法,以將您想要的內容放入 MIME 物件中。

  • recipients() 會返回訊息的所有收件人清單,無論它們是否記錄在 toccbcc 屬性中。這是您在建立子類別時可能需要覆寫的另一個方法,因為在傳送訊息時,需要告知 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[原始碼]

EmailMessage 的子類別,具有額外的 attach_alternative() 方法,用於在電子郵件中包含額外的訊息本文版本。所有其他方法 (包括類別初始化) 都直接繼承自 EmailMessage

attach_alternative(content, mimetype)[原始碼]

附加電子郵件中訊息本文的替代表示形式。

例如,若要傳送文字和 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()

更新預設內容類型

預設情況下,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)[原始碼]

預設情況下,呼叫 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",
    "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 發送任何電子郵件。例如,在開發網站時,您可能不希望發送數千封電子郵件 – 但您可能想要驗證電子郵件是否會在正確的條件下發送給正確的人員,以及這些電子郵件是否會包含正確的內容。

為本地開發設定電子郵件的最簡單方式是使用 主控台 電子郵件後端。此後端會將所有電子郵件重新導向到 stdout,讓您可以檢查郵件的內容。

檔案 電子郵件後端在開發期間也可能很有用 – 此後端會將每個 SMTP 連線的內容轉儲到一個檔案,您可以在空閒時檢查該檔案。

另一種方法是使用「啞」SMTP 伺服器,它會在本地接收電子郵件並將其顯示在終端機上,但實際上不會發送任何內容。 aiosmtpd 套件提供了一種實現此目標的方法。

python -m pip install aiosmtpd

python -m aiosmtpd -n -l localhost:8025

此命令將啟動一個最小的 SMTP 伺服器,監聽 localhost 的 8025 連接埠。此伺服器會將所有電子郵件標頭和電子郵件內容列印到標準輸出。您只需相應地設定 EMAIL_HOSTEMAIL_PORT 即可。如需更詳細的 SMTP 伺服器選項討論,請參閱 aiosmtpd 模組的文件。

有關在您的應用程式中進行電子郵件發送單元測試的資訊,請參閱測試文件的 電子郵件服務 章節。

回到頂部