SMTP (Simple Mail Transfer Protocol)
郵件傳送代理 (Mail Transfer Agent,MTA) 程式使用SMTP協議來發送電郵到接收者的郵件伺服器。SMTP協議只能用來發送郵件,不能用來內送郵件。大多數的郵件發送伺服器 (Outgoing Mail Server) 都是使用SMTP協議。SMTP協議的預設TCP連接埠號碼是25。
SMTP協議的一個重要特點是它能夠接力傳送郵件。它工作在兩種情況下:一是電子郵件從客戶機傳輸到伺服器;二是從某一個伺服器傳輸到另一個伺服器。
POP3 (Post Office Protocol) & IMAP (Internet Message Access Protocol)
POP協議和IMAP協議是用於郵件接收的最常見的兩種協議。幾乎所有的郵件用戶端和伺服器都支援這兩種協議。
POP3協議為使用者提供了一種簡單、標準的方式來訪問郵箱和擷取電郵。使用POP3協議的電郵用戶端通常的工作過程是:串連伺服器、擷取所有資訊並儲存在使用者主機、從伺服器刪除這些訊息然後中斷連線。POP3協議的預設TCP連接埠號碼是110。
IMAP協議也提供了方便的郵件下載服務,讓使用者能進行離線閱讀。使用IMAP協議的電郵用戶端通常把資訊保留在伺服器上直到使用者顯式刪除。這種特性使得多個用戶端可以同時管理一個郵箱。IMAP協議提供了摘要瀏覽功能,可以讓使用者在閱讀完所有的郵件到達時間、主題、寄件者、大小等資訊後再決定是否下載。IMAP協議的預設TCP連接埠號碼是143。
郵件格式 (RFC 2822)
每封郵件都有兩個部分:郵件標頭和郵件體,兩者使用一個空行分隔。
郵件標頭每個欄位 (Field) 包括兩部分:欄位名和欄位值,兩者使用冒號分隔。有兩個欄位需要注意:From和Sender欄位。From欄位指明的是郵件的作者,Sender欄位指明的是郵件的寄件者。如果From欄位包含多於一個的作者,必須指定Sender欄位;如果From欄位只有一個作者並且作者和寄件者相同,那麼不應該再使用Sender欄位,否則From欄位和Sender欄位應該同時使用。
郵件體包含郵件的內容,它的類型由郵件標頭的Content-Type欄位指明。RFC 2822定義的郵件格式中,郵件體只是單純的ASCII編碼的字元序列。
MIME (Multipurpose Internet Mail Extensions) (RFC 1341)
MIME擴充郵件的格式,用以支援非ASCII編碼的文本、非文本附件以及包含多個部分 (multi-part) 的郵件體等。
首先簡單的介紹一下python的smtplib模組:
smtplib.SMTP([host[, port[, local_hostname[, timeout]]]])
SMTP類建構函式,表示與SMTP伺服器之間的串連,通過這個串連我們可以向smtp伺服器發送指令,執行相關操作(如:登陸、發送郵件)。該類提供了許多方法,將在下面介紹。它的所有參數都是可選的,其中host參數表示smtp伺服器主機名稱,上面例子中的smtp主機為”smtp.yeah.net”;port表示smtp服務的連接埠,預設是25;如果在建立SMTP對象的時候提供了這兩個參數,在初始化的時候會自動調用connect方法去串連伺服器。
smtplib模組還提供了SMTP_SSL類和LMTP類,對它們的操作與SMTP基本一致。
smtplib.SMTP提供的方法:
SMTP.set_debuglevel(level)
設定是否為偵錯模式。預設為False,即非偵錯模式,表示不輸出任何調試資訊。
SMTP.connect([host[, port]])
串連到指定的smtp伺服器。參數分別表示smpt主機和連接埠。注意: 也可以在host參數中指定連接埠號碼(如:smpt.yeah.net:25),這樣就沒必要給出port參數。
SMTP.docmd(cmd[, argstring])
向smtp伺服器發送指令。選擇性參數argstring表示指令的參數。下面的例子完全通過調用docmd方法向伺服器發送指令來實現郵件的發送(在smtp.yeah.net郵件伺服器上實驗通過。其他郵件伺服器沒有試過):
常用指令有:
HELO 向伺服器標識使用者身份
MAIL 初始化郵件傳輸 mail from:
RCPT 標識單個的郵件接收人;常在MAIL命令後面,可有多個rcpt to:
DATA 在單個或多個RCPT命令後,表示所有的郵件接收人已標識,並初始化資料轉送,以.結束
VRFY 用於驗證指定的使用者/郵箱是否存在;由於安全方面的原因,伺服器常禁止此命令
EXPN 驗證給定的郵箱列表是否存在,擴充郵箱列表,也常被禁用
HELP 查詢服務器支援什麼命令
NOOP 無操作,伺服器應響應OK
QUIT 結束會話
RSET 重設會話,當前傳輸被取消
MAIL FROM 指定寄件者地址
RCPT TO 指明的接收者地址
SMTP.helo([hostname])
使用”helo”指令向伺服器確認身份。相當於告訴smtp伺服器“我是誰”。
SMTP.has_extn(name)
判斷指定名稱在伺服器郵件清單中是否存在。出於安全考慮,smtp伺服器往往屏蔽了該指令。
SMTP.verify(address)
判斷指定郵件地址是否在伺服器中存在。出於安全考慮,smtp伺服器往往屏蔽了該指令。
SMTP.login(user, password)
登陸到smtp伺服器。現在幾乎所有的smtp伺服器,都必須在驗證使用者資訊合法之後才允許發送郵件。
SMTP.sendmail(from_addr, to_addrs, msg[, mail_options, rcpt_options])
發送郵件。這裡要注意一下第三個參數,msg是字串,表示郵件。我們知道郵件一般由標題,發信人,收件者,郵件內容,附件等構成,發送郵件的時候,要注意msg的格式
如果想在郵件中攜帶附件、使用html書寫郵件,附帶圖片等等,就需要使用email模組及其子模組。下面來看看email包,email包是用來管理email資訊的,它包括MIME和其他基於RFC 2822的訊息格式。email包的主要特徵是在它內部解析和產生email資訊是分開的模組來實現的。
MIME訊息由訊息頭和訊息體兩大部分組成,在郵件裡就是郵件標頭和郵件體。郵件標頭與郵件體之間以空行進行分隔。
郵件標頭包含了寄件者、收件者、主題、時間、MIME版本、郵件內容的類型等重要訊息。每條資訊稱為一個域,由網域名稱後加“: ”和資訊內容構成,可以是一行,較長的也可以佔用多行。域的首行必須“頂頭”寫,即左邊不能有空白字元(空格和定位字元);續行則必須以空白字元打頭,且第一個空白字元不是資訊本身固有的。
郵件體包含郵件的內容,它的類型由郵件標頭的“Content-Type”域指出。最常見的類型有text/plain(純文字)和text/html(超文本)。郵件體被分為多個段,每個段又包含段頭和段體兩部分,這兩部分之間也以空行分隔。常見的multipart類型有三種:multipart/mixed, multipart/related和multipart/alternative。
主要來看看email.mime,在郵件中攜帶附件、圖片、音頻時,主要使用的是該模組。一般情況下,你通過解析一個檔案或者一段text來產生一個訊息對象結構,你也可以從頭開始建立一個訊息結構,實際上,你可以給一個已經存在的訊息結構追加一個新的訊息對象。你可以通過建立message執行個體來建立一個對象結構,然後給該結構追加附件和頭部資訊。email包提供了一些子類使得該操作變得很容易。
class email.mime.multipart. MIMEMultipart: 多個MIME對象的集合。
class email.mime.audio. MIMEAudio: MIME音頻對象。
class email.mime.image. MIMEImage: MIME二進位檔案對象。
class email.mime.text. MIMEText: MIME文字物件。
下面是用smpt發送純文字形式郵件的代碼:
import smtplibfrom email.mime.text import MIMETextfrom email.header import Headerfrom email.utils import COMMASPACE,formatdatesender = 'xxxxxx@qq.com'receiver = 'xxxxxx@qq.com'subject = 'hahahaha'smtpserver = 'smtp.qq.com'username = 'xxxxxx@qq.com'password = 'xxxxxx'msg = MIMEText("You're my baby!",'plain','utf-8')msg['Subject'] = Header(subject, 'utf-8')#不寫這個Header也是對的msg['Subject'].append('gaolee','utf-8')msg['From'] =sender#這個不寫郵件的寄件者不會顯示,相當於匿名msg['To'] = receiver#msg['To'] = COMMASPACE.join(receiver)#以逗號分割receiver#msg['Date']=formatdate(localtime=True)smtp = smtplib.SMTP('smtp.qq.com')#smtp.connect('smtp.163.com')smtp.set_debuglevel(1)#列印整個過程中的資訊smtp.login(username, password)#登入smtp.sendmail(sender, receiver, msg.as_string())smtp.quit()
這裡解釋一下Header的用途(摘自官方文檔):
formatdata的作用:
以上內容也可以直接用docmd實現:
import smtplibimport base64sender='upc_dip@163.com'receiver='acprime@163.com'username=base64.encodestring(b'upc_dip@163.com')password=base64.encodestring(b'upc_cs')smtp=smtplib.SMTP('smtp.163.com')smtp.set_debuglevel(1)smtp.docmd('EHLO server')smtp.docmd('AUTH LOGIN')smtp.send(username)smtp.send(password)smtp.docmd('MAIL FROM:<%s>' % sender)smtp.docmd('RCPT TO:<%s>' % receiver)smtp.docmd('DATA')smtp.send('test')smtp.send('.')smtp.quit()
注意username和password要用base64編碼
發送html文本形式的郵件:
msg = MIMEText("<html><h1>haha</h1></html>",'html','utf-8')msg['Subject'] = Header(subject, 'utf-8')msg['Subject'].append('gaolee','utf-8')msg['From'] =sendermsg['To'] = receiversmtp = smtplib.SMTP('smtp.163.com')smtp.set_debuglevel(1)smtp.login(username, password)smtp.sendmail(sender, receiver, msg.as_string())smtp.quit()
從上面可以看出,區分不同的郵件體可以通過MIMEText的參數來改變。
MIMEImage發送圖片郵件:
f=open('12072113.JPG','rb')msgImage=MIMEImage(f.read())f.close()msgImage['From']=sendermsgImage['To']=receivermsgImage['Subject']=Header('Image')smtp=smtplib.SMTP('smtp.163.com')smtp.login(username,password)smtp.sendmail(sender,receiver,msgImage.as_string())smtp.quit()
那麼如果我想把圖片嵌入到html中,也就是把他們關聯起來,怎麼搞:
msgRoot=MIMEMultipart('related')msgRoot['From']=sendermsgRoot['To']=receivermsgRoot['Subject']=Header('Image')msgText=MIMEText('<b>Some <i>HTML</i> text</b> and an image.<br><img src="cid:image1"><br>good!','html','utf-8')msgRoot.attach(msgText)f=open('12072113.JPG','rb')msgImage=MIMEImage(f.read())msgImage.add_header('Content-ID','<image>')msgRoot.attach(msgImage)f.close()smtp=smtplib.SMTP('smtp.163.com')smtp.login(username,password)smtp.sendmail(sender,receiver,msgRoot.as_string())smtp.quit()
發送帶附件的郵件:
步驟如下:
1. 構造MIMEMultipart對象做為根容器
2. 構造MIMEText對象做為郵件顯示內容並附加到根容器
3. 構造MIMEBase對象做為檔案附件內容並附加到根容器
a. 讀入檔案內容並格式化
b. 設定附件頭
4. 設定根容器屬性
5. 得到格式化後的完整文本
6. 用smtp發送郵件
import smtplibfrom email.mime.text import MIMETextfrom email.header import Headerfrom email.utils import COMMASPACE,formatdatefrom email.mime.image import MIMEImagefrom email.mime.multipart import MIMEMultipart,MIMEBasefrom email import encodersimport osimport mimetypesmailserver='smtp.163.com'sender = 'upc_dip@163.com'receiver = 'acprime@163.com'subject = 'hahahaha'smtpserver = 'smtp.163.com'username = 'upc_dip@163.com'password = 'upc_cs'file='D:\BDdownloads\WxBoxSetup.1398305031.exe'# 構造MIMEMultipart對象做為根容器msgRoot=MIMEMultipart()msgRoot['From']=sendermsgRoot['To']=receivermsgRoot['Subject']=Header('Image')# 構造MIMEText對象做為郵件顯示內容並附加到根容器text=MIMEText("This's my downloads",'plain','utf-8')msgRoot.attach(text)#測試識別檔案類型:mimetypes.guess_type(file_name) # ctype,encoding值:# rar 檔案 None None(ini檔案、csv檔案、apk檔案) # txt text/plain None # py text/x-python None # gif image/gif None # png image/x-png None # jpg image/pjpeg None # pdf application/pdf None # doc application/msword None # zip application/x-zip-compressed None data=open(file,'rb')ctype,encoding=mimetypes.guess_type(file)if ctype is None or encoding is not None: ctype='application/octet-stream'maintype,subtype=ctype.split('/',1)# 構造MIMEBase對象做為檔案附件內容並附加到根容器msg=MIMEBase(maintype,subtype)msg.set_payload(data.read())msg.add_header('Content-Disposition','attachment',filename=os.path.basename(file))data.close()encoders.encode_base64(msg)msgRoot.attach(msg)smtp=smtplib.SMTP(mailserver)smtp.login(username,password)smtp.sendmail(sender,receiver,msgRoot.as_string())smtp.quit()
或者像下面一樣:
att = MIMEText(open('h:\\python\\1.jpg', 'rb').read(), 'base64', 'utf-8') att["Content-Type"] = 'application/octet-stream' att["Content-Disposition"] = 'attachment; filename="1.jpg"' msgRoot.attach(att)
這裡有幾點需要說明:
1.Message對象包含兩部分:頭和承載(我理解應該就是攜帶的內容)所以set_payload就是設定負載
2.
也就是說MIMEImage和MIMEAudio內建了,其他的需要自己進行編碼
如果想發送群郵件其實就是把receiver寫成一個列表
剛開始不太明白MIMEMultipart和其他的區別
class email.mime.multipart.MIMEMultipart(_subtype=’mixed’, boundary=None, _subparts=None, **_params)
Module: email.mime.multipartA subclass of MIMEBase, this is an intermediate base class for MIME messages that are multipart. Optional _subtype defaults to mixed, but can be used to specify the subtype of the message. A Content-Type header of multipart/_subtype will be added to the message object. A MIME-Version header will also be added.
class email.mime.text.MIMEText(_text, _subtype=’plain’, _charset=None)
Module: email.mime.textA subclass of MIMENonMultipart, the MIMEText class is used to create MIME objects of major type text. _text is the string for the payload. _subtype is the minor type and defaults to plain. _charset is the character set of the text and is passed as an argument to the MIMENonMultipart constructor; it defaults to us-ascii if the string contains only ascii code points, and utf-8 otherwise.
其實MIMEMultipart就像是一個集合,可以在裡面添加其他的想要添加的東西
還可以添加音頻等等,詳見python文檔
email.message(發送的郵件資訊的介紹)
還有幾個問題是我剛開始疑惑地:
1.在添加附件時,需要自己設定郵件標頭,那麼怎麼設定,有哪些內容
2.構造MIMEBase對象時的參數是什麼(其實就是上面代碼中注釋的哪一些,也就是表明這是什麼類型的附件)
MIME協議分析
廖雪峰的官方網站(SMTP發送郵件)
MIME協議(包含比較全面的MIME類型)