smtplib --- SMTP 協定用戶端

原始碼:Lib/smtplib.py


smtplib 模組定義了一個 SMTP 用戶端 session 物件,可用於將郵件傳送至任何具備 SMTP 或 ESMTP listener daemon 的網際網路機器。有關 SMTP 和 ESMTP 運作的細節,請參閱 RFC 821(Simple Mail Transfer Protocol,簡易郵件傳輸協定)和 RFC 1869(SMTP Service Extensions,SMTP 服務擴充)。

可用性: not WASI.

此模組在 WebAssembly 平台上不起作用或無法使用。更多資訊請參閱 WebAssembly 平台

class smtplib.SMTP(host='', port=0, local_hostname=None, [timeout, ]source_address=None)

SMTP 實例封裝了一個 SMTP 連線。它擁有支援完整 SMTP 和 ESMTP 操作的各種方法。如果有給定選用的 hostport 參數,初始化期間會以這些參數呼叫 SMTP 的 connect() 方法。如果有指定 local_hostname,則會將其用作 HELO/EHLO 指令中本地主機的 FQDN。否則,會使用 socket.getfqdn() 來尋找本地主機名稱。如果 connect() 呼叫回傳的不是成功碼,則會引發 SMTPConnectError。選用的 timeout 參數會以秒為單位指定阻塞操作(例如連線嘗試)的逾時時間(如果未指定,則會使用全域預設的逾時設定)。如果逾時,則會引發 TimeoutError。選用的 source_address 參數允許在具有多個網路介面的機器中綁定到某個特定的來源位址,和/或綁定到某個特定的來源 TCP 連接埠。它接受一個 2-tuple (host, port),供 socket 在連線前綁定為其來源位址。如果省略(或者 hostport 分別為 '' 和/或 0),則會使用作業系統的預設行為。

一般使用上,你應該只會需要初始化/連線、sendmail()SMTP.quit() 等方法。以下附上一個範例。

SMTP 類別支援 with 陳述式。當這樣使用時,在 with 陳述式結束時會自動發出 SMTP QUIT 指令。例如:

>>> from smtplib import SMTP
>>> with SMTP("domain.org") as smtp:
...     smtp.noop()
...
(250, b'Ok')
>>>

所有指令都會引發一個附帶引數 selfdata稽核事件 smtplib.SMTP.send,其中 data 為即將傳送至遠端的位元組。

在 3.3 版的變更: 新增對 with 陳述式的支援。

在 3.3 版的變更: 新增 source_address 引數。

在 3.5 版被加入: 現在支援 SMTPUTF8 擴充(RFC 6531)。

在 3.9 版的變更: 如果 timeout 參數被設為零,則會引發 ValueError,以防止建立非阻塞的 socket。

class smtplib.SMTP_SSL(host='', port=0, local_hostname=None, *, [timeout, ]context=None, source_address=None)

SMTP_SSL 實例的行為與 SMTP 的實例完全相同。SMTP_SSL 應該用於從連線一開始就需要 SSL、且不適合使用 starttls() 的情況。如果未指定 host,則會使用本地主機。如果 port 為零,則會使用標準的 SMTP-over-SSL 連接埠(465)。選用引數 local_hostnametimeoutsource_address 的意義與它們在 SMTP 類別中的相同。同樣為選用的 context 可以包含一個 SSLContext,並允許設定安全連線的各個面向。最佳做法請參閱 Security considerations

在 3.3 版的變更: 新增 context

在 3.3 版的變更: 新增 source_address 引數。

在 3.4 版的變更: 此類別現在支援以 ssl.SSLContext.check_hostname 進行主機名稱檢查以及 伺服器名稱指示 (Server Name Indication)(請參閱 ssl.HAS_SNI)。

在 3.9 版的變更: 如果 timeout 參數被設為零,則會引發 ValueError,以防止建立非阻塞的 socket

在 3.12 版的變更: 已棄用的 keyfilecertfile 參數已被移除。

class smtplib.LMTP(host='', port=LMTP_PORT, local_hostname=None, source_address=None[, timeout])

LMTP 協定與 ESMTP 非常相似,它大量基於標準的 SMTP 用戶端。LMTP 常會使用 Unix socket,因此我們的 connect() 方法除了支援一般的 host:port 伺服器外,也必須支援它。選用引數 local_hostnamesource_address 的意義與它們在 SMTP 類別中的相同。要指定一個 Unix socket,你必須為 host 使用以 '/' 開頭的絕對路徑。

