【问题标题】:Sending emails in PHP and using Google Apps mail as SMTP is too slow在 PHP 中发送电子邮件并将 Google Apps 邮件用作 SMTP 太慢
【发布时间】:2013-01-05 23:19:26
【问题描述】:

目前我一直在使用 PHP 和 Zend 框架通过 SMTP 和 GMail 的 SMTP 服务器发送电子邮件。那是因为我有一个 Google Apps 域设置来处理我的所有电子邮件,所以看起来我的电子邮件来自 mailer@mycompany.com。

现在,当用户注册或执行需要我的 Web 服务器向他们发送电子邮件的操作时,PHP 端可能需要 5 秒左右来启动 SMTP 连接并将电子邮件发送给他们。这个过程是这样的:

  1. 用户填写注册表
  2. 用户提交表单
  3. PHP 脚本将信息保存到数据库并向他们发送电子邮件(5-10 秒)
  4. 用户被重定向到另一个页面

现在,当他们刚刚提交表单时,我的网络服务器不会有很大的延迟,而用户在那里坐了 10 秒钟,看起来好像正在加载。基本上,发送电子邮件过程是单线程的,因此服务器在完成发送电子邮件之前不能做任何其他事情,这在 5-10 秒之间,如果电子邮件地址有错误,有时需要 20 秒,所以这太长了。

人们通常如何解决此类问题?

最初我尝试了几件事:

  1. 使用另一个库,如 SwiftMailer。还是一样的问题。我认为是 SMTP/TLS 握手需要额外的时间。

  2. 将电子邮件数据存储在数据库中,然后使用 fsockopen 向另一个页面发起异步请求,该页面将从数据库中获取电子邮件数据并发送电子邮件。同时,我的 PHP 脚本可以同步继续,用户会在表单提交后立即被重定向到下一页。问题是无法确认电子邮件是否已发送,或者是否失败。我想我可以在数据库表上设置一个标志,但我想向用户提供他们的电子邮件已发送的即时反馈。这意味着在下一页上,在 10 秒后向另一个页面发出 Ajax 请求,该页面将检查数据库表上的标志以查看它是否已发送,然后在页面上向用户显示响应。如果他们输入错误的电子邮件并且无法发送电子邮件,这将不起作用,因为 GMail 的响应有时需要 20 秒才能出现错误情况,然后不会设置数据库标志。

  3. 我想出的另一个选择是将电子邮件数据放在 $_SESSION 数组中,因此在提交页面并且用户位于重定向页面后,它会将 ajax 请求发送到另一个页面会话数据并发送电子邮件,然后将响应发送回用户所在的页面并告诉他们电子邮件已发送。这很好用,但我觉得这可能不是最好的方法。

所以我想知道最好的做法是什么? GMail 对于这种事情是否太慢了,因为它必须进行 TLS 握手?我是否应该将所有电子邮件排队到数据库中并每隔几分钟运行一次 CRON 作业以将它们发送出去,然后在发送后将它们从数据库中删除?问题是没有立即向用户提供他们的电子邮件发送正确的反馈。还是我不需要为此烦恼?他们要么收到电子邮件,要么没有。

【问题讨论】:

    标签: php zend-framework email gmail google-apps


    【解决方案1】:

    您是否考虑过使用主机的电子邮件 smtp 服务器并手动设置发件人地址?这通常对我有用。如果这不是一个选项,我的第一反应是选项 3 - 这类似于 Google 邮件在他们的 web ui 中选择延迟发送选项时所做的。

    【讨论】:

    • 使用我主机的电子邮件 SMTP 服务器是什么意思?我使用的是我自己的 VPS,所以我必须自己设置所有这些,对吗?
    • @Dagon,您有在 Linux 上进行设置的指南吗?上次看的时候,我迷路了。是这样吗...我的 Web 服务器将发送电子邮件(可能使用 PHP 的 send() 方法)以由我的邮件服务器在同一台 Web 服务器机器上处理。然后,该邮件服务器使用 Google Apps SMTP 将电子邮件发送给用户,还是使用我必须设置的其他电子邮件地址?
    • 通常情况下,如果 sendmail 配置正确,那么您可以调用 PHP 中的 mail() 方法,您可以传递的参数之一是“From”地址。如果您担心您的电子邮件被路由到垃圾邮件,您可以探索部署 SPF 记录(它们非常简单且非常快速)。如果您以前没有使用过邮件方法,可以在以下位置获得快速参考:w3schools.com/php/php_mail.asp。我还有一个简单的邮件对象,我在github.com/kaiesh/PHPLightweightFramework/blob/master/… 用完了(工作,但正在进行中)
    • @zuallaz,抱歉没有澄清您的第一个问题 - 如果您使用 PHP 邮件功能而不指定备用 SMTP 提供程序,那么您的服务器会直接与收件人 MX 协商。所以谷歌被排除在外 - 但如果 FROM 地址设置正确,那么用户会认为这是来自您的邮箱。我从来没有真正遇到过垃圾邮件陷阱问题,因为邮件来自托管域的服务器 - 但您可以格外小心,并使用 SPF 记录来防止攻击性垃圾邮件过滤器。
    • @zuallauz 根据我的经验(我认为这取决于您的 linux 发行版),sendmail 已被直接配置为支持向其他 MX 服务器发送邮件。当您想要将其配置为接收邮件时,您需要跳过一些环节。在 ubuntu 中,您可以简单地运行“apt-get install sendmail”,它应该会自动配置发送。你能在你的服务器上运行这个 PHP 脚本来测试它吗? ,“测试主题”,“测试正文”,“来自:no-reply@.com\r\n”); ?> 接收可能需要几秒钟。
    【解决方案2】:

    编写一个应用程序脚本并将其用作网络应用程序来处理电子邮件怎么样?您可以使用带有参数的 HTTP Get 让应用程序脚本使用基本邮件服务发送电子邮件。 MailApp.sendEmail()

    我是一个自学应用脚本的人,所以我只是在这里推测,但理论上它应该可以工作。我认为它看起来像这样。

    您应该能够通过 webApp url 传递 e.parameter 末尾的密钥。

    function doGet(e){
    var userEmail = e.parameter.email;
    
    var body = 'message here';//could even pass params for the body also.
    
    MailApp.sendEmail(userEmail, 'Subject', 'body', {noReply: true});
    }
    

    我们所有的东西都是建立在应用程序脚本上的,我有几十个电子邮件通知发出,并且在应用程序脚本中它是即时的。

    希望这会有所帮助。

    【讨论】:

      【解决方案3】:

      实际上我使用相同的组件来发送电子邮件。但我在后台使用Zend_Queue 发送电子邮件,在这种情况下,访问者不必等待。这就是我完成这项任务的方式(Zend_QueueBootstap.php 中初始化):

      1.创造工作

      class My_Job_SendEmail extends My_Job
      {
          /**
           * Message subject:
           * 
           * @var string|null 
           */
          protected $_subject = null;
      
          /**
           * Message body:
           * @var string|null 
           */
          protected $_message = null;
      
          /**
           * Message To:
           * @var string|null 
           */
          protected $_to = null;
      
          public function __construct(Zend_Queue $queue, array $options = null)
          {
              if (is_array($options)) {
                  $this->setOptions($options);
              }
      
              parent::__construct($queue);
          }
      
          public function job()
          {
              $mail = new Zend_Mail();
              $mail->addTo($this->_to);
              $mail->setSubject($this->_subject);
              $mail->setBody($this->_message);
              return $mail->send();
          }
      }
      

      2.My_Job的来源

      3.将作业添加到队列中(可能在您的服务中的某处)

      $backgroundJob = new My_Job_SendEmail(Zend_Registry::get('queue'), array(
          'to'      => $to,
          'bcc'     => $bcc,
          'subject' => $subject,
          'message' => $message,
      ));
      
      $backgroundJob->execute();
      

      4.查看this答案,后台脚本示例。

      5.将后台脚本添加到 cronjob

      */1 * * * * php /path/to/project/background/emailQueue.php

      【讨论】:

        【解决方案4】:

        我遇到了这个确切的问题,我实际上只是将它们排队在数据库中,并且我有一个每分钟运行一次的 cronjob 来发送在数据库中排队的电子邮件。

        顺便说一句,许多中型网站都会这样做,并告诉您应该在(5 分钟)内或很快收到一封电子邮件......等

        保存在 db 中的好处是,您可以添加一个额外的列来指示它是否已发送,如果未在下一个 cronjob 中发送,则重试。


        锁定 Cronjob 的更新:

        请注意这只是一个顺序逻辑,并注意步骤 1,2 和 4 是关于 Cron #1

        1-Cron #1 runs at 12:05, reads the locking table and finds it empty so it creates a db record in a pre-created table "locking" with 1 field "cronlock": holding the value 12345678910, which is the current time stamp.
        
        2-Cron #1 at 12:06 (Still sending emails, but it completed 6 emails so far) so it updates the record of locking again with the current time-stamp to extend the locking period.
        
        3-Cron #2 runs at 12:07, reads the locking table and find a value, compares this value with the current timestamp if the difference is less than 120 seconds, then it terminates itself.
        
        4-Cron #1 finishes sending some emails at 12:08, it deletes the locking record and quits.**
        
        5-Cron #3 runs at 12:09 and finds no locking record so it creates a new one and starts sending.... and so on.
        

        【讨论】:

        • 假设我遇到了访问我网站的繁忙时段,并且有 50 多封电子邮件在数据库中排队等待发送。发送每封电子邮件大约需要 5-10 秒。可以想象,下一个 cronjob 可能会再次运行并开始发送相同的电子邮件列表,因为另一个 cronjob 尚未完成。然后客户会收到 2 封非常糟糕的电子邮件。您将如何解决该并发问题?也许在它试图发送的表中的行上设置一个标志?因此,如果下一个 cronjob 出现,它会忽略该行并继续另一行?
        • 添加一个带时间戳的锁字段用于锁定,如果发送则更新,否则释放锁
        • 是的,很酷,应该可以,谢谢。您是否将已发送的电子邮件无限期地保存在数据库中?或者你会在某个时候清除它们?我想我可以在发送后清除它们,因为如果我登录 Google Apps 并查看是否真的需要,我发送邮件的地址仍然会有副本。
        • 看你可以用更简单的方式锁定,锁定 cron 本身。即当一个新的 cron 启动时,它会检查最后一个 cron 是否仍在锁定电子邮件,如果是,则退出,如果不是,则继续发送并根据时间戳进行锁定,这样如果出于任何原因,cron 脚本在解锁您将指定的记录或文件或字段之前在中间终止;它最多保持锁定状态,比如说 3 分钟,并确保每隔 X 封电子邮件更新一次此锁定,以确保在您仍在发送时锁定不会超时。
        猜你喜欢
        • 2011-09-09
        • 2013-12-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-11-24
        • 1970-01-01
        相关资源
        最近更新 更多