PHP 和 Drupal 使用 SMTP 發信

事情的開始

前陣子突然發現承租的主機好像沒有發信給自己。不管是 Drupal 系統的留言通知,或是自己寫的一些 PHP 程式,都沒有寄各種通知信給我,上站一測,果然如此,這可不得了,立刻通知主機商,請他們處理。過去也有類似的經驗,他們通常很快就處理好了。

主機商不久來信,信中說是 GMail 擋了我的信,系統本身並沒有問題,還要我參考這一篇:

https://support.google.com/mail/answer/81126#authentication

我再上站測試,果然寄信到 outlook.com 的信箱沒有問題,寄到 gmail.com 就石沈大海,心中隱隱覺得不妙。

上面那個網址,我看了半天,也不知是怎麼回事。上網查了一些資料,猜想大概是站上的註冊信與廣告信太多,gmail 可能懷疑我的主機有問題,因此要求進一步的認證,否則就不接受這個主機寄出來的信。

於是上網向各方求助,也請主機商再指導一番。他們來信說,我使用的都是 php mail(),建議我改用 smtp 的方式寄信,另外也要我啟用 SPF 和 DKIM,一些朋友也是如此建議,雖然不太懂,但至少有進一步研究的方向了。

 

SPF 和 DKIM 的設定

 
這二個東西還是第一次聽到,又上網去查,大概得知主要的目的是要驗證一封信的來源是不是可靠的,底下概略說明,細節請大家自行去找囉。
 
因為一般在寄 email 時,可以很容易設定寄件者,例如我可以設定寄件者是 xxx@abc.com,但實際上是由其他非 abc.com 的主機寄出的。現在為了避免這種有可能假冒別人寄信的情況,因此用這些設定可以來判斷。
 
SPF (Sender Policy Framework) 的作法是在 abc.com 主機的 DNS TXT 記錄中指定了可以寄信的 IP,底下只是一種範例,還有許多設定法,也可以指定寄信的網域。
 
名稱 : abc.com.
值 : v=spf1 +mx +a +ip4:xxx.xxx.xxx.xxx ~all
 
而收信的主機收到了 abc.com 寄來的信,就會去查 abc.com 的 SPF 設定,檢查 IP 來源或網域是否符合?若符合就表示沒問題,不是偽造的信件。
 
DKIM (DomainKeys Identified Mail) 則是使用電腦數位簽章,也就是用公鑰與私鑰這種加密驗證法。它是在 DNS 的 TXT 記錄公鑰,例如:
 
名稱 : default._domainkey.abc.com.
值 : v=DKIM1; k=rsa; p=xxxxxx (公鑰) xxxxxxx;
 
發信時則用私鑰進行簽章,收信的主機收到了 abc.com 寄來的信,就會去查 abc.com 的 DSN 記錄的 DKIM 公鑰,再針對信件的簽章解碼,如果解碼成功就表示信件沒問題,不是偽造的信件。
 
底下是摘錄一封 spf 和 dkim 驗證成功的信件原始內容。
 
ARC-Authentication-Results: i=1; mx.google.com;
       dkim=pass header.i=@abc.com header.s=default header.b=xxxxxx;
       spf=pass (google.com: domain of xxx@abc.com designates xxx.xxx.xxx.xxx as permitted sender) smtp.mailfrom=xxx@abc.com
 
這二個的設定細節我也沒有深入了解,只知道主機的 cPanel 控制台可以設定,我就依要求逐一處理,很快就設定完成了。
 
這個網址可以用來測試是否設定成功
 
 

php mail() 和其用法

 

php mail() 就是一般我在 php 上寄信的程式,原本都用的很開心,現在才知道因為 mail() 程式是透過程式直接寄信,收信端無法反向查詢驗證,所以經常會有被阻擋當成垃圾信件的情況,很多主機已經不接收這類的信了,要改成使用 smtp 才行。

在此先記錄一下自己過去是如何使用 mail 來寄信。

 

<?php

if (! utf8mail("寄信者@gmail.com", "收信者@gmail.com", "主旨內容", "信件內容\n這是第二行"))

{

//.... error

}

 

function utf8mail($from, $to, $subject, $message)

{

$headers  = "MIME-Version: 1.0\r\n";

$headers .= "Content-Type: text/plain;charset=utf-8\r\n";

$headers .= "From: $from\nReply-To: $from\n";

$headers .= "X-Mailer: PHP/".phpversion();

$bool = mail($to, $subject, $message, $headers);

return $bool;

}

?>

 

使用 PHPMailer 用 smtp 寄信

 

SMTP 是簡單郵件傳輸協定(Simple Mail Transfer Protocol) ,這是在 Internet 上傳送 email 的一個協定,意思就是可以用它來寄信。

再度上網查詢,得知可以安裝一個套件 PHPMailer 就可以在 PHP 使用 smtp 寄信。問題來了,我不是主機的擁有者,無法直接安裝此套件。所幸後來找到有人有類似的問題,找到了只要複製幾個重要的檔案,就可以執行的方法了。

 

下載 PHPMailer v6.1.3

 
這一段是新加入處理 PHPMailer v6.1.3 的內容,底下還有舊版 v5.2.27 的舊文章。
 