支援使用一般的 SMTP 機制進行身分驗證。當使用 Unix socket 時,LMTP 通常不支援或不需要任何身分驗證,但實際情況可能因人而異。

在 3.9 版的變更: 新增 timeout 選用參數。

此外也定義了一系列例外:

exception smtplib.SMTPException

OSError 的子類別,是此模組所提供之所有其他例外的基底例外類別。

在 3.4 版的變更: SMTPException 成為 OSError 的子類別

exception smtplib.SMTPServerDisconnected

當伺服器意外斷線時,或者當嘗試在將 SMTP 實例連線至伺服器之前就使用它時,會引發此例外。

exception smtplib.SMTPResponseException

所有包含 SMTP 錯誤碼的例外的基底類別。當 SMTP 伺服器回傳錯誤碼時,在某些情況下會產生這些例外。

smtp_code

錯誤碼。

smtp_error

錯誤訊息。

exception smtplib.SMTPSenderRefused

寄件者位址被拒絕。除了所有 SMTPResponseException 例外所設定的屬性外,這還會將 'sender' 設定為 SMTP 伺服器所拒絕的字串。

exception smtplib.SMTPRecipientsRefused

所有收件者位址都被拒絕。

recipients

一個 dictionary,其類型與 SMTP.sendmail() 所回傳的完全相同,包含每個收件者的錯誤。

exception smtplib.SMTPDataError

SMTP 伺服器拒絕接受訊息資料。

exception smtplib.SMTPConnectError

與伺服器建立連線的過程中發生錯誤。

exception smtplib.SMTPHeloError

伺服器拒絕了我們的 HELO 訊息。

exception smtplib.SMTPNotSupportedError

所嘗試的指令或選項不被伺服器支援。

在 3.5 版被加入.

exception smtplib.SMTPAuthenticationError

SMTP 身分驗證出錯。最有可能是伺服器不接受所提供的使用者名稱/密碼組合。

也參考

RFC 821 - 簡易郵件傳輸協定

SMTP 的協定定義。本文件涵蓋了 SMTP 的模型、操作流程和協定細節。

RFC 1869 - SMTP 服務擴充

SMTP 的 ESMTP 擴充定義。這描述了一個以新指令擴充 SMTP 的框架,支援動態探查伺服器所提供的指令,並定義了一些額外的指令。

SMTP 物件

SMTP 實例擁有以下方法:

SMTP.set_debuglevel(level)

設定除錯輸出的層級。level 為 1 或 True 會為連線以及所有傳送至伺服器和從伺服器接收的訊息產生除錯訊息。level 為 2 則會為這些訊息加上時間戳記。

在 3.5 版的變更: 新增 debuglevel 2。

SMTP.docmd(cmd, args='')

傳送一個指令 cmd 給伺服器。選用引數 args 只會以一個空格分隔串接到指令後。

這會回傳一個 2-tuple,由數值回應碼和實際的回應行所組成(多行回應會被合併為一長行)。

在正常操作中,應該不需要明確地呼叫此方法。它被用於實作其他方法,並可能對測試私有擴充很有用。

如果在等待回覆時與伺服器的連線中斷,則會引發 SMTPServerDisconnected

SMTP.connect(host='localhost', port=0)

在給定的連接埠上連線到某個主機。預設值是連線到本地主機的標準 SMTP 連接埠(25)。如果主機名稱以一個冒號(':')後接一個數字結尾,該後綴會被去除,並將該數字解釋為要使用的連接埠號。如果在實例化期間有指定主機,建構函式會自動叫用此方法。回傳一個 2-tuple,內容為伺服器在其連線回應中所傳送的回應碼和訊息。

引發一個附帶引數 selfhostport稽核事件 smtplib.connect

SMTP.helo(name='')

使用 HELO 向 SMTP 伺服器表明你的身分。hostname 引數預設為本地主機的完整網域名稱(fully qualified domain name)。伺服器回傳的訊息會被儲存為物件的 helo_resp 屬性。

在正常操作中,應該不需要明確地呼叫此方法。必要時它會被 sendmail() 隱式地呼叫。

