Python郵件傳送,看這篇就夠

程式碼與藝術發表於2019-02-20

本文為譯文,原文連結 Sending Emails With Python
本人部落格: 程式設計禪師

你可能因為想使用Python傳送電子郵件而找到了本教程。 也許你希望寫程式碼來接收郵件提醒,在使用者建立帳戶時向使用者傳送確認郵件,或向組織成員傳送郵件以提醒他們支付會費。 傳送郵件是一項耗時且容易出錯的任務,但是使用Python可以輕鬆實現自動化。

在本教程中,你將瞭解如何:

  • 使用 SMTP_SSL().starttls() 設定安全連線

  • 使用Python的內建 smtplib 庫傳送基本電子郵件

  • 使用 email 包傳送包含HTML內容和附件的電子郵件

  • 傳送多份包含聯絡人資料的CSV檔案的個性化電子郵件

  • 使用 Yagmail 包只需幾行程式碼即可通過Gmail帳戶傳送電子郵件

你將在本教程結束時找到一些事務性的電子郵件服務,當你想要傳送大量電子郵件時,這些服務會很有用。

準備開始

Python內建了 smtplib 模組,用於使用簡單郵件傳輸協議(SMTP)傳送電子郵件。 smtplib 在SMTP中使用 RFC 821協議。 本教程中的示例將使用Gmail SMTP伺服器傳送電子郵件,但相同的原則適用於其他電子郵件服務。 雖然大多數電子郵件提供商使用與本教程中相同的連線埠,但你可以使用Google搜尋來快速確認。

要開始本教程,請先建立用於開發的Gmail帳戶,或者建立一個SMTP除錯伺服器,郵件將不會傳送而是將其列印到控制檯中。 下面列出了這兩個選項。 本地SMTP除錯伺服器可用於修復電子郵件功能的任何問題,並確保在傳送任何電子郵件之前你的電子郵件功能沒有錯誤。

選項一: 建立用於開發的Gmail賬戶

如果你決定使用Gmail帳戶傳送電子郵件,我強烈建議你為開發程式碼設定一次性帳戶。 這是因為你必須調整Gmail帳戶的安全設定來允許從你的Python程式碼訪問,而且你也可能會意外地洩露你的登入詳細資訊。 此外,我發現我的測試帳戶的收件箱很快就填滿了測試電子郵件,這也足以讓你設定一個新的Gmail帳戶進行開發。

Gmail的一個不錯的功能是,你可以使用 + 號在@符號前面的電子郵件地址中新增任何修飾符。 例如,傳送到 my+person1@gmail.commy+person2@gmail.com 的郵件都將傳送到 my@gmail.com 。 在測試電子郵件功能時,你可以使用此功能模擬所有指向同一收件箱的多個地址。

要建立用於測試程式碼的Gmail地址,請執行以下操作:

如果你不想降低Gmail帳戶的安全設定,請檢視Google的文件,瞭解如何使用Python指令碼進行OAuth2授權來獲取訪問憑據。

選項二:建立一個本地SMTP伺服器

你可以使用Python預先安裝的 smptd 模組執行一個本地SMTP除錯伺服器來測試電子郵件功能。 它不會將電子郵件傳送到指定的地址,而是丟棄它們並將其內容列印到控制檯。 執行本地除錯伺服器意味著無需處理訊息加密或使用憑據登入電子郵件伺服器。

你可以通過在命令提示符下輸入以下內容來啟動本地SMTP除錯伺服器:

python -m smtpd -c DebuggingServer -n localhost:1025
複製程式碼

在Linux上,在相同的命令之前加上 sudo

通過此伺服器傳送的任何電子郵件都將被丟棄,並在終端視窗中每行顯示一個 bytes 物件:

---------- MESSAGE FOLLOWS ----------
b'X-Peer: ::1'
b''
b'From: my@address.com'
b'To: your@address.com'
b'Subject: a local test mail'
b''
b'Hello there, here is a test email'
------------ END MESSAGE ------------
複製程式碼

