如何用 Pyhon 寄電子郵件-1 — 使用smtplib, Gmail

Sean Yeh
Python Everywhere -from Beginner to Advanced
15 min readNov 11, 2021
Jinguashi, Taiwan ,photo by Sean Yeh

每天使用電子郵件的您是否聽過SMTP、POP3或IMAP?是否知道SMTP用來傳送郵件;POP3或IMAP則負責接收郵件?

SMTP電子郵件傳輸協定

SMTP,全名Simple Mail Transfer Protocol 是用來發送電子郵件的協定。在這個協定中,規定了電子郵件要使用什麼格式、如何加密、以及如何在郵件伺服器之間傳遞等等。

此外,前面提到的POP與IMAP,POP的全名是Post Office Protocol 而IMAP的全名則是Internet Message Access Protocol,它們都是傳輸協定的一種。POP會將信件從伺服器端下載到本機端,IMAP則會把信件留在伺服器上。

在此,我們要集中討論『寄信』的部分,要如何透過簡單的Python程式,透過SMTP將郵件寄出?

SMTP電子郵件伺服器

透過SMTP將郵件寄出基本上有五個步驟:

  • 指定SMTP伺服器的位置以及它的通訊埠(PORT)
  • 啟動TLS加密
  • 登入SMTP伺服器
  • 傳送電子郵件訊息
  • 切斷與SMTP伺服器的連接

以上五個步驟,我們也可以透過Python來實現。實際上,已經有一個Python模組可以讓輕鬆的使用。

smtplib 模組

在Pyhton裡面有一個smtplib 模組,可以透過這個模組的函式寄發電子郵件。

import smtplib

使用smtplib 模組寄信,也需要上述的五個步驟。以下針對各個步驟逐一說明:

1.指定SMTP伺服器的位置以及它的通訊埠(PORT)

透過Python 編寫發送郵件的程式時,首先必須與郵件伺服器進行連線。在此的第一步,需要在程式碼中設置 SMTP 伺服器。

安裝完smtplib 模組後,就可以建立一個SMTP物件。我們需要把SMTP伺服器的位置與通訊埠這兩個值做為參數傳入給這個物件,並建立一個server變數指定給這個SMTP物件。

server = smtplib.SMTP(SMTP伺服器的位置,通訊埠)

假設我們想要透過gmail的SMTP位置來發送郵件,可以使用smtp.gmail.com作為伺服器的位置,以及587通訊埠。

server = smtplib.SMTP('smtp.gmail.com',587)

可以在後面加上ehlo查看結果,如果返回的代碼是250,就代表成功的與SMTP伺服器進行應答。

server_res = server.ehlo()
print(f'res 1==> {server_res}')

2.啟動TLS加密

建立了server物件後,需要使用starttls方法來啟動TLS郵件加密模式。

smtp_ttls = server.starttls()

我們一樣可以print出結果查看代碼,如果回應的代碼為220,表示準備完畢,啟動加密成功。

print(f'start tls ==> {smtp_ttls}')

3.登入SMTP伺服器

加密後,就可以安心的使用login方法傳入帳號密碼登入伺服器。

smtp_login = server.login(EMAIL, 密碼)

承上假設,要使用Gmail的SMTP發信,此處要填入的帳號密碼為您在Gmail 申請的email信箱與該信箱的密碼。如果您不是使用Gmail發信的話,請先查看該服務商提供的設定方式,再填入欄位中。

我們一樣可以print出結果查看代碼,如果回應的代碼為235,表示認證成功,成功登入伺服器。

print(f'SMTP login ==> {smtp_login}')

不過,我相信大家應該在這一步驟都不會一次成功,而出現SMTPAuthenticationError的錯誤訊息。因為Gmail在預設上面並不允許較低安全性的存取方式。必須先去調整設定,才有辦法順利的登入伺服器。

調整的方式是,1. 進入自己的「Google帳戶」設定的地方;2. 點選「安全性」設定;3. 開啟「低安全性應用程式存取權」。

從左向右滑動更改為啟用。圖片為啟用的狀態。

4.傳送電子郵件訊息

登入SMTP成功後,就可以進行信息的傳遞。我們要透過sendmail方法來傳遞訊息。根據smtplib的文件,在sendmail裡面可以放入下面的參數,前三項為必要參數:

SMTP.sendmail(from_addr, to_addrs, msg, mail_options=(), rcpt_options=())
  • from_addr:為寄件者的地址;
  • to_addrs:為收件者的地址,如果在此傳入list串列可以一次寄給多人;
  • msg:信件的訊息。就格式來說以『Subject:』開頭,後面的文字為信件的主旨,主旨結束後要使用『 \n 』符號分割內容,符號的前面為主旨而後面為信件的主文。