SMTP.ehlo(name='')

使用 EHLO 向 ESMTP 伺服器表明你的身分。hostname 引數預設為本地主機的完整網域名稱(fully qualified domain name)。檢視回應中的 ESMTP 選項並將其儲存起來,供 has_extn() 使用。同時也會設定數個資訊性屬性:伺服器回傳的訊息會被儲存為 ehlo_resp 屬性,does_esmtp 會依伺服器是否支援 ESMTP 被設為 TrueFalse,而 esmtp_features 會是一個 dictionary,包含此伺服器所支援的 SMTP 服務擴充名稱以及它們的參數(如果有的話)。

除非你想在寄送郵件前使用 has_extn(),否則應該不需要明確地呼叫此方法。必要時它會被 sendmail() 隱式地呼叫。

SMTP.ehlo_or_helo_if_needed()

如果此 session 之前沒有過 EHLOHELO 指令,此方法會呼叫 ehlo() 和/或 helo()。它會先嘗試 ESMTP 的 EHLO

SMTPHeloError

伺服器沒有正確地回覆 HELO 問候。

SMTP.has_extn(name)

如果 name 在伺服器回傳的 SMTP 服務擴充集合中,則回傳 True,否則回傳 False。大小寫會被忽略。

SMTP.verify(address)

使用 SMTP 的 VRFY 檢查某個位址在此伺服器上的有效性。如果使用者位址有效,則回傳一個由代號 250 和一個完整的 RFC 822 位址(包含人名)組成的 tuple。否則回傳一個 400 或更大的 SMTP 錯誤碼以及一個錯誤字串。

備註

許多網站會停用 SMTP 的 VRFY 以阻擋垃圾郵件發送者。

SMTP.login(user, password, *, initial_response_ok=True)

登入一個需要身分驗證的 SMTP 伺服器。引數為用來驗證的使用者名稱和密碼。如果此 session 之前沒有過 EHLOHELO 指令,此方法會先嘗試 ESMTP 的 EHLO。如果身分驗證成功,此方法會正常回傳,否則可能引發以下例外:

SMTPHeloError

伺服器沒有正確地回覆 HELO 問候。

SMTPAuthenticationError

伺服器不接受該使用者名稱/密碼組合。

SMTPNotSupportedError

伺服器不支援 AUTH 指令。

SMTPException

找不到合適的身分驗證方法。

如果伺服器宣告支援 smtplib 所支援的每一種身分驗證方法,這些方法會被依序嘗試。支援的身分驗證方法清單請參閱 auth()initial_response_ok 會被傳遞給 auth()

選用的關鍵字引數 initial_response_ok 指定對於支援它的身分驗證方法,是否可以將 RFC 4954 中所指定的「initial response(初始回應)」隨 AUTH 指令一起傳送,而非要求進行 challenge(質詢)/response(回應)。

在 3.5 版的變更: 可能會引發 SMTPNotSupportedError,並新增了 initial_response_ok 參數。

SMTP.auth(mechanism, authobject, *, initial_response_ok=True)

為指定的身分驗證 mechanism 發出一個 SMTP AUTH 指令,並透過 authobject 處理 challenge 回應。

mechanism 指定要用作 AUTH 指令引數的身分驗證機制;有效值為 esmtp_featuresauth 元素中所列出的那些。

authobject 必須是一個接受單一選用引數的可呼叫物件:

data = authobject(challenge=None)

如果選用的關鍵字引數 initial_response_ok 為真,authobject() 會先在不帶引數的情況下被呼叫。它可以回傳 RFC 4954 的「initial response(初始回應)」ASCII str,該回應會如下被編碼並隨 AUTH 指令一起傳送。如果 authobject() 不支援初始回應(例如因為它需要一個 challenge),則它在以 challenge=None 被呼叫時應回傳 None。如果 initial_response_ok 為假,則 authobject() 不會先以 None 被呼叫。

如果初始回應檢查回傳 None,或者如果 initial_response_ok 為假,authobject() 會被呼叫以處理伺服器的 challenge 回應;傳遞給它的 challenge 引數會是一個 bytes。它應回傳會被 base64 編碼並傳送至伺服器的 ASCII str data