對於本教程的其餘部分,我假設你使用的是Gmail帳戶,但如果你使用的是本地除錯伺服器,請確保使用 localhost 作為SMTP伺服器地址並使用埠為1025而不是埠465或587。 除此之外,你不需要使用 login() 或使用 SSL / TLS 加密通訊。


傳送純文字郵件

在我們深入傳送包含HTML內容和附件的電子郵件之前,你將學會使用Python傳送純文字電子郵件。 這些是你可以在簡單的文字編輯器中編寫的電子郵件。 沒有像文字格式或超連結這樣的奇特東西。 這些稍後你會了解到。

開始一個安全SMTP連線

當你通過Python傳送電子郵件時,應確保你的SMTP連線已加密,以便其他人無法輕鬆訪問你的郵件和登入憑據。 SSL(安全套接字層)和TLS(傳輸層安全性)是兩種可用於加密SMTP連線的協議。 使用本地除錯伺服器時,不必使用其中任何一個。

有兩種方法可以與你的電子郵件伺服器建立安全連線:

  • 使用 SMTP_SSL() 建立安全的SMTP連線
  • 使用不安全的SMTP連線,然後使用 .starttls() 加密

在這兩種情況下,Gmail都會使用TLS加密電子郵件,因為這是SSL更安全的後續版本。 根據Python的安全注意事項,強烈建議你使用 ssl 模組中的 create_default_context()。 這將載入系統的可信CA證照,啟用主機名檢查和證照驗證,並嘗試選擇適合的安全的協議和密碼設定。

如果要檢查Gmail收件箱中電子郵件的加密資訊,請轉到更多→顯示原始內容來檢視“已接收”標題下列出的加密型別。

smtplib 是Python的內建模組,用於使用SMTP或ESMTP監聽器守護程式向任何Internet計算機傳送電子郵件。

我將首先向你展示如何使用 SMTP_SSL(),因為它從一開始就例項化一個安全的連線,並且比使用 .starttls() 替代方案略微簡潔。 請注意,如果使用 SMTP_SSL() ,則Gmail要求你連線到465埠,使用 .starttls() 時,要求連線到589埠。

選項1: 使用 SMPT_SSL()

下面的程式碼示例使用 smtplibSMTP_SSL() 初始化TLS加密連線,以此來與Gmail的SMTP伺服器建立安全連線。 ssl 的預設上下文驗證主機名及其證照,並優化連線的安全性。 請務必填寫你自己的電子郵件地址而不是 my@gmail.com

import smtplib, ssl

port = 465  # For SSL
password = input("Type your password and press enter: ")

# Create a secure SSL context
context = ssl.create_default_context()

with smtplib.SMTP_SSL("smtp.gmail.com", port, context=context) as server:
    server.login("my@gmail.com", password)
    # TODO: Send email here
複製程式碼

使用 with smtplib.SMTP_SSL() as server 確保連線在縮排程式碼塊的末尾自動關閉。 如果port為零或未指定,則 .SMTP_SSL() 將使用標準埠(埠465)。

將電子郵件密碼儲存在程式碼中並不安全,特別是如果你打算與他人共享。 相反,使用input() 讓使用者在執行指令碼時輸入密碼,如上例所示。 如果你在輸入時不希望密碼顯示在螢幕上,則可以匯入 getpass 模組並使用 .getpass() 代替直接輸入密碼。

選項2: 使用 .starttls()

我們可以建立一個不安全的SMTP連線並使用 .starttls() 加密它,而不是使用 .SMTP_SSL() 一開始就建立是安全的連線。

為此,建立一個 smtplib.SMTP 例項,該例項封裝SMTP連線並允許你訪問其方法。 我建議你在指令碼開頭定義SMTP伺服器和埠,以便輕鬆配置它們。

