Djano發送郵件
盡管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_HOST 和 EMAIL_PORT 配置項,分別用來指定發郵件服務器和端口。 如果SMTP服務器需要用戶認證,還須設置 EMAIL_HOST_USER 和 EMAIL_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_user 和 auth_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/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() is just like mail_admins() ,不同之處在于該方法的郵件接收人是網站負責人(manager), 可以在 MANAGERS 配置項設置網站負責人。
例子
該例同時給 john@example.com 和 jane@example.com 發送同一封郵件,這兩個郵箱地址都會出現在 “收件人/To:” 一欄:
send_mail('Subject', 'Message.', 'from@example.com', ['john@example.com', 'jane@example.com'])
該例則是分別為 john@example.com 和 jane@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_email 和recipient_list 包含換行(不管是Windows格式,還是Unix或是Mac OS 格式) ,發送函式 (比如 send_mail()) 都會拋出 django.core.mail.BadHeaderError 異常( ValueError 的一個子類),也不會發送該郵件。 因此在給郵件函式傳遞參數之前,一定要對所有數據進行驗證。
如果 message 在文本最開始處含有頭信息,那么這些頭信息就會被打印成郵件內容的第一個比特。
下面這個view例子, subject , message 和 from_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 EmailMessageEmailMessage 類使用下列參數初始化(除非使用位置參數,否則默認順序如下)。所有參數均可選,均可在調用send() 方法之前的任何時間對其賦值。
- subject: 郵件的標題行
- body: 郵件的主體內容文本,須是純文本信息。
- from_email: 發送者的地址。 fred@example.com 或 Fred <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_silently 為 True ,就會忽略郵件發送時拋出的異常。
-
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, content 和 mimetype. 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()
默認情況下,:class:~django.core.mail.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.mail 的 get_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_PASSWORD 和 EMAIL_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'
該后端并不建議在生產環境下使用–它僅僅是為開發和測試提供方便。
自定義郵件發送后端
如果要改變郵件的發送方式,可以自己寫一個郵件發送后端。 將 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_HOST 和 EMAIL_PORT 即可。
更多關于本地測試和處理郵件的細節資料,請查看Python文檔 smtpd 模塊。
SMTPConnection
class SMTPConnectionSMTPConnection 類已棄用,轉而使用通用郵件后端(backend)API。
出于向上兼容性的考慮, SMTPConnection 仍然保留在 django.core.mail 中,且做為 SMTP backend. 的別名。新代碼應該使用 get_connection() 取代。