SMTP 類別為 CRAM-MD5PLAINLOGIN 機制提供了 authobjects;它們分別被命名為 SMTP.auth_cram_md5SMTP.auth_plainSMTP.auth_login。它們都要求 SMTP 實例的 userpassword 屬性被設定為適當的值。

使用者程式碼通常不需要直接呼叫 auth,而是可以改為呼叫 login() 方法,它會依所列順序依序嘗試上述每一種機制。auth 被公開出來是為了方便實作 smtplib 尚未(或還沒有)直接支援的身分驗證方法。

在 3.5 版被加入.

SMTP.starttls(*, context=None)

將 SMTP 連線置於 TLS(Transport Layer Security,傳輸層安全性)模式。後續所有的 SMTP 指令都會被加密。接著你應該再次呼叫 ehlo()

如果有提供 keyfilecertfile,它們會被用來建立一個 ssl.SSLContext

選用的 context 參數是一個 ssl.SSLContext 物件;這是使用 keyfile 和 certfile 的替代方案,如果有指定它,則 keyfilecertfile 都應為 None

如果此 session 之前沒有過 EHLOHELO 指令,此方法會先嘗試 ESMTP 的 EHLO

在 3.12 版的變更: 已棄用的 keyfilecertfile 參數已被移除。

SMTPHeloError

伺服器沒有正確地回覆 HELO 問候。

SMTPNotSupportedError

伺服器不支援 STARTTLS 擴充。

RuntimeError

你的 Python 直譯器無法使用 SSL/TLS 支援。

在 3.3 版的變更: 新增 context

在 3.4 版的變更: 此方法現在支援以 ssl.SSLContext.check_hostname 進行主機名稱檢查以及 Server Name Indicator(請參閱 HAS_SNI)。

在 3.5 版的變更: 針對缺乏 STARTTLS 支援所引發的錯誤現在是 SMTPNotSupportedError 子類別,而非基底的 SMTPException

SMTP.sendmail(from_addr, to_addrs, msg, mail_options=(), rcpt_options=())

寄送郵件。必要引數為一個 RFC 822 寄件位址字串、一個 RFC 822 收件位址字串的 list(單一字串會被視為含有 1 個位址的 list),以及一個訊息字串。呼叫者可以傳入一個 ESMTP 選項的 list(例如 "8bitmime")作為 mail_options,供 MAIL FROM 指令使用。應與所有 RCPT 指令一起使用的 ESMTP 選項(例如 DSN 指令)可以作為 rcpt_options 傳入。每個選項都應作為一個包含該選項完整文字的字串傳入,包括任何可能的鍵(例如 "NOTIFY=SUCCESS,FAILURE")。(如果你需要對不同的收件者使用不同的 ESMTP 選項,你必須使用如 mail()rcpt()data() 等低階方法來寄送訊息。)

備註

from_addrto_addrs 參數用來建構傳輸代理(transport agent)所使用的訊息信封(message envelope)。sendmail 不會以任何方式修改訊息標頭。

msg 可以是一個包含 ASCII 範圍內字元的字串,或者一個位元組字串。字串會使用 ascii codec 被編碼為位元組,而單獨的 \r\n 字元會被轉換為 \r\n 字元。位元組字串不會被修改。

如果此 session 之前沒有過 EHLOHELO 指令,此方法會先嘗試 ESMTP 的 EHLO。如果伺服器支援 ESMTP,訊息大小和每個指定的選項都會被傳遞給它(如果該選項在伺服器宣告的功能集合中)。如果 EHLO 失敗,則會嘗試 HELO 並抑制 ESMTP 選項。

如果郵件至少被一個收件者接受,此方法會正常回傳。否則它會引發一個例外。也就是說,如果此方法沒有引發例外,那麼應該會有人收到你的郵件。如果此方法沒有引發例外,它會回傳一個 dictionary,其中每個被拒絕的收件者都有一個條目。每個條目都包含一個由 SMTP 錯誤碼和伺服器所傳送的隨附錯誤訊息組成的 tuple。

如果 SMTPUTF8 被包含在 mail_options 中且伺服器支援它,則 from_addrto_addrs 可以包含非 ASCII 字元。

此方法可能引發以下例外:

SMTPRecipientsRefused

所有收件者都被拒絕。沒有人收到郵件。

SMTPHeloError

伺服器沒有正確地回覆 HELO 問候。

SMTPSenderRefused