下面的程式碼片段使用 server= SMTP() ,而不是使用 with SMTP() as server: 這個我們在上一個示例中使用了的格式。 為了確保在出現問題時程式碼不會崩潰,請將主程式碼放在 try 塊中,讓 except 塊將任何錯誤訊息列印到 stdout

import smtplib, ssl

smtp_server = "smtp.gmail.com"
port = 587  # For starttls
sender_email = "my@gmail.com"
password = input("Type your password and press enter: ")

# Create a secure SSL context
context = ssl.create_default_context()

# Try to log in to server and send email
try:
    server = smtplib.SMTP(smtp_server,port)
    server.ehlo() # Can be omitted
    server.starttls(context=context) # Secure the connection
    server.ehlo() # Can be omitted
    server.login(sender_email, password)
    # TODO: Send email here
except Exception as e:
    # Print any error messages to stdout
    print(e)
finally:
    server.quit() 
複製程式碼

要通知伺服器讓它知道自己,應在建立 .SMTP() 物件後呼叫 .helo() (SMTP)或 .ehlo() (ESMTP),並在 .starttls() 後再呼叫。 此函式由 .starttls().sendmail() 隱式呼叫,因此除非你要檢查伺服器的SMTP服務擴充套件,否則不必顯式呼叫 .helo().ehlo()

傳送你的純文字郵件

使用上述任一方法發起一個安全SMTP連線後,你可以使用 .sendmail() 傳送電子郵件:

server.sendmail(sender_mail, receiver_email, message)
複製程式碼

我建議在 import 後在指令碼頂部定義電子郵件地址和郵件內容,以便你可以方便更改它們:

sender_email = "my@gmail.com"
receiver_email = "your@gmail.com"
message = """\
Subject: Hi there

This message is sent from Python."""

# Send email here
複製程式碼

message 字串以 “Subject:Hi there” 開頭,後跟兩個換行符(\ n)。 這樣可以確保 Hi there 為電子郵件的主題顯示,並且新的一個行後面的文字將被視為郵件正文。

下面的程式碼示例使用 SMTP_SSL() 傳送純文字電子郵件:

import smtplib, ssl

port = 465  # For SSL
smtp_server = "smtp.gmail.com"
sender_email = "my@gmail.com"  # Enter your address
receiver_email = "your@gmail.com"  # Enter receiver address
password = input("Type your password and press enter: ")
message = """\
Subject: Hi there

This message is sent from Python."""

context = ssl.create_default_context()
with smtplib.SMTP_SSL(smtp_server, port, context=context) as server:
    server.login(sender_email, password)
    server.sendmail(sender_email, receiver_email, message)
複製程式碼

這是通過使用 .starttls() 加密SMTP連線傳送純文字電子郵件的一個程式碼示例。作為對照, server.ehlo() 行可以省略,因為如果需要,它們將由 .starttls().sendmail() 隱式呼叫:

import smtplib, ssl

port = 587  # For starttls
smtp_server = "smtp.gmail.com"
sender_email = "my@gmail.com"
receiver_email = "your@gmail.com"
password = input("Type your password and press enter:")
message = """\
Subject: Hi there

This message is sent from Python."""

context = ssl.create_default_context()
with smtplib.SMTP(smtp_server, port) as server:
    server.ehlo()  # Can be omitted
    server.starttls(context=context)
    server.ehlo()  # Can be omitted
    server.login(sender_email, password)
    server.sendmail(sender_email, receiver_email, message)
複製程式碼

傳送花哨的電子郵件