PHPMailer 可在此下載
 
 
解壓縮後,直接將整個目錄 PHPMailer-6.1.3 放在網站上。
 
 

撰寫由 GMail SMTP 寄信的程式 (PHPMailer v6.1.3)

 
直接使用 PHPMailer 網站上的例子,底下是修改後測試成功的程式碼。
 
<?php
// Import PHPMailer classes into the global namespace
// These must be at the top of your script, not inside a function
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
 
// Load Composer's autoloader
// require 'vendor/autoload.php';
// 範例原本如上,但因為我沒有使用安裝的方式,所以就採用如下
 
require '/xxx/PHPMailer-6.1.3/src/Exception.php';
require '/xxx/PHPMailer-6.1.3/src/PHPMailer.php';
require '/xxx/PHPMailer-6.1.3/src/SMTP.php';
 
// 產生 Mailer 實體
$mail = new PHPMailer(true);
 
try {
    //Server settings
    // 產生 Debug 訊息
    $mail->SMTPDebug = SMTP::DEBUG_SERVER;
    
    $mail->isSMTP();
 
    // SMTP 伺服器的設定,以及驗證資訊  
    $mail->Host = 'smtp.gmail.com';
    $mail->SMTPAuth = true;
    // 底下也可以使用 PHPMailer::ENCRYPTION_SMTPS
    $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
    $mail->Port = 587;
 
    // SMTP 驗證的使用者資訊
    $mail->Username = 'xxx@gmail.com';
    $mail->Password = 'xxx';
 
    // 信件內容的編碼方式,這在範例中沒有,但若不加中文會有問題       
    $mail->CharSet = "utf-8";
    $mail->Encoding = "base64";
 
    // 寄信者與收信者
    $mail->setFrom('xxx@gmail.com', 'xxx');
    $mail->addAddress('xxx@abc.com', 'xxx');
    
    // 其他收信者(可不加名稱), 回信地址, CC副本, BCC 密件副本
    //$mail->addAddress('xxx@example.com');
    //$mail->addReplyTo('xxx@example', 'xxx');
    //$mail->addCC('cc@example.com');
    //$mail->addBCC('bcc@example.com');
 
    // 附件
    //$mail->addAttachment('/var/tmp/file.tar.gz');
    //$mail->addAttachment('/tmp/image.jpg', 'new.jpg');
 
    // 信件內容
    $mail->isHTML(true); // 若是純文字的信,此處用 false
    $mail->Subject = 'Here is the subject 中文標題主旨';
    $mail->Body = '使用中文';
    $mail->Body .= '<span style="color:red">紅色字</span>';
 
    $mail->send();
    echo '送出成功';
} catch (Exception $e) {
    echo "送出失敗: {$mail->ErrorInfo}";
}
?>
 
執行後就可以用 gmail 的 smtp 主機寄信了。
 
有一點要注意,若是要用 gmail 的主機寄信,則 gmail 要設定成「低安全性應用程式存取權」,可在登錄 Google 帳號後,瀏覽此頁說明。
 
 
如果還是失敗,出現 SMTP ERROR: Password command failed,可參考這篇
 
 
以及根據它的說明,至此網頁操作 Unlock。
 
 

舊版:下載 PHPMailer v5.2.27

 

PHPMailer 可在此下載

https://github.com/PHPMailer/PHPMailer/releases

因為我使用的是 PHP 5.3 ,所以選 PHPMailer 5.2.27 版。(當時以為 PHPMailer 6.X 是給 PHP 6.X 使用的)

解壓縮後,取出這四個檔案:

class.phpmailer.php
class.pop3.php
class.smtp.php
PHPMailerAutoload.php

在 php 程式碼的目錄中建一個子目錄,取名為 phpmailer,將這四個檔案放進去。

 

舊版:撰寫由 GMail SMTP 寄信的程式 (PHPMailer v5.2.27)

 

在 PHPMailer 的套件中就有不少例子,底下則是我抄自網路,修改後測試成功的程式碼。

 

<?php

require_once("phpmailer/PHPMailerAutoload.php");

 

// 產生 Mailer 實體

$mail = new PHPMailer();

// 設定為 SMTP 方式寄信

$mail->IsSMTP();

// SMTP 伺服器的設定,以及驗證資訊

$mail->Host = "smtp.gmail.com";

$mail->Port = 465;

$mail->SMTPAuth = true;

$mail->SMTPSecure = 'ssl';

// 信件內容的編碼方式       

$mail->CharSet = "utf-8";

// 信件處理的編碼方式

$mail->Encoding = "base64";

// SMTP 驗證的使用者資訊

$mail->Username = "myemail@gmail.com";  //mail的帳號(需要完整的mail帳號,含@後都要填寫)

$mail->Password = "password";  //密碼

// 信件內容設定  

$mail->From = "myemail@gmail.com"; //需要與上述的使用者資訊相同mail

$mail->FromName = "myemail"; //此顯示寄件者名稱

$mail->Subject = "信件標題"; //信件主旨

$mail->Body = "這是一封測是信!";   //信件內容