伺服器不接受 from_addr

SMTPDataError

伺服器以一個非預期的錯誤碼回覆(而非拒絕某個收件者)。

SMTPNotSupportedError

mail_options 中有給定 SMTPUTF8,但伺服器不支援它。

除非另有說明,否則即使在引發例外後,連線仍會保持開啟。

在 3.2 版的變更: msg 可以是一個位元組字串。

在 3.5 版的變更: 新增 SMTPUTF8 支援,且如果指定了 SMTPUTF8 但伺服器不支援它,可能會引發 SMTPNotSupportedError

SMTP.send_message(msg, from_addr=None, to_addrs=None, mail_options=(), rcpt_options=())

這是一個便利方法,用於以一個 email.message.Message 物件所表示的訊息呼叫 sendmail()。引數的意義與 sendmail() 的相同,但 msg 是一個 Message 物件。

如果 from_addrNoneto_addrsNonesend_message 會依 RFC 5322 中所指定的方式,以從 msg 的標頭中擷取出的位址填入這些引數:如果 Sender 欄位存在,from_addr 會被設為該欄位,否則設為 From 欄位。to_addrs 會結合 msgToCcBcc 欄位的值(如果有的話)。如果訊息中剛好出現一組 Resent-* 標頭,則一般的標頭會被忽略,並改用 Resent-* 標頭。如果訊息包含超過一組 Resent-* 標頭,則會引發 ValueError,因為無法明確地偵測出最近的那組 Resent- 標頭。

send_message 會使用 BytesGenerator 並以 \r\n 作為 linesep 來序列化 msg,並呼叫 sendmail() 來傳輸產生的訊息。無論 from_addrto_addrs 的值為何,send_message 都不會傳輸 msg 中可能出現的任何 BccResent-Bcc 標頭。如果 from_addrto_addrs 中有任何位址包含非 ASCII 字元,且伺服器未宣告支援 SMTPUTF8,則會引發 SMTPNotSupportedError。否則 Message 會以其 policy 的副本(其中 utf8 屬性被設為 True)進行序列化,且 SMTPUTF8BODY=8BITMIME 會被新增到 mail_options

在 3.2 版被加入.

在 3.5 版被加入: 對國際化位址(SMTPUTF8)的支援。

SMTP.quit()

終止 SMTP session 並關閉連線。回傳 SMTP QUIT 指令的結果。

也支援對應於標準 SMTP/ESMTP 指令 HELPRSETNOOPMAILRCPTDATA 的低階方法。通常這些方法不需要被直接呼叫,因此這裡不對它們加以說明。詳情請參閱模組程式碼。

此外,SMTP 實例擁有以下屬性:

SMTP.helo_resp

HELO 指令的回應,請參閱 helo()

SMTP.ehlo_resp

EHLO 指令的回應,請參閱 ehlo()

SMTP.does_esmtp

一個 boolean 值,指出伺服器是否支援 ESMTP,請參閱 ehlo()

SMTP.esmtp_features

一個包含伺服器所支援之 SMTP 服務擴充名稱的 dictionary,請參閱 ehlo()

SMTP 範例

這個範例會提示使用者輸入訊息信封中所需的位址(「To」和「From」位址),以及要傳遞的訊息。請注意,要隨訊息一起包含的標頭必須在輸入訊息時就包含進去;這個範例不會對 RFC 822 標頭做任何處理。特別是,「To」和「From」位址必須明確地包含在訊息標頭中:

import smtplib

def prompt(title):
    return input(title).strip()

from_addr = prompt("From: ")
to_addrs  = prompt("To: ").split()
print("Enter message, end with ^D (Unix) or ^Z (Windows):")

# 在開頭加上 From: 和 To: 標頭!
lines = [f"From: {from_addr}", f"To: {', '.join(to_addrs)}", ""]
while True:
    try:
        line = input()
    except EOFError:
        break
    else:
        lines.append(line)

msg = "\r\n".join(lines)
print("Message length is", len(msg))

server = smtplib.SMTP("localhost")
server.set_debuglevel(1)
server.sendmail(from_addr, to_addrs, msg)
server.quit()

備註

一般而言,你會想使用 email 套件的功能來建構電子郵件訊息,接著就可以透過 send_message() 來寄送它;請參閱 email:範例