不知從何時開始, PHP 內建的 mail() 行為改變了,使用 mail() 寄發電子郵件時,似乎會固定將內文之字元編碼轉為 iso-8859-1 字元集。於是用 mail() 寄中文郵件時變亂碼、寄東歐文字郵件時變亂碼、寄日文郵件時變亂碼等等問題一一出現。如何寄非英文語系郵件幾乎成了 FAQ 級問題。
不知從何時開始, PHP 內建的 mail() 行為改變了,使用 mail() 寄發電子郵件時,似乎會固定將內文之字元編碼轉為 iso-8859-1 字元集。於是用 mail() 寄中文郵件時變亂碼、寄東歐文字郵件時變亂碼、寄日文郵件時變亂碼等等問題一一出現。如何寄非英文語系郵件幾乎成了 FAQ 級問題。
早期的 mail() 並沒有這種狀況。基本上只要在信件的標頭欄位中添加文件型態及字元集(charset)資訊,如 'Content-type: text/plain; charset="big5"' 即可用以寄發中文郵件。但不知何時開始,這種方式不再適用。在 PHP manual 中也未提到這方面的變化,僅僅說可用 PEAR::Mail 完成更複雜郵件寄送工作。另外隱晦地加上 mb_send_mail() ,表示應該使用此一函數寄發多位元組字元集文字郵件。
為了測試前述問題所在,以及完成寄送中文郵件的工作,我設計了一個替用的 mail 函數。對於了解 SMTP 協定內容的程序員而言,要自行設計一個 mail() 並不困難。下列便是我設計的 mail() 替用函數。它是一個對照組,用以測試並確認郵件內容變亂碼是 PHP 內建的 mail() 所造成的。由於設計目的之中包含了測試用途,所以程式碼中還留有觀察訊息的敘述。
MyMailer 以靜態方法 MyMailer::mail() 實現郵件寄送行為,其用法與 PHP 內建的 mail() 完全相同。它將讀取 php.ini 中的 SMTP 與 sendmail_from 之設定值內容決定 SMTP 伺服器位址以及寄信人資訊,接著開啟 socket 連線直接與 SMTP 伺服器交談以完成郵件遞送工作。
以下為一測試範例,將同一訊息分別以 PHP的 mail() 和我設計的 MyMailer::mail() 寄送,藉此觀察能否寄送中文郵件。從測試結果來看,我們可以發現 PHP 的 mail() 所寄送之內容會成亂碼 (但也有人不會碰到這種情形)。
由於 PHP 內建的 mail() 功能頗為簡單,用於寄送簡單的 log 訊息尚可,寄送一般信件就略顯力有未逮。因此在開發應用軟體時,建議使用 PEAR::Mail 或是 PHPMailer。不僅可解決寄送中文郵件的困擾,尚可寄送夾帶附件的郵件,應多加利用。雖然兩者功能類似,但我建議依授權方式選擇。 PEAR::Mail 採 PHP/BSD license 授權發佈,而 PHPMailer 採 GPL/LGPL 授權發佈。故偏好 GPL/LGPL 授權的程序員應選擇採用 PHPMailer 。啥?用本文中的 MyMailer ? 那也成,順便提醒一下, MyMailer 也是採 GPL/LGPL 授權發佈。
標題和寄信人的內容是亂碼之原因在於未指定字元編碼。此處常引起誤解,程序員會說「我已經在Content-type中指定 charset 了啊」。內文的字元編碼可透過 Content-type 指定字元集(charset) ,但標題和寄信人的內容並非內文之一部份,故其字元編碼並非由 Content-type 所指定。事實上,標題和寄信人屬於郵件 Header ,其規範內容為 RFC 2047 - MIME (Multipurpose Internet Mail Extensions) Part Three: Message Header Extensions for Non-ASCII Text。根據該規範內容, Header 中的非 ASCII 字元之內容應以 「=?charset encoding?encoding code?header content?=」之格式指定。起於=? ,止於 ?=。encoding code 一般為 "q": Quoted-printable ;或 "b": Base64。
由於 PHP 僅提供 base64_encode() ,故下列範例之 encoding code 一律為 b 。
$mailer->IsSMTP(); 或 $mailer->Mailer = "smtp"; 。
我也是使用虛擬主機,也遇到亂碼的問題,剛剛看到一篇文章,只用了簡單兩行(用mb_send_mail代替mail),就解決了我的問題,提供大家參考。