Python的內建 email 包允許你構建更多花哨的電子郵件,然後可以使用 smptlib 進行傳輸。 下面,你將瞭解如何使用 email` 包傳送包含HTML內容和附件的電子郵件。

包含HTML內容

如果你要格式化電子郵件中的文字(粗體,斜體等),或者如果要新增任何影象,超連結或響應式的內容,則使用HTML非常方便。 今天最常見的電子郵件型別是MIME(多用途因特網郵件擴充套件)多部分的電子郵件,它結合了HTML和純文字。 MIME訊息由Python的 email.mime 模組處理。 有關詳細說明,請檢視文件

由於並非所有電子郵件客戶端都預設顯示HTML內容,並且出於安全原因,有些人僅選擇接收純文字電子郵件,因此為HTML訊息新增純文字的替代非常重要。 由於電子郵件客戶端將首先渲染最後一部分的附件,因此請確保在純文字版本之後新增HTML訊息。

在下面的示例中,我們的 MIMEText() 物件將包含訊息的HTML和純文字版本,MIMEMultipart("alternative") 例項將這些組合成一個帶有兩個備用渲染選項的訊息:

import smtplib, ssl
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

sender_email = "my@gmail.com"
receiver_email = "your@gmail.com"
password = input("Type your password and press enter:")

message = MIMEMultipart("alternative")
message["Subject"] = "multipart test"
message["From"] = sender_email
message["To"] = receiver_email

# Create the plain-text and HTML version of your message
text = """\
Hi,
How are you?
Real Python has many great tutorials:
www.realpython.com"""
html = """\
<html>
  <body>
    <p>Hi,<br>
       How are you?<br>
       <a href="http://www.realpython.com">Real Python</a> 
       has many great tutorials.
    </p>
  </body>
</html>
"""

# Turn these into plain/html MIMEText objects
part1 = MIMEText(text, "plain")
part2 = MIMEText(html, "html")

# Add HTML/plain-text parts to MIMEMultipart message
# The email client will try to render the last part first
message.attach(part1)
message.attach(part2)

# Create secure connection with server and send email
context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", 465, context=context) as server:
    server.login(sender_email, password)
    server.sendmail(
        sender_email, receiver_email, message.as_string()
    )
複製程式碼

在此示例中,首先將純文字和HTML訊息定義為字串文字,然後將它們儲存為 plain / html MIMEText 物件。 然後可以按順序將這些新增到 MIMEMultipart("alternative") 訊息中,並通過與電子郵件伺服器的安全連線傳送。 請記住在替代的純文字後新增HTML訊息,因為電子郵件客戶端將嘗試首先渲染最後一個子部分。

使用 email 新增附件

為了將二進位制檔案傳送到設計用於處理文字資料的電子郵件伺服器,需要在傳輸之前對其進行編碼。 這通常使用base64完成,它將二進位制資料編碼為可列印的ASCII字元。

下面的程式碼示例展示瞭如何在傳送電子郵件時將PDF檔案作為附件:

import email, smtplib, ssl

from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

subject = "An email with attachment from Python"
body = "This is an email with attachment sent from Python"
sender_email = "my@gmail.com"
receiver_email = "your@gmail.com"
password = input("Type your password and press enter:")

# Create a multipart message and set headers
message = MIMEMultipart()
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = subject
message["Bcc"] = receiver_email  # Recommended for mass emails

# Add body to email
message.attach(MIMEText(body, "plain"))

filename = "document.pdf"  # In same directory as script

# Open PDF file in binary mode
with open(filename, "rb") as attachment:
    # Add file as application/octet-stream
    # Email client can usually download this automatically as attachment
    part = MIMEBase("application", "octet-stream")
    part.set_payload(attachment.read())

# Encode file in ASCII characters to send by email    
encoders.encode_base64(part)

# Add header as key/value pair to attachment part
part.add_header(
    "Content-Disposition",
    f"attachment; filename= {filename}",
)

# Add attachment to message and convert message to string
message.attach(part)
text = message.as_string()

# Log in to server using secure context and send email
context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", 465, context=context) as server:
    server.login(sender_email, password)
    server.sendmail(sender_email, receiver_email, text)
複製程式碼

MIMEultipart() 接受RFC5233樣式的鍵/值對形式的引數,這些引數儲存在字典中並傳遞給 Message 基類的 .add_header方法。

檢視Python的 email.mime 模組的文件,瞭解有關使用MIME類的更多資訊。


傳送多封個性化電子郵件

想象一下,你希望向你組織的成員傳送電子郵件,以提醒他們支付他們的捐款費用。 或者,你可能希望向班級中的學生髮送個性化電子郵件,其中包含最近作業的成績。 這些任務在Python中輕而易舉。

使用相關個人資訊製作CSV檔案

傳送多封個性化電子郵件的簡單開始是建立包含所有必需個人資訊的CSV(逗號分隔值)檔案。 (確保在未經他們同意的情況下不共享其他人的私人資訊。)CSV檔案可以被視為一個簡單的表,其中第一行通常包含列標題。

以下是 contacts_file.csv 檔案的內容,我將其儲存在與Python程式碼相同的資料夾中。 它包含一組虛構人物的名稱,地址和成績。 我使用 my+modifier@gmail.com 構造來確保所有電子郵件最終都在我自己的收件箱中,在此示例中為 my@gmail.com

name,email,grade
Ron Obvious,my+ovious@gmail.com,B+
Killer Rabbit of Caerbannog,my+rabbit@gmail.com,A
Brian Cohen,my+brian@gmail.com,C
複製程式碼

建立CSV檔案時,請確保使用逗號分隔你的值,而不包含任何的空格。

遍歷行傳送多個郵件

下面的程式碼示例顯示瞭如何開啟CSV檔案並迴圈其內容行(跳過標題行)。 為了確保程式碼在你向所有聯絡人傳送電子郵件之前正常執行。我已經為每個聯絡人列印了 Sending email to ...,我們稍後可以用實際傳送電子郵件的功能替換它們:

import csv

with open("contacts_file.csv") as file:
    reader = csv.reader(file)
    next(reader)  # Skip header row
    for name, email, grade in reader:
        print(f"Sending email to {name}")
        # Send email here
複製程式碼

在上面的示例中,使用 open(filename) as file: 確保你的檔案在程式碼塊的末尾關閉。 csv.reader() 可以逐行讀取CSV檔案並提取其值。 next(reader) 會跳過標題行,接下的一行 for name, email, grade in reader:使用Python中的解包操作,並將結果值儲存在當前聯絡人的名稱,電子郵件和成績中。

如果CSV檔案中的值包含任一側或兩側的空格,則可以使用 .strip() 方法刪除它們。

個性化的的內容

你可以使用 str.format() 填充大括號佔位符,將個性化內容放入訊息中。 例如,"hi {name}, you {result} your assignment".format(name="John", result="passed") 會給你 "hi John, you passed your assignment"

從Python 3.6開始,使用f-string可以更優雅地完成字串格式化,但這些需要在f-string本身之前定義佔位符。 為了在指令碼開頭定義電子郵件訊息,並在迴圈CSV檔案時填寫每個聯絡人的佔位符,使用較舊的 .format() 方法。

考慮到這一點,你可以設定一個通用訊息體,其中可以為個人定製佔位符。

程式碼案例

以下程式碼示例允許你向多個聯絡人傳送個性化電子郵件。 它會迴圈CSV檔案顯示每個聯絡人的姓名,電子郵件,成績的,如上例所示。

常規訊息在指令碼開頭定義,對於CSV檔案中的每個聯絡人,其 {name}{grade} 佔位符都已填入,並且通過與Gmail伺服器的安全連線傳送個性化電子郵件,正如你之前看過的:

import csv, smtplib, ssl

message = """Subject: Your grade

Hi {name}, your grade is {grade}"""
from_address = "my@gmail.com"
password = input("Type your password and press enter: ")

context = ssl.create_default_context()
with smtplib.SMTP_SSL("smtp.gmail.com", 465, context=context) as server:
    server.login(from_address, password)
    with open("contacts_file.csv") as file:
        reader = csv.reader(file)
        next(reader)  # Skip header row
        for name, email, grade in reader:
            server.sendmail(
                from_address,
                email,
                message.format(name=name,grade=grade),
            )
複製程式碼

Yagmail

有多個庫可以讓您更輕鬆地傳送電子郵件,例如 EnvelopesFlankerYagmail。 Yagmail旨在專門用於Gmail,它通過友好的API大大簡化了傳送電子郵件的過程,如下面的程式碼示例所示:

import yagmail

receiver = "your@gmail.com"
body = "Hello there from Yagmail"
filename = "document.pdf"

yag = yagmail.SMTP("my@gmail.com")
yag.send(
    to=receiver,
    subject="Yagmail test with attachment",
    contents=body, 
    attachments=filename,
)
複製程式碼

此程式碼示例傳送帶有PDF附件的電子郵件,比我們使用 emailsmtplib 傳送郵件的例子程式碼量大大減少 。

設定Yagmail時,你可以將Gmail驗證新增到作業系統的金鑰環中,如文件中所述。 如果你不這樣做,Yagmail會在需要時提示你輸入密碼並自動將其儲存在金鑰環中。


事務性郵件服務

如果你計劃傳送大量電子郵件,想要檢視電子郵件統計資訊,並希望確保可靠的投放,則可能需要檢視事務性電子郵件服務。 雖然以下所有服務都傳送大量電子郵件的付費套餐,但他們還提供免費套餐,以便你可以試用它們。 其中一些免費計劃無限期有效,可能足以滿足你的電子郵件需求。

以下是一些主要事務性電子郵件服務的免費計劃概述。 單擊提供商名稱將轉到其網站的定價部分。

供應商 免費套餐
Sendgrid 起初30天內40000封免費,接下來100封/天
Sendinblue 200 封/天
Mailgun 開始的10000封免費
Mailjet 200 封/天
Amazon SES 62,000 封/月

你可以執行Google搜尋以檢視最符合你需求的提供商,或者只是嘗試一些免費計劃,以檢視你最喜歡哪種API。


Sendgrid程式碼示例

這是一個使用Sendgrid傳送電子郵件的程式碼示例,為你提供如何使用Python的事務性電子郵件服務的方法:

import os
import sendgrid
from sendgrid.helpers.mail import Content, Email, Mail

sg = sendgrid.SendGridAPIClient(
    apikey=os.environ.get("SENDGRID_API_KEY")
)
from_email = Email("my@gmail.com")
to_email = Email("your@gmail.com")
subject = "A test email from Sendgrid"
content = Content(
    "text/plain", "Here's a test email sent through Python"
)
mail = Mail(from_email, subject, to_email, content)
response = sg.client.mail.send.post(request_body=mail.get())

# The statements below can be included for debugging purposes
print(response.status_code)
print(response.body)
print(response.headers)
複製程式碼

要執行此程式碼,你必須先:

  • 註冊一個(免費)Sendgrid帳戶
  • 請求一個API金鑰用於進行使用者驗證
  • 通過在命令提示符中鍵入 setx SENDGRID_API_KEY “YOUR_API_KEY”(永久儲存此API金鑰)來新增API金鑰,或者設定 SENDGRID_API_KEY YOUR_API_KEY 以僅為當前客戶端會話儲存它

有關如何在Mac和Windows設定Sendgrid,可以在Github上的儲存庫README中找到更多資訊。


總結

你現在可以啟動安全的SMTP連線,並向聯絡人列表中的人員傳送多個個性化電子郵件!

你已經學會了如何使用純文字替代方式傳送HTML電子郵件,並將檔案附加到你的電子郵件中。 當你使用Gmail帳戶時,Yagmail軟體包可簡化所有這些任務。 如果你計劃傳送大量電子郵件,則值得研究事務性電子郵件服務。

享受用Python傳送電子郵件,但記住:請不要垃圾郵件!

程式碼與藝術

關注公眾號 <程式碼與藝術>,學習更多國外精品技術文章。

相關文章