由於sendmail會回傳一個字典。如果信件傳送成功的話,回傳的字典裡面會是空白。反之,如果是寄送失敗的話,會回傳失敗的結果。

或者是使用send_message方法來傳遞訊息:

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

5.切斷SMTP伺服器的連接

傳訊完畢後,就可以使用quit切斷與SMTP伺服器的連接。

server.quit()

範例程式碼

下面列出透過Gmail發信的簡單範例程式碼。其中很多『OO』的地方,請自行填入自己信箱的訊息以及想要寄發的訊息。

import smtplibdef send_email(sender,receiver,message):
smtp_server = 'smtp.gmail.com'
smtp_server_port = 587
smtp_server_account = 'ooooo@gmail.com'
smtp_server_password = 'ooooooooo'
server = smtplib.SMTP(smtp_server,smtp_server_port)
server_res = server.ehlo()
print(f'res==> {server_res}')
smtp_ttls = server.starttls()
print(f'start tls ==> {smtp_ttls}')
smtp_login = server.login(smtp_server_account,smtp_server_password)
print(f'SMTP login ==> {smtp_login}')

status = server.sendmail(sender,receiver, message)
if not status:
print('寄信成功')
else:
print(f'寄信失敗。{status}')

server.quit()
if __name__ == '__main__':
from_mail = 'ooooooo@gmail.com'
to_mail = 'ooooooo'
to_message = 'oooooooooooooooooooooooooooooooooooooooooo'
send_email(from_mail,to_mail,to_message)

實際上,上面的程式碼還有很大的改善空間。例如,上面的程式碼是無法寄送中文的。如果將中文字寫入to_message 裡面的話,應該會看到下面的編碼錯誤訊息,接下來,我們要處理這個問題。

UnicodeEncodeError: 'ascii' codec can't encode characters

什麼是MIME

MIME的全名Multipurpose Internet Mail Extensions, 為多用途網際網路郵件擴展(定義參考維基百科)。藉由MIME的使用,使得原本無法使用的非ASCII編碼的文件以及二進位制、聲音、圖片等非文件格式的附件,都可以被郵件傳送。下表列出一些常見的MIME Type與Subtype供參考。

如何應用MIME?

我們要以MIME傳送一封含有文字的郵件。這個郵件的Type類型即為text,因此我們需要匯入MIMEText。

from email.mime.text import MIMEText

匯入之後,要用來建立一個MIMEText物件,且該物件需要三個參數:

text = MIMEText('內容',subtype類型,字元編碼)
  • 第一個參數要放的是寄件內容;
  • 第二個參數是文字的subtype類型,以 text/plain 來說,要放入的就是 plain
  • 第三個參數則是信件的字元編碼。我們現在大多使用utf-8作為字元編碼,可以顯示中文,其他國語言也可以顯示。

若以上面範例程式碼的例子來說,to_message與send_email為原本程式碼。其中to_message 為信件的內容,而send_email用來寄出信件。我們要在兩者之間加上以MIMEText物件做成之變數text(如粗體字),後面要加上as_string() 來將物件轉為字串。最後,將變數text放入send_email中寄出。

to_message = 'oooooooooooooooooooooooooooooooooooooooooo'text = MIMEText(to_message,'plain','utf-8').as_string()send_email(from_mail,to_mail, text)

結果,你可能會發現寄出的信件雖然有中文字,但是卻沒有主旨。

修改

因此我們要修改一下上面的程式碼,讓郵件可以出現中文主旨等訊息。

text = MIMEText(to_message,'plain','utf-8')
text['Subject'] = '標題ooo'
text['From'] = '寄件者ooo'
text['To'] = '收件者ooo'
text['Cc'] = '副本收件者ooo'

text.as_string()

我們透過設定物件的方式,來增加郵件的相關訊息。最後將物件轉為字串然後再透過send_email寄出。

上面的訊息不論是Subject、From、To等等,都可以使用中文字。你或許會疑惑,如果在To的地方寫下收件者的中文名字,程式怎麼知道我要寄到哪一個信箱?實際上,是透過我們在send_email裡面,填入的to_mail參數來決定的。text只決定了郵件的訊息部分。

send_email(from_mail,to_mail, text)  

修改寄件者

