1. 前言
互聯(lián)網(wǎng)發(fā)展到現(xiàn)在,郵件服務(wù)已經(jīng)成為互聯(lián)網(wǎng)企業(yè)中必備功能之一,應(yīng)用場(chǎng)景非常廣泛,比較常見的有:用戶注冊(cè)、忘記密碼、監(jiān)控提醒、企業(yè)營(yíng)銷等。
大多數(shù)互聯(lián)網(wǎng)企業(yè)都會(huì)將郵件發(fā)送抽取為一個(gè)獨(dú)立的微服務(wù),對(duì)外提供接口來支持各種類型的郵件發(fā)送。
中國(guó)的第一封電子郵件
1987 年 9 月 14 日中國(guó)第一封電子郵件是由“德國(guó)互聯(lián)網(wǎng)之父”維納·措恩與王運(yùn)豐在當(dāng)時(shí)的兵器工業(yè)部下屬單位—計(jì)算機(jī)應(yīng)用技術(shù)研究所(簡(jiǎn)稱
ICA)發(fā)往德國(guó)卡爾斯魯厄大學(xué)的,其內(nèi)容為德文和英文雙語,第一段大意如下:
原文:“ Across the Great Wall we can reach every corner in the world. ”
中文大意:“ 越過長(zhǎng)城,我們可以到達(dá)世界的每一個(gè)角落。 ”
這是中國(guó)通過北京與德國(guó)卡爾斯魯厄大學(xué)之間的網(wǎng)絡(luò)連接,發(fā)出的第一封電子郵件?,F(xiàn)在看這封郵件內(nèi)容,頗具深意!
2. 郵件協(xié)議
發(fā)送郵件的本質(zhì)是將一個(gè)人的信息傳輸給另外一個(gè)人,那么如何傳輸就需要商量好標(biāo)準(zhǔn),這些標(biāo)準(zhǔn)就是協(xié)議。最初只有兩個(gè)協(xié)議:
· SMTP 協(xié)議
SMTP 的全稱是 “Simple Mail Transfer
Protocol”,即簡(jiǎn)單郵件傳輸協(xié)議。它是一組用于從源地址到目的地址傳輸郵件的規(guī)范,通過它來控制郵件的中轉(zhuǎn)方式。它的一個(gè)重要特點(diǎn)是它能夠在傳送中接力傳送郵件,即郵件可以通過不同網(wǎng)絡(luò)上的主機(jī)接力式傳送。
SMTP 認(rèn)證,簡(jiǎn)單地說就是要求必須在提供了賬戶名和密碼之后才可以登錄 SMTP 服務(wù)器,這就使得那些垃圾郵件的散播者無可乘之機(jī)。增加 SMTP
認(rèn)證的目的是為了使用戶避免受到垃圾郵件的侵?jǐn)_。SMTP主要負(fù)責(zé)底層的郵件系統(tǒng)如何將郵件從一臺(tái)機(jī)器傳至另外一臺(tái)機(jī)器。
· POP3 協(xié)議
POP3 是 Post Office Protocol 3 的簡(jiǎn)稱,即郵局協(xié)議的第3個(gè)版本,它規(guī)定怎樣將個(gè)人計(jì)算機(jī)連接到 Internet
的郵件服務(wù)器和下載電子郵件的電子協(xié)議。
它是因特網(wǎng)電子郵件的第一個(gè)離線協(xié)議標(biāo)準(zhǔn),POP3 允許用戶從服務(wù)器上把郵件存儲(chǔ)到本地主機(jī)(即自己的計(jì)算機(jī))上,同時(shí)刪除保存在郵件服務(wù)器上的郵件。
POP 協(xié)議支持“離線”郵件處理。其具體過程是:郵件發(fā)送到服務(wù)器上,電子郵件客戶端調(diào)用郵件客戶機(jī)程序以連接服務(wù)器,并下載所有未閱讀的電子郵件。
這種離線訪問模式是一種存儲(chǔ)轉(zhuǎn)發(fā)服務(wù),將郵件從郵件服務(wù)器端送到個(gè)人終端機(jī)器上,一般是 PC 機(jī)或 MAC。
一旦郵件發(fā)送到 PC 機(jī)或 MAC上,郵件服務(wù)器上的郵件將會(huì)被刪除。但目前的 POP3 郵件服務(wù)器大都可以“只下載郵件,服務(wù)器端并不刪除”,也就是改進(jìn)的
POP3 協(xié)議。
SMTP 和 POP3 是最初的兩個(gè)協(xié)議,隨著郵件的不斷發(fā)展后來又增加了兩個(gè)協(xié)議:
· IMAP 協(xié)議
全稱 Internet Mail Access Protocol(交互式郵件存取協(xié)議),IMAP 是斯坦福大學(xué)在 1986
年開發(fā)的研發(fā)的一種郵件獲取協(xié)議,即交互式郵件存取協(xié)議,它是跟 POP3 類似郵件訪問標(biāo)準(zhǔn)協(xié)議之一。
不同的是,開啟了 IMAP
后,在電子郵件客戶端收取的郵件仍然保留在服務(wù)器上,同時(shí)在客戶端上的操作都會(huì)反饋到服務(wù)器上,如:刪除郵件,標(biāo)記已讀等,服務(wù)器上的郵件也會(huì)做相應(yīng)的動(dòng)作。
所以無論從瀏覽器登錄郵箱或者客戶端軟件登錄郵箱,看到的郵件以及狀態(tài)都是一致的。
IMAP 的一個(gè)與 POP3 的區(qū)別是:IMAP
它只下載郵件的主題,并不是把所有的郵件內(nèi)容都下載下來,而是你郵箱當(dāng)中還保留著郵件的副本,沒有把你原郵箱中的郵件刪除,你用郵件客戶軟件閱讀郵件時(shí)才下載郵件的內(nèi)容。
較好支持這兩種協(xié)議的郵件客戶端有:Foxmail、Outlook 等。
· Mime 協(xié)議
由于 SMTP 這個(gè)協(xié)議開始是基于純 ASCⅡ文本的,在二進(jìn)制文件上處理得并不好。后來開發(fā)了用來編碼二進(jìn)制文件的標(biāo)準(zhǔn),如 MIME,以使其通過 SMTP
來傳輸。
今天,大多數(shù) SMTP 服務(wù)器都支持 8 位 MIME 擴(kuò)展,它使二進(jìn)制文件的傳輸變得幾乎和純文本一樣簡(jiǎn)單。
用一張圖來看發(fā)送郵件過程中的協(xié)議使用:
實(shí)線代表?[email protected] 發(fā)送郵件給 [email protected];
虛線代表?[email protected] 發(fā)送郵件給 [email protected]
3. 郵件發(fā)送流程
發(fā)信人在用戶代理上編輯郵件,并寫清楚收件人的郵箱地址;
用戶代理根據(jù)發(fā)信人編輯的信息,生成一封符合郵件格式的郵件;
用戶代理把郵件發(fā)送到發(fā)信人的郵件服務(wù)器上,郵件服務(wù)器上面有一個(gè)緩沖隊(duì)列,發(fā)送到郵件服務(wù)器上面的郵件都會(huì)加入到緩沖隊(duì)列中,等待郵件服務(wù)器上的 SMTP
客戶端進(jìn)行發(fā)送;
發(fā)信人的郵件服務(wù)器使用 SMTP 協(xié)議把這封郵件發(fā)送到收件人的郵件服務(wù)器上
收件人的郵件服務(wù)器收到郵件后,把這封郵件放到收件人在這個(gè)服務(wù)器上的信箱中;
收件人使用用戶代理來收取郵件。首先用戶代理使用 POP3
協(xié)議來連接收件人所在的郵件服務(wù)器,身份驗(yàn)證成功后,用戶代理就可以把郵件服務(wù)器上面的收件人郵箱里面的郵件讀取出來,并展示給收件人。
這就是郵件發(fā)送的一個(gè)完整流程。
4. 簡(jiǎn)單使用
最早期的時(shí)候使用 JavaMail 相關(guān) API 來開發(fā),需要自己去封裝消息體,代碼量比較龐大;
后來 Spring 推出了 JavaMailSender 簡(jiǎn)化了郵件發(fā)送過程,JavaMailSender
提供了強(qiáng)大的郵件發(fā)送功能,可支持各種類型的郵件發(fā)送。
現(xiàn)在 Spring Boot 在 JavaMailSender 的基礎(chǔ)上又進(jìn)行了封裝,就有了現(xiàn)在的
spring-boot-starter-mail,讓郵件發(fā)送流程更加簡(jiǎn)潔和完善。
下面給大家介紹如何使用 Spring Boot 發(fā)送郵件。
<1> pom 包配置
引入加 spring-boot-starter-mail 依賴包:
<2> 配置文件
在?application.properties?中添加郵箱配置,不同的郵箱參數(shù)稍有不同,下面列舉幾個(gè)常用郵箱配置:
163 郵箱配置:
126 郵箱配置
qq 郵箱配置如下:
注意:
測(cè)試時(shí)需要將?spring.mail.username?和?spring.mail.password?改成自己郵箱對(duì)應(yīng)的登錄名和密碼,這里的密碼不是郵箱的登錄密碼,是開啟
POP3 之后設(shè)置的客戶端授權(quán)密碼。
這里以 126 為郵件舉例,有兩個(gè)地方需要郵箱中設(shè)置:
開啟 POP3/SMTP 服務(wù)、IMAP/SMTP 服務(wù)
圖片下方會(huì)有 smtp 等相關(guān)信息的配置提示。
開通設(shè)置客戶端授權(quán)密碼
設(shè)置客戶端授權(quán)密碼一般需要手機(jī)驗(yàn)證碼驗(yàn)證。
<3> 文本郵件發(fā)送
Spring 已經(jīng)幫我們內(nèi)置了 JavaMailSender,直接在項(xiàng)目中引用即可。我們封裝一個(gè) MailService 類來實(shí)現(xiàn)普通的郵件發(fā)送方法。
文本郵件抄送使用:message.copyTo(copyTo)?來實(shí)現(xiàn)。
from,即為郵件發(fā)送者,一般設(shè)置在配置文件中
to,郵件接收者,此參數(shù)可以為數(shù)組,同時(shí)發(fā)送多人
subject,郵件主題
content,郵件的主體
郵件發(fā)送者?from?一般采用固定的形式寫到配置文件中。
<4> 編寫 test 類進(jìn)行測(cè)試
稍微等待幾秒,就可以在郵箱中找到此郵件內(nèi)容了。至此一個(gè)簡(jiǎn)單的文本郵件發(fā)送就完成了。
5. 富文本郵件
在日常使用的過程中,我們通常在郵件中加入圖片或者附件來豐富郵件的內(nèi)容,下面講介紹如何使用 Spring Boot 來發(fā)送富文本郵件。
發(fā)送 HTML 格式郵件
郵件發(fā)送支持以 HTML 語法去構(gòu)建自定義的郵件格式,Spring Boot 支持使用 HTML 發(fā)送郵件。
我們?cè)?MailService 中添加支持 HTML 郵件發(fā)送的方法.
富文本郵件抄送使用:helper.addCc(cc)?來實(shí)現(xiàn)。
和文本郵件發(fā)送代碼對(duì)比,富文本郵件發(fā)送使用 MimeMessageHelper 類。MimeMessageHelper
支持發(fā)送復(fù)雜郵件模板,支持文本、附件、HTML、圖片等,接下來會(huì)一一使用到。
在測(cè)試類中構(gòu)建 HTML 內(nèi)容,測(cè)試發(fā)送
郵件內(nèi)容大寫了一段話,下面為接收到的效果:
由此我們發(fā)現(xiàn)發(fā)送 HTML 郵件,就是需要拼接一段 HTML 的 String 字符串交給 MimeMessageHelper
來處理,最后由郵件客戶端負(fù)責(zé)渲染顯示內(nèi)容。
發(fā)送帶附件的郵件
在 MailService 添加 sendAttachmentsMail 方法,發(fā)送帶附件的郵件主要是使用 FileSystemResource
對(duì)文件進(jìn)行封裝,在添加到 MimeMessageHelper 中。
添加多個(gè)附件可以使用多條?helper.addAttachment(fileName, file)
在測(cè)試類中添加測(cè)試方法
附件可以是圖片、壓縮包、Word 等任何文件,但是郵件廠商一般都會(huì)對(duì)附件大小有限制,太大的附件建議使用網(wǎng)盤上傳后,在郵件中給出鏈接。
效果圖如下:
發(fā)送帶靜態(tài)資源的郵件
郵件中的靜態(tài)資源一般指圖片,在 MailService 添加 sendInlineResourceMail 方法。
在測(cè)試類中添加測(cè)試方法
添加多個(gè)圖片可以使用多條?<img src='cid:" + rscId + "' >?和helper.addInline(rscId, res)?來實(shí)現(xiàn)
6. 郵件系統(tǒng)
如果只是想在系統(tǒng)中做一個(gè)郵件工具類的話,以上的內(nèi)容基本就可以滿足要求了。要做成一個(gè)郵件系統(tǒng)的話還需要考慮以下幾方面:
對(duì)外提供發(fā)送郵件的服務(wù)接口
固定格式郵件是否考慮使用模板
發(fā)送郵件時(shí)出現(xiàn)網(wǎng)絡(luò)錯(cuò)誤,是否考慮適當(dāng)?shù)闹卦嚈C(jī)制
郵件系統(tǒng)是否考慮異步化,提升服務(wù)響應(yīng)時(shí)間
是否開發(fā)郵件后臺(tái)管理系統(tǒng),開發(fā)出對(duì)應(yīng)的管理軟件,通過頁(yè)面發(fā)送郵件,統(tǒng)計(jì)發(fā)送郵件成功率等數(shù)據(jù)。
常見異常處理措施
6.1 對(duì)外提供接口
作為一個(gè)獨(dú)立的郵件系統(tǒng),需要對(duì)外提供接口調(diào)用,我們以簡(jiǎn)單文本郵件為例做個(gè)演示:
首先需要定義個(gè)實(shí)例返回對(duì)象:
默認(rèn)成功的返回碼為:00,返回消息為:發(fā)送成功。
創(chuàng)建一個(gè) MailController 類對(duì)外提供 HTTP 請(qǐng)求接口。
外部請(qǐng)求過來時(shí)首先進(jìn)行參數(shù)校驗(yàn),如果參數(shù)有誤返回請(qǐng)求;發(fā)送郵件出現(xiàn)異常時(shí)返回錯(cuò)誤,正常情況下返回 00;
注意在 Service 層如果對(duì)異常信息進(jìn)行了捕獲的話,需要將異常信息拋到上層。
類似上述代碼。
按照這個(gè)思路也可以提供發(fā)送帶圖片、帶附件的郵件,同時(shí)也可以封裝發(fā)送多人郵件,群發(fā)郵件等復(fù)雜情況。
6.2 郵件模板
通常我們使用郵件發(fā)送服務(wù)的時(shí)候,都會(huì)有一些固定的場(chǎng)景,比如重置密碼、注冊(cè)確認(rèn)等,給每個(gè)用戶發(fā)送的內(nèi)容可能只有小部分是變化的。
所以,很多時(shí)候我們會(huì)使用模板引擎來為各類郵件設(shè)置成模板,這樣我們只需要在發(fā)送時(shí)去替換變化部分的參數(shù)即可。
我們會(huì)經(jīng)常收到這樣的郵件:
尊敬的 neo 用戶:
恭喜您注冊(cè)成為 xxx 網(wǎng)的用戶,同時(shí)感謝您對(duì) xxx 的關(guān)注與支持并歡迎您使用 xx 的產(chǎn)品與服務(wù)。
?...
郵件正文只有?neo?這個(gè)用戶名在變化,郵件其它內(nèi)容均不變,如果每次發(fā)送郵件都需拼接 HTML
代碼,程序不夠優(yōu)雅,并且每次郵件正文有變化都需修改代碼非常不方便。
因此對(duì)于這類郵件,都建議做成郵件模板來處理,模板的本質(zhì)很簡(jiǎn)單,就是在模板中替換變化的參數(shù),轉(zhuǎn)換為 HTML 字符串即可,這里以 Thymeleaf
為例來演示。
Thymeleaf 是 Spring 官方推薦的前端模板引擎,類似 Velocity、FreeMarker
等模板引擎,相較與其他的模板引擎,Thymeleaf 開箱即用的特性。
它提供標(biāo)準(zhǔn)和 Spring 標(biāo)準(zhǔn)兩種方言,可以直接套用模板實(shí)現(xiàn) JSTL、 OGNL 表達(dá)式效果,避免每天套模板、該 Jstl、改標(biāo)簽的困擾。
Thymeleaf 在有網(wǎng)絡(luò)和無網(wǎng)絡(luò)的環(huán)境下皆可運(yùn)行,即它可以讓美工在瀏覽器查看頁(yè)面的靜態(tài)效果,也可以讓程序員在服務(wù)器查看帶數(shù)據(jù)的動(dòng)態(tài)頁(yè)面效果。
下面我們來演示使用 Thymeleaf 制作郵件模板:
1. 添加依賴包
2. 在?resorces/templates?下創(chuàng)建?emailTemplate.html
emailTemplate.html?文件內(nèi)容即為郵件的正文內(nèi)容模板。
我們發(fā)現(xiàn)上述的模板中只有?id?是一個(gè)動(dòng)態(tài)的值,發(fā)送過程中會(huì)根據(jù)傳入的?id?值來替換鏈接中的?{id}。
3. 解析模板并發(fā)送
我們發(fā)現(xiàn)最后調(diào)用的還是 sendHtmlMail 的方法,郵件模板的作用只是處理 HTML 生成部分,通過 Thymeleaf
模板引擎解析固定的模板,再更具參數(shù)來動(dòng)態(tài)替換其中的變量,最后通過前面的 HTML 發(fā)送的方法發(fā)送郵件。
效果圖如下:
點(diǎn)擊“激活賬號(hào)”跳轉(zhuǎn)的鏈接為:http://www.ityouknow.com/register/006
6.3 發(fā)送失敗
因?yàn)楦鞣N原因,總會(huì)有郵件發(fā)送失敗的情況,比如:郵件發(fā)送過于頻繁、網(wǎng)絡(luò)異常等。在出現(xiàn)這種情況的時(shí)候,我們一般會(huì)考慮重新重試發(fā)送郵件,會(huì)分為以下幾個(gè)步驟來實(shí)現(xiàn):
接收到發(fā)送郵件請(qǐng)求,首先記錄請(qǐng)求并且入庫(kù)。
調(diào)用郵件發(fā)送接口發(fā)送郵件,并且將發(fā)送結(jié)果記錄入庫(kù)。
啟動(dòng)定時(shí)系統(tǒng)掃描時(shí)間段內(nèi),未發(fā)送成功并且重試次數(shù)小于3次的郵件,進(jìn)行再次發(fā)送.
重新發(fā)送郵件的時(shí)間,建議以 2 的次方間隔時(shí)間,比如:2、4、8、16 ...
常見的錯(cuò)誤返回碼:
421 HL:ICC 該IP同時(shí)并發(fā)連接數(shù)過大,超過了網(wǎng)易的限制,被臨時(shí)禁止連接。
451 Requested mail action not taken: too much fail authentication
登錄失敗次數(shù)過多,被臨時(shí)禁止登錄。請(qǐng)檢查密碼與帳號(hào)驗(yàn)證設(shè)置
553 authentication is required,密碼配置不正確
554 DT:SPM 發(fā)送的郵件內(nèi)容包含了未被許可的信息,或被系統(tǒng)識(shí)別為垃圾郵件。請(qǐng)檢查是否有用戶發(fā)送病毒或者垃圾郵件;
550 Invalid User 請(qǐng)求的用戶不存在
554 MI:STC 發(fā)件人當(dāng)天內(nèi)累計(jì)郵件數(shù)量超過限制,當(dāng)天不再接受該發(fā)件人的投信。
如果使用一個(gè)郵箱頻繁發(fā)送相同內(nèi)容郵件,也會(huì)被認(rèn)定為垃圾郵件,報(bào) 554 DT:SPM 錯(cuò)誤
如果使用網(wǎng)易郵箱可以查看這里的提示:企業(yè)退信的常見問題?
6.4 異步發(fā)送
很多時(shí)候郵件發(fā)送并不是主業(yè)務(wù)必須關(guān)注的結(jié)果,比如通知類、提醒類的業(yè)務(wù)可以允許延時(shí)或者失敗。這個(gè)時(shí)候可以采用異步的方式來發(fā)送郵件,加快主交易執(zhí)行速度。
在實(shí)際項(xiàng)目中可以采用消息中間件 MQ
發(fā)送郵件,具體做法是創(chuàng)建一個(gè)郵件發(fā)送的消息隊(duì)列,在業(yè)務(wù)中有需要用到郵件發(fā)送功能時(shí),給對(duì)應(yīng)消息隊(duì)列按照規(guī)定參數(shù)發(fā)送一條消息,郵件系統(tǒng)監(jiān)聽此隊(duì)列,當(dāng)有消息過來時(shí),處理郵件發(fā)送的邏輯。
6.5 管理后臺(tái)
考慮做一個(gè)完善的郵件系統(tǒng),可以設(shè)計(jì)一個(gè)獨(dú)立的郵件管理后臺(tái),不但可以讓系統(tǒng)之間調(diào)用時(shí)使用,也可以提供圖形化界面讓公司的運(yùn)營(yíng)、市場(chǎng)部的同事來發(fā)送郵件,查詢郵件的發(fā)送進(jìn)度,統(tǒng)計(jì)郵件發(fā)送成功率。
也可以設(shè)置一些代碼鉤子,統(tǒng)計(jì)用戶點(diǎn)擊固定鏈接次數(shù),方便公司營(yíng)銷人員監(jiān)控郵件營(yíng)銷轉(zhuǎn)化率。
一個(gè)非常完善的郵件系統(tǒng)需要考慮的因素非常多,比如是否設(shè)置白名單、黑名單來做郵件接收人的過濾機(jī)制,是否給用戶提供郵件退訂的接口等。
因此在初期郵件發(fā)送的基本功能完成之后,再結(jié)合公司業(yè)務(wù),快速迭代的逐步完善郵件系統(tǒng),是一個(gè)推薦的做法。
7. 總結(jié)
使用 Spring Boot 集成發(fā)送郵件的功能非常簡(jiǎn)單,只需要簡(jiǎn)單編碼就可以實(shí)現(xiàn)發(fā)送普通文本郵件、帶附件郵件、HTML 格式郵件、帶圖片郵件等。
如果需要做成一個(gè)郵件系統(tǒng)還需要考慮很多因素,比如:郵箱發(fā)送失敗重試機(jī)制、防止郵件被識(shí)別為垃圾郵件,固定時(shí)間內(nèi)發(fā)送郵件的限制等。
在微服務(wù)架構(gòu)中,常常將一些基礎(chǔ)功能下沉下來,作為獨(dú)立的服務(wù)來使用,郵件系統(tǒng)作為平臺(tái)的基礎(chǔ)功能,特別適合做為獨(dú)立的微服務(wù)來支持整個(gè)系統(tǒng)。
歡迎工作一到五年的Java工程師朋友們加入Java架構(gòu)開發(fā):860113481
群內(nèi)提供免費(fèi)的Java架構(gòu)學(xué)習(xí)資料(里面有高可用、高并發(fā)、高性能及分布式、Jvm性能調(diào)優(yōu)、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個(gè)知識(shí)點(diǎn)的架構(gòu)資料)合理利用自己每一分每一秒的時(shí)間來學(xué)習(xí)提升自己,不要再用"沒有時(shí)間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個(gè)交代!
熱門工具 換一換