轉載請註明文章出處: tlanyan.me/php-review-…
PHP回顧系列目錄
傳送郵件是網站的常用功能,使用者啟用、找回密碼等場景常需要傳送郵件到使用者郵箱。本文先回顧傳送郵件的相關概念,再給出使用PHP傳送郵件的示例程式碼。
傳送簡訊
從功能上看,簡訊和郵件類似,用途常是通知和安全校驗。傳送簡訊(基本上)需要向供應商付費,所以簡訊供應商有動力提供清晰的文件,易用的介面方便使用者接入。一般而言,傳送簡訊的是:
- 尋找供應商,例如阿里大魚、聚合資料等;
- 註冊賬戶,獲取appid和appkey;
- 申請模板;
- 檢視介面文件,整合到應用中;
- 呼叫API傳送簡訊。
流程簡單易懂,接入和使用也十分便捷,基本上一兩小時內就能對接和測試好。使用者無需考慮訊息在通訊過程中的編碼、定址下發等細節,缺點是要付費。
郵件一般是免費服務,相關支援沒那麼到位,這也要理解。各種程式語言傳送郵件的類庫不少,從信源角度看基本可以分成兩類:從本機傳送和從第三方郵件服務商傳送。為了理解郵件傳送的流程,先介紹一些相關概念。
相關概念
大部分接觸到網際網路的人都有使用郵件的經驗,但基本上限於郵件客戶端、網頁端和提供商這幾個概念。作為一個開發,理解本節中的以下概念能更好的幫你掌握郵件通訊中的細節。
MUA : Mail User Agent,郵件使用者代理。使用者代理是開發中經常接觸到的詞,主要指 理解人的意圖並代表使用者向資源方請求的工具。例如瀏覽器是最常用的使用者代理,以HTTP/HTTPS協議格式向web伺服器傳送請求,並解析響應,渲染後呈現給使用者。郵件使用者代理,常見的是Foxmail、Outlook這類工具,人們寫好郵件後,按格式封裝郵件內容與郵件伺服器通訊。
MTA : Mail Transfer Agent,郵件傳輸代理,幫使用者收發郵件的程式。常說的郵件伺服器指的就是MTA,開源的程式有sendmail,postfix,QMail等。
MRA : Mail Retrieval Agent,郵件收取代理,將使用者的郵件從郵件伺服器取回本地。郵件客戶端是常見的MRA。
SMTP : Simple Mail Transfer Protocol,簡單郵件傳輸協議。使用者與郵件伺服器、郵件伺服器互相傳遞郵件均使用該協議(預設明文,可使用SSL\TLS加密)。
POP3/IMAP : Post Office Protocol version 3/Internet Message Access Protocol,郵局協議版本3或網路資訊獲取協議,客戶端從服務端獲取郵件時使用的協議。
使用者A(163郵箱)向使用者B(Gmail郵箱)發信,使用者B獲取信件的過程涉及到上述的概念。流程和概念關係可用如下簡圖表示:
使用者A --傳送郵件--> 使用者B
M|S M|I
U|M R|M
A|T A|A
|P |P
v v
MTA(163)--轉發(SMTP)->MTA(gmail)
複製程式碼
注:上圖給出的是郵件傳送的大體流程,其他MSA、MDA、ESMTP、SMTPS等可能會出現在整個流程中,但不影響郵件收發的理解。下文中會提到的縮寫和概念會註明,其他請自行查詢。
postfix
Linux下傳送郵件的軟體主要是sendmail和postfix,它們在系統中充當上文概念中的MTA/MDA(Mail Delivery Agent,郵件投遞代理)角色。它幫助使用者向外傳送郵件,接收郵件投遞到使用者信箱(預設位置/var/spool/mail/使用者名稱)。
sendmail是老牌的郵件軟體,知名度非常高。但是Wietse(Wietse Zweitze Venema)用的不爽,於是有了postfix。postfix命令(幾乎)相容於sendmail,但更高效和安全(字尾fix的由來),是目前大部分Linux發行版的預設郵件收發軟體,推薦使用postfix而非sendmail(本部落格多年前有篇文章寫如何配置sendmail,那時年少無知見識少,打算抽空把那篇文章改一下)。
postfix的主要配置檔案是/etc/postfix/main.cf
,配置檔案的註釋非常全,選項基本是自解釋的。最重要的幾個配置是:myhostname
、myorigin
、inet_interfaces
、inet_protocols
以及mydestination
(如果你打算收外網來信的話)。需要注意inet_interfaces
配置為localhost
時,inet_protocols
的值應為ipv4,否則可能會出現類似postfix: fatal: parameter inet_interfaces: no local interface found for ::1
的錯誤提示。
與郵件相關的幾個常用postfix命令是:
-
postquque
,檢視郵件傳送佇列。postqueue -p
可取代sendmail
中的mailq
命令,postqueue -f
重新整理佇列(強制嘗試傳送佇列中的郵件)。 -
postcat
,檢視未傳送郵件的資訊。例如postcat -q xxxx
(xxxx是postqueue或者mailq顯示的未傳送佇列ID)可檢視郵件的詳細資訊,postcat -b -q xxxxx
只檢視郵件正文。 -
postsuper
,超級使用者才可使用的郵件管理程式。postsuper -d xxxx
,刪除佇列ID為xxxxx的郵件;postsuper -h xxxxx
,暫停佇列ID為xxxx的郵件傳送,等。
以上介紹對於傳送郵件基本已足夠。注意,mail命令傳送的郵件能投遞的前提是postfix
正在執行(ps aux | grep postfix | grep -v grep輸出不為空)。
有了postfix,配置好後可以對外傳送郵件,也能收取外網傳送過來的郵件,但限於命令列操作。想用foxmail等客戶端收發郵件,需要讓伺服器支援POP3/IMAP協議。開源的dovecot可以實現這個功能。dovecot服務於收郵件而非傳送,瞭解其對開發中的幫助不大。如果想搭建一套完整的郵件系統(包括網頁端支援、垃圾郵件過濾、病毒查殺、傳輸加密等),建議參考或使用國產開源的 EwoMail。
瞭解postfix對開發中傳送郵件幫助有多大?說實話,幾乎沒有幫助。原因是為了防止垃圾郵件氾濫,各大雲伺服器廠商遮蔽了25埠(Google Cloud連465都幹掉了)。亞馬遜雲通過申請還有放行的可能(但有速率和每日額度限制),其他廠商幾乎不會讓你使用自己的域名從本機直接傳送郵件。封禁25埠,必須使用第三方的郵件服務,幾乎是業界的標準做法。
聰明的人可能想到,使用465加密埠(基於SMTPS,SMTP over SSL協議)或587埠(SMTP over STARTTLS協議)傳送郵件,是不是就能繞開限制了?阿里雲/騰訊雲等廠商並不封禁465埠,傳送郵件可以使用該埠而無需申請。但注意465和587埠是客戶端和郵件伺服器通訊使用的埠,郵件伺服器之間通訊使用25埠。你可以通過465埠連線到Gmail郵箱對外傳送郵件,但無法讓postfix使用465埠投遞郵件到hotmail郵件伺服器。
總結來說,sendmail/postfix作為垃圾和欺詐郵件氾濫前的郵件伺服器軟體,對業界貢獻很大。隨著雲伺服器的盛行,幾乎無法以指向本機的域名向外傳送郵件,sendmail/postfix除了在本機內傳送提醒郵件,用處已然不大。要對外傳送郵件,要麼自建機房,要麼使用第三方郵件系統。
PHP的mail函式
作為PHP開發中,瞭解sendmail/postfix還是有點用處。mail
函式預設使用sendmail/postfix傳送郵件,瞭解相關配置,就能知道為啥能工作/為啥不能工作。
簡單來說,要讓PHP自帶的mail函式正常工作,需要做以下事情:
- 申請域名,在DNS解析中設定MX記錄,指向本機(非合法主機(FQDN, Fully Qualified Domain Name)傳送的郵件都會被當做垃圾郵件直接丟棄);
- 安裝sendmail/postfix,配置軟體並執行;
- 配置防火牆、安全組,放行埠。
傳送效率低、非物件導向的呼叫方式,配置麻煩以及雲伺服器廠商的封鎖,是使用mail
函式的最大阻礙。所以做PHP以來,本人並未直接用過mail
函式。
PHP傳送郵件
發個郵件要了解這麼多,會讓人覺得很心累。說好的PHP是最好的語言呢?
PHP傳送郵件也可以很簡單,推薦方式就是使用Swift Mailer
或PHPMailer
等類庫。引入這些類庫後,註冊第三方郵箱(比如Gmail、QQ等),填好使用者名稱密碼,配置好STMP地址和埠,就能像傳送簡訊一樣輕鬆傳送郵件。當然這些類庫也支援使用sendmail/postfix傳送郵件,但我想你不會再這樣做了。
以Swift Mailer
為例,直接上程式碼說明使用PHP傳送郵件也是一個非常簡單的事情!
首先,在專案中引入Swift Mailer
:
composer require "swiftmailer/swiftmailer:^6.0"
複製程式碼
然後準備好郵件內容(以文字檔案為例,不帶附件):
$message = (new Swift_Message('Test Message'))
->setFrom(['tlanyan@tlanyan.me' => 'tlanyan'])
->setTo(['tlanyan1@tlanyan.me'])
->setBody('Hello, this is a test mail from Swift Mailer!');
複製程式碼
接著,設定好郵件傳輸方式(使用Gmail郵箱):
$transport = (new Swift_SmtpTransport('smtp.gmail.com', 465, 'ssl'))
->setUsername('username')
->setPassword('password');
複製程式碼
或者使用sendmail/postfix的方式(不推薦):
$transport = (new Swift_SendmailTransport());
複製程式碼
最後,使用transport
構造mailer
例項,傳送郵件:
$mailer = new Swift_Mailer($transport);
$result = $mailer->send($message);
複製程式碼
老闆再也不用擔心傳送郵件收不到了,So easy!
總結
本文先回顧了傳送郵件的相關概念,說明不推薦使用內建的mail
函式原因,最後給出了使用第三方類庫傳送郵件的程式碼示例。
感謝閱讀,歡迎評論指正!