我們可以把寄件者的資料設定為一個字典,這個字典裡面存放著中文名稱(name)與電子郵件地址(addr),可以在後面使用字典取值的方式取出名稱( to_mail['name'] )與郵件地址( to_mail['addr'] ),分別放到send_email函式( send_email(from_mail,to_mail['addr'],text) ) 與 MIMEText物件的 text (text['To'] = to_mail['name'])裡面。

to_mail = {'name':'ooo','addr':'ooo@abc.com'}

完整程式碼

下面是修改後的完整程式碼,其中很多圈圈的地方(ooooooo)代表的是各位自己的資訊。至於程式碼中print的地方用來方便看寄信的過程,可以省略。大家可以參考看看:

import smtplib
from email.mime.text import MIMEText
def send_email(sender,receiver,message):
smtp_server = 'smtp.gmail.com'
smtp_server_port = 587
smtp_server_account = 'ooooo@gmail.com'
smtp_server_password = 'ooooooooo'
server = smtplib.SMTP(smtp_server,smtp_server_port)
server_res = server.ehlo()
print(f'res==> {server_res}')
smtp_ttls = server.starttls()
print(f'start tls ==> {smtp_ttls}')
smtp_login = server.login(smtp_server_account,smtp_server_password)
print(f'SMTP login ==> {smtp_login}')
status = server.sendmail(sender,receiver, message) if not status:
print('寄信成功')
else:
print(f'寄信失敗。 {status}')
server.quit()
if __name__ == '__main__':
from_mail = 'ooooooo@gmail.com'
to_mail = 'ooooooo'
to_message = 'oooooooooooooooooooooooooooooooooooooooooo'
to_mail = {'name':'ooo','addr':'ooo@abc.com'}
text = MIMEText(to_message,'plain','utf-8')
text['Subject'] = 'ooo'
text['From'] = 'ooo'
text['To'] = to_mail['name']
text['Cc'] = 'ooo@abc.com'
text = text.as_string()
send_email(from_mail,to_mail['addr'],text)

結語

到目前為止,大家應該可以使用Python的smtplib套件,以及MIME寫出一支可以透過Gmail的SMTP寄發各種語言的信了。如果要使用Gmail SMTP以外的伺服器寄送信件的話,需要參考一下個郵件伺服器的設定說明,應該就可以發送。

不過,現階段的我們還停留在只能寄出純文字郵件的狀態。要如何附加檔案到信件中一並寄出?又或者要如何將寄件內容預先寫成一個郵件模板?或者是如何一次發送給多位收件者?這些問題我們留待下次解說。

後記:解決Google限制帳戶低安全性應用程式存取權的問題

由於日前Google宣布:為了保護帳戶的安全,自2022年 5 月 30 日起,如果第三方應用程式或裝置只要求以使用者名稱和密碼登入Google帳戶,Google將不再支援這些應用程式與裝置。換句話說,您可能無法再存取採用低安全性登入技術的應用程式。

實際上Google只是將「低安全性使用」關掉,但這不代表我們不能用「高安全性」的方式登入。所謂「應用程式密碼」是指Google在高安全性下的額外提供的密碼。

解決方式

  • 啟用Google 帳戶中的已兩步驟驗證
  • 建立應用程式密碼
  • 透過應用程式密碼連接SMTP

# 開啟Google 帳戶的兩步驟驗證功能

兩步驟驗證 (又稱為「雙重驗證」),可以幫助你在密碼遭竊時為你的帳戶多加一層保護。你可以透過下面的方式來設定:

  1. 開啟您的 Google 帳戶。
  2. 選取導覽面板上的 [安全性]。(如下圖1的部分,可以看到2的地方,目前的兩段式驗證是關閉的狀態)
  1. 依序選取「登入 Google」底下的 [兩步驟驗證] [開始使用]。(從上圖2的地方點進去)
  2. 按照畫面上的步驟操作。

驗證手機號碼。

啟用後可以看到:

您可以參考Google上面的說明

#建立應用程式密碼

應用程式在此選擇「郵件」

裝置我在此選擇Mac。

最後會得到一組Mac專用的應用程式密碼

# 透過應用程式密碼連接SMTP

將原來程式碼中smtp_server_password的部分改為前面產出的應用程式密碼。應用程式密碼為16位的字串值,並且一組密碼只能綁定到裝置上。當你在某台裝置使用此密碼後就無法再用同一個密碼在別台裝置上使用。

smtp_server_password = 'ooooooooo'

修改完畢之後,就可以執行程式看看。

--

--

Sean Yeh
Python Everywhere -from Beginner to Advanced

# Taipei, Internet Digital Advertising,透過寫作讓我們回想過去、理解現在並思考未來。並樂於分享,這才是最大贏家。