$mail->IsHTML(true);

// 收件人

$mail->AddAddress("someone@gmail.com", "someone"); //此為收件者的電子信箱及顯示名稱

// 顯示訊息

if(!$mail->Send()) {

echo "Mail error: " . $mail->ErrorInfo;     

} else {

echo "Mail sent";     

}

?>

 

執行後就可以用 gmail 的 smtp 主機寄信了。

有一點要注意,若是要用 gmail 的主機寄信,則 gmail 要設定成「低安全性應用程式存取權」,可在登錄 Google 帳號後,瀏覽此頁說明。

https://myaccount.google.com/lesssecureapps

 

撰寫由網站主機寄信的程式

 

上面是由 gmail smtp 寄信的測試,不過實際上還是希望由租用主機的 smtp 寄出,因此要修改一些資料。

首先要在租用的主機上建立一個信箱帳號,我以前都沒有建,因為我都直接把信件轉到 gmail,這次為了要使用主機的 smtp,因此建了帳號,設了密碼,再把上面測試的 php 程式碼修改相關的部份,所幸也測試成功了。

以上就是自行撰寫的 PHP 程式可以使用 smtp 寄信了。

 

在 Drupal 系統中使用 smtp 寄信

 

前面是介紹直接用 php 撰寫用 smtp 寄信的程式,目前遇到的問題,還有一個是需要讓 Drupal 系統使用 smtp 寄信。

在查詢的過程中,找到二個模組可以做到,分別是 SMTP Authentication Support 和 phpmailer。

 

模組 SMTP Authentication Support

 

SMTP Authentication Support 可在這裡下載。

https://www.drupal.org/project/smtp

依一般模組的安裝方式處理後,還要下載 PHPMailer 的程式,在此模組的 INSTALL.txt 有詳細說明,它是建議放在這裡

sites/all/libraries/phpmailer

我一切處理好之後,測試是失敗的。主要是出現這個訊息:

PHP Fatal error: Class 'SMTP' not found in ….class.phpmailer.php on line ….

後來也發現模組中有補丁要處理,因為懶得再深入,就打算試另一個模組。

 

模組 phpmailer

 

phpmailer 可在這裡下載。

https://www.drupal.org/project/phpmailer

我用的是 Drupal 6,所以選擇 phpmailer-6.x-3.1 版。後來發現它需要依靠 Libraries API 模組,所以我就選了不需要 Libraries 的 phpmailer-6.x-2.2 版。

這一版的 README.txt 裡面有說明,同樣要下載 PHPMailer for PHP5/6 套件,解開後將其中這二個檔案

class.phpmailer.php
class.smtp.php

放在模組本身的 phpmailer 子目錄裡面,這個子目錄在安裝模組時已經先有預先準備好了。

不過在測試時,同樣有問題,訊息也是

PHP Fatal error: Class 'SMTP' not found in ….class.phpmailer.php on line ….

查了一些資料,我猜測可能當時的 phpmailer 套件是可以用的,但我下載比較新的版本,裡面還有

PHPMailerAutoload.php

有人說這是比較好的載入點,要改成這個。

不過我也不知怎麼改,明明也有 class.smtp.php,裡面就有 'SMTP' 類別,不知為何沒有捉到?

於是我用暴力解決法,把 class.smtp.php 的內容全部複製到 class.phpmailer.php 裡面,我就不信它還找不到。

這一次果然就成功了,順利寄出測試信了。 :)

我想之前使用 SMTP Authentication Support 也是類似的問題,但已經不想回頭去測試了。

 

設定模組 phpmailer

 

我把 Drupal 設定的畫面也記錄下來。同時啟用翻譯,看起來比較親切。

這是最基本的主機設定。

 

image

 

這是要使用 gmail 的帳號和密碼。

 

image

 

設定寄件者

 

image

 

測試寄件

 

image

 

PHP 錯誤小插曲

 

這裡再記錄過程中遇到的二個小插曲。

 

is_a() 的錯誤

 

第一個是在測試 PHPMailer 時就發生了。錯誤訊息如下:

Strict Standards: is_a(): Deprecated. Please use the instanceof operator in ......

問題發生在這一行:

if (is_a($this->smtp, 'SMTP')) {

查到資料說,is_a 這個函數在 PHP 5.2.x 是不能用的,可以用 instanceof 來取代,也就是改成如下,注意這個用法中的 SMTP 沒有使用引號。

if ($this->smtp instanceof SMTP)) {

 

改過之後就可以使用了。

有人說 is_a() 有點危險,所以不再使用,但在 PHP 5.3.x 又可以用了。

 

goto() 的錯誤

 

我原本系統的 PHP 也是使用 5.2,後來想改成 5.3,但有一個新的錯誤訊息如下。

Parse error: syntax error, unexpected T_GOTO, expecting T_STRING in …

查詢後得知,在 PHP 5.3.x ,goto 這個名稱變成保留字,所以要把程式中用到使用 goto 名稱的自訂函數要改掉,這樣問題就解決了。

 

至此,PHP + SMTP 的技能終於解鎖成功。 ^_^

 

重要度:
文章分類:

發表新回應

借我放一下廣告