2009-04-25 50 views
16

我想爲我的應用程序實現一個郵件列表系統。我目前使用Zend_Mail_Transport_Smtp('localhost')作爲我的交通工具,循環訪問我的用戶列表,並向每個用戶發送新的Zend_Mail。但是,我注意到隨着用戶數量的增加,腳本完成所需的時間也會增加。從Zend Framework應用程序向數百個收件人發送電子郵件的最佳方法是什麼?

我確信必須有一個更專業的方法來做到這一點,涉及電子郵件排隊。我認爲理想的方法是讓用戶填寫表單,點擊發送,然後立即得到一個回覆​​,表示電子郵件正在發送,而不是等待數百封電子郵件完成發送。

據我所知,Zend_Mail不做任何排序郵件排隊。任何有此經驗的人都可以給我一個關於如何做到這一點的概述。我對cron/crontab/cronjobs一無所知,所以如果涉及到這一點,請解釋一下過程。

回答

19

爲了使用PHP可靠地發送大量的電子郵件,你必須使用排隊機制。正如其他人則建議,使用隊列的過程看起來是這樣的:

  • 循環在你的用戶組,對每一個創建電子郵件,並可能自定義內容
  • 通過每個郵件對象到隊列這將延遲發送電子郵件,直到後來
  • 在某種cron腳本中,一次發送幾百個隊列的內容。 注意:您需要通過查看日誌查找實際發送過程中返回的錯誤來調整發送的電子郵件數量。如果您嘗試發送太多,我注意到它達到一個地步,郵件傳輸將不再接受連接(我使用Qmail)

有幾個庫,在那裏你可以用它來做到這一點,PEAR Mail Queue(Mail_Mime)和SwiftMailer都允許您創建和排隊電子郵件。到目前爲止,Zend Mail只提供創建電子郵件,而不是排隊(稍後更多)。

我有經驗主要與PEAR Mail Queue,並有幾個陷阱。如果您試圖排隊大量電子郵件(例如,循環使用20,000個用戶並試圖在合理的時間內將其加入隊列中),則使用Mail Mime的quoted-printable編碼實現非常緩慢。你可以通過切換到base64編碼來加快速度。對於Zend Mail,您可以編寫一個Zend郵件傳輸對象,它將您的Zend郵件對象放入PEAR郵件隊列中。我已經取得了一些成功,但需要一點努力才能做到。爲此,請擴展Zend郵件傳輸摘要,實現_sendMail方法(將您的Zend郵件對象放入郵件隊列中),並將傳輸對象的實例傳遞給Zend Mail對象的send()方法,或者通過Zend Mail :: setDefaultTransport()。

底線是有很多方法可以做到這一點,但這需要一些研究和學習。然而,這是一個非常可以解決的問題。

0

Zend Mail類看起來不錯,而且使用起來很簡單,它還允許您發送電子郵件的純文本和HTML版本,這在電子郵件營銷中非常重要。

如果你熟悉框架的工作,我會堅持下去。

重要的事情發送電子郵件到大量的人時,要考慮的是:

  • 你的網絡服務器能應對圖像請求時,郵件被打開+的人訪問您的網站服務器上的負載。

如果答案是否定的或者您不確定,使用apache基準測試應該能夠幫助您解決問題。如果您仍然不確定,最好批量發送電子郵件(可以使用crontab定時)來傳播負載。

我希望這會有所幫助。

+0

這是一個令人困惑的答案,因爲如果不使用cron的腳本,腳本超時時發送的電子郵件將成爲一個問題之前交通的關注。 – rick 2009-04-25 18:42:37

+0

我認爲菲爾建議您使用cron來限制發送電子郵件。例如,每30分鐘一次只發送100個,直到列表用盡。 – grossvogel 2009-04-25 19:05:28

+0

但他似乎建議crontab應該被用作高流量的解決方案?無論如何,我們都應該如此幸運,以便通過營銷活動產生過多的流量。有可能,這不是一個問題。 – rick 2009-04-25 19:10:31

3

來自PHP.net文檔。

注意:值得注意的是mail()函數不適合循環中的大量電子郵件。此功能爲每封電子郵件打開和關閉SMTP套接字,效率不高。
有關發送大量電子郵件的信息,請參閱»PEAR::Mail和»PEAR::Mail_Queue程序包。

Zend Mail類可能相當不錯(Zend的大部分東西都不錯)但是如果你想要其他的選擇。他們來了。

+0

PEAR :: Mail在我的經驗中很慢。 PHPMailer和swiftmailer都非常出色。 – rick 2009-04-25 18:19:13

2

雖然避免使用其他人注意到的郵件(),但您應該很好地使用PHP使用數千個收件人。我見過一些爲大量郵件而設計的系統(超過10萬名收件人)跳過了標準郵件功能,並試圖更直接地使用MTA。即使如此,我還不清楚這是必需的。

使電子郵件專業人員更關注確保格式是好的(HTML和純文本儘可能),人們可以輕鬆取消訂閱,反彈處理正確,郵件服務器具有所有正確的DNS記錄,並且服務器配置不違反任何主要黑名單系統的規則。您編寫應用程序的語言不是幾百甚至幾千條消息的主要因素。

18

注意:當我第一次看到你的問題時,我以爲它一次說幾十萬封電子郵件。當我加倍檢查時,我注意到它實際上說成百上千。我現在懶得改變我的帖子,所以這裏有一些警告:根據我的經驗,如果沒有商業工具,大概可以運行到40K左右。在大約10K時,你會希望遵循「最低限度」列表,以防止在開始到達更大的列表大小時出現嚴重的痛苦。我建議儘快實施它。

我以前說過這一點,有兩個方面發送電子郵件:

  1. 技術方 - RFC的周圍的SMTP 協議基本上都 ,電子郵件格式,DNS 記錄,等等。這是溫和的 複雜但可以解決。
  2. 神奇的一面 - 電子郵件傳遞 管理是巫術。你會得到 沮喪,事情將打破沒有 明顯的原因,你會 考慮離開另一份工作 不涉及電子郵件。

我建議您不要編寫自己的批量發件人。我相信PHP可以做得很好,但是你應該把時間花在其他地方。我過去使用過的兩種產品是Strongmail和PowerMTA。被警告 - 他們有很高的價格標籤,但我幾乎可以保證你將花費更多的時間來構建自己的解決方案。

在PHP中編寫屬於自己的一個領域是節流/焦油點。郵件服務器會在您發送幾條消息以減慢速度並阻止您發送垃圾郵件後,開始在睡眠中添加(30)。

通常,這些商業批量發件人運行SMTP協議進行排隊。您將繼續使用Zend_Mail,但是會將其硬編碼以連接到您的服務器。它會盡可能快地發送郵件,然後使用自己的引擎將郵件發送到目的地。

在100K的列表中,您將不得不採用電子郵件最佳做法。至少,你需要:

  • SPF記錄,可能DKIM以及超過
  • 多個IP地址,以段交通 - 有3個IP的,一個你信任的質量地址,一個用於中等風險的IP地址另一個用於高風險IP地址。這種設計有助於最大限度地降低將郵件發送給最佳客戶的風險。發送IP
  • 正確的反向DNS地址
  • 使用反饋來自AOL,Hotmail的,雅虎循環和其他處理垃圾郵件投訴
  • 退訂和彈跳管理 - 確保你修剪這些地址
  • 有打開/點擊跟蹤也很重要 - 如果您是A列表上的客戶不打開您的電子郵件,則需要將其降級到B列表等等。這很重要,因爲ISP會將不活動的帳戶變成蜜罐。 Hotmail以此而聞名。

最後,如果你真的想發送電子郵件,你會需要一些其他工具,如返回路徑。

2

我在php中實現了一個批量郵件程序,其中每個郵件都是爲個人定製的。這並不難,也不需要太長時間。我用swiftmailer和cron。 Zend Mail也可以。我從PEAR郵件隊列開始,但排隊郵件太慢了。

排隊電子郵件的過程中去,像這樣:

  1. 創建電子郵件模板,並添加佔位符(或使用模板引擎)的,獨特的內容將被取代的區域。
  2. 在一個循環中,將佔位符替換爲任何唯一內容,將生成的電子郵件內容,主題,地址,批次ID以及可選的優先級值插入到數據庫表中。

我用cron作業發出批量的電子郵件。 cron時間間隔和每批發送的電子郵件的數量非常重要,因爲我在一臺有限制的共享主機上。由cron作業調用的腳本只能由cron訪問。該腳本從批處理標識和可選的優先級排序的表中讀取x個電子郵件。如果電子郵件發送成功,它將從數據庫隊列中刪除。如果電子郵件無法發送,它將保留在隊列中,並且該記錄的計數器增加。如果一個計數器超過設定的數字,那麼該電子郵件已從隊列中刪除。

0

我開發了一個新聞通訊管理系統Swiftmailer,它很容易實現。它支持SMTP,加密,附件,批量發送,...

3

使用Zend_Queue將電子郵件放入隊列中進行異步後臺處理。您將需要一個cron作業來在後臺處理隊列。

protected function _enqueueEmail(WikiEmailArticle $email) 
{ 
    static $intialized = false; 

    if (!$initialized) { 

     $this->_initializeMailQueue("wikiappwork_queue"); 
     $initialized = true; 
    } 

    $this->_mailQueue->send(serialize($email)); 
} 
protected function _initializeMailQueue() 
{ 
    /* See: 1.) http://framework.zend.com/manual/en/zend.queue.adapters.html and 
    *  2.) Zend/Queue/Adapter/Db/mysql.sql. 
    */ 

$ini = Zend_Controller_Front::getInstance()->getParam('bootstrap') 
              ->getOptions(); 

    $queueAdapterOptions = array('driverOptions' => array(
    'host' => $ini['resources']['multidb']['zqueue']['host'], 
    'username' => $ini['resources']['multidb']['zqueue']['username'], 
    'password' => $ini['resources']['multidb']['zqueue']['password'], 
    'dbname' => $ini['resources']['multidb']['zqueue']['dbname'], 
    'type' => $ini['resources']['multidb']['zqueue']['adapter']), 
    'name' => $ini['resources']['multidb']['zqueue']['queueName']); 

    $this->_mailQueue = new Zend_Queue('Db', $queueAdapterOptions); 

} 

那麼對於cron作業,像

<?php 
use \Wiki\Email\WikiEmailArticle; 

// Change this define to correspond to the location of the wikiapp.work/libary 
define('APPLICATION_PATH', '/home/kurt/public_html/wikiapp.work/application'); 

set_include_path(implode(PATH_SEPARATOR, array(
    APPLICATION_PATH . '/../library', 
    get_include_path(), 
))); 

// autoloader (uses closure) for loading both WikiXXX classes and Zend_ classes. 
spl_autoload_register(function ($className) { 

    // Zend classes need underscore converted to PATH_SEPARATOR 
    if (strpos($className, 'Zend_') === 0) { 

     $className = str_replace('_', '/', $className); 
    } 

    $file = str_replace('\\', '/', $className . '.php'); 

    // search include path for the file. 
    $include_dirs = explode(PATH_SEPARATOR, get_include_path()); 

    foreach($include_dirs as $dir) { 

    $full_file = $dir . '/'. $file; 

    if (file_exists($full_file)) { 

     require_once $full_file; 
     return true; 
    } 
    } 

    return false; 
}); 

// Load and parese ini file, grabing sections we need. 
$ini = new Zend_Config_Ini(APPLICATION_PATH . 
          '/configs/application.ini', 'production'); 

$queue_config = $ini->resources->multidb->zqueue; 

$smtp_config = $ini->email->smtp; 

$queueAdapterOptions = array('driverOptions' => array(
             'host'  => $queue_config->host, 
        'username' => $queue_config->username, 
        'password' => $queue_config->password, 
        'dbname' => $queue_config->dbname, 
        'type'  => $queue_config->adapter), 
       'name' => $queue_config->queuename); 

$queue = new Zend_Queue('Db', $queueAdapterOptions); 


$smtp = new Zend_Mail_Transport_Smtp($smtp_config->server, array(
       'auth'  => $smtp_config->auth, 
     'username' => $smtp_config->username, 
     'password' => $smtp_config->password, 
     'port'  => $smtp_config->port, 
     'ssl'  => $smtp_config->ssl 
     )); 

Zend_Mail::setDefaultTransport($smtp); 

$messages = $queue->receive(10); 

foreach($messages as $message) { 

     // new WikiEmailArticle.  
    $email = unserialize($message->body); 

     try { 

      $email->send(); 

     } catch(Zend_Mail_Exception $e) { 

       // Log the error? 
       $msg = $e->getMessage(); 
       $str = $e->__toString(); 
       $trace = preg_replace('/(\d\d?\.)/', '\1\r', $str); 
     } // end try 

$queue->deleteMessage($message); 

} // end foreach 
相關問題