2

目前,我一直在使用 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 请求,该页面将检查数据库表上的标志以查看它是否已发送,然后在页面上向用户显示响应。如果他们输入错误的电子邮件并且无法发送电子邮件,这将不起作用,

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

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

4

4 回答 4

1

编写一个应用程序脚本并将其用作 web 应用程序来处理电子邮件怎么样?您可以使用带有参数的 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});
}

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

希望这可以帮助。

于 2013-01-06T00:39:07.003 回答
1

我遇到了这个确切的问题,我实际上只是将它们排队在数据库中,并且我有一个每分钟运行一次的 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.
于 2013-01-06T00:45:06.140 回答
1

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

于 2013-01-05T23:40:12.753 回答
1

实际上我使用相同的组件来发送电子邮件。但是我在后台发送电子邮件,Zend_Queue在这种情况下,访问者不必等待。这就是我完成这项任务的方式(Zend_Queue在中初始化Bootstap.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.查看这个答案,以获取后台脚本的示例。

5.给cronjob添加后台脚本

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

于 2013-01-06T22:29:13.157 回答