cronjob是使用SMTP通过PHPMailer发送大量通知电子邮件的正确方式吗?

cronjob是使用SMTP通过PHPMailer发送大量通知电子邮件的正确方式吗?,php,mysql,email,cron,phpmailer,Php,Mysql,Email,Cron,Phpmailer,我有一个web应用程序(PHP/MYSQL),每次有人通过我的应用程序中的表单提交我数据库中的条目时,它都会向与该条目相关的所有选择接受通知的人发送通知电子邮件 例如,有一个足球选秀池,里面有15个人。当第16个人提交他们的条目时,0-15人中的任何人都会收到一个新条目的通知。可能是1个,可能是全部15个,可能是其中的一半 但我的网站可能有100个足球池,有些有5个人,有些有100个人 因此,我设置了一个单独的表,记录每个条目的基本信息,并将“sent”列设置为“0” 然后,我让下面的cronj

我有一个web应用程序(PHP/MYSQL),每次有人通过我的应用程序中的表单提交我数据库中的条目时,它都会向与该条目相关的所有选择接受通知的人发送通知电子邮件

例如,有一个足球选秀池,里面有15个人。当第16个人提交他们的条目时,0-15人中的任何人都会收到一个新条目的通知。可能是1个,可能是全部15个,可能是其中的一半

但我的网站可能有100个足球池,有些有5个人,有些有100个人

因此,我设置了一个单独的表,记录每个条目的基本信息,并将“sent”列设置为“0”

然后,我让下面的cronjob每分钟运行一次,它查找所有具有“0”的条目(这意味着与该条目相关联的人员尚未收到通知),循环浏览并向每个相关人员发送唯一的电子邮件。我发送独特的电子邮件,因为每封电子邮件都有一个退订链接和特定于此人的信息。发送电子邮件后,它会将“已发送”列中的所有条目更新为“1”,因此将忽略该条目

我通过Amazon SES使用PHPMailer SMTP发送电子邮件

<?php
require_once("includes/session.php");
require_once("includes/connection.php");
require_once("includes/functions.php");
require 'phpmailer/PHPMailerAutoload.php';

//find all new entries with sent = 0, loop thru and send emails
$stmt = $pdo->prepare("SELECT * FROM cron_email_notify WHERE sent = 0");
$stmt->execute();
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
    //bunch of variables set here (which i'll leave out for brevity's sake) to be used in the emails (like firstname, etc)

        //Next, send a unique email to all people who have chosen to be notified
        $stmt = $pdo->prepare("SELECT * FROM entries WHERE poolid = ? AND notify = 'yes'");
        $stmt->execute([$poolid]);
        foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
            //get emails into array
            $notifyemailsarray[$row['email']]=$row;
        }

        /// Send notification email to all people who wish to be notified, if any
        $mail = new PHPMailer;
        //$mail->SMTPDebug = 3;                               // Enable verbose debug output
        $mail->isSMTP();                                      // Set mailer to use SMTP
        $mail->SMTPKeepAlive = true;  //Helps with speed for multiple emails
        $mail->Host = 'tls://email-smtp.us-east-1.amazonaws.com';  // Specify main and backup SMTP servers
        $mail->SMTPAuth = true;                               // Enable SMTP authentication
        // and so on with other phpmailer info which I cut out for brevity's sake

        if (!empty($notifyemailsarray)) {
            foreach($notifyemailsarray as $email => $details)   {
                // Assemble the fullname here
               $fullname = $details['firstname'] . ' ' . $details['lastname'];
               $mail->addAddress($details['email'], $fullname);
               $mail->Body    =  "(leaving out for brevity's sake)";
               $mail->AltBody = "(leaving out for brevity's sake)"; 
                if(!$mail->send()) {
                    echo 'Message could not be sent.';
                    echo 'Mailer Error: ' . $mail->ErrorInfo;
                } else {
                    //email sent
                }
                // Clear all addresses for next loop
                $mail->clearAddresses();
            }
        } else {
            //no people  are set to be notified 
        }
        $mail->SmtpClose();  
    //update cron_email_notify table's sent field to 1, so we know not to send again
    $stmt = $pdo->prepare("UPDATE cron_email_notify SET sent = 1 WHERE poolid = ? and sent = 0");
    $stmt->execute([$poolid]);
}
?>

我在一家公司工作,该公司为美国超过一半的公立学校发送通知。我们每天发送1000万封电子邮件

您的代码是单线程的,因此它将一个接一个地连续发送电子邮件,这可能需要一段时间。您曾问,如果时间超过cron作业之间的间隔,会发生什么情况?也就是说,如果cron作业每分钟运行一次,但在某个点上,它会在上一个脚本仍在运行时启动脚本。然后,对脚本的两次调用都将处理同一批电子邮件,您的用户将开始接收重复的电子邮件

因此,运行守护进程可能更好。这只是意味着,与cron每分钟启动PHP脚本不同,您可以将PHP脚本编码为运行
while(true)
循环,并且永远不会退出。在循环结束时,使脚本
sleep()
持续60秒。然后它进入循环的顶部,检查数据库以查看是否有一批电子邮件要运行,并处理该批邮件。然后它返回睡眠状态,时间为60秒减去处理批处理所需的时间(但不少于零秒)

这样一来,即使一批电子邮件需要75秒的时间,如果出现激增,它也不会同时运行两个应用程序。您的守护进程的睡眠计算将使它至少睡眠0秒,然后它将立即开始下一批。希望这种激增是暂时的,应用程序可以在某个时候赶上。否则,你的应用程序将越来越落后于计划

你的应用程序应该在日志文件中写下它发送了多少封电子邮件,以及在批处理之间休眠了多长时间。您应该监视此文件,这样如果它无法满足需求,您就会知道。一旦你变得非常专业,你将把这些数据放到Grafana仪表板或类似的东西中

如果您的流量增长到单线程应用始终无法在60秒内发送一批电子邮件的程度,那么您将不得不并行运行多个电子邮件发件人应用。PHP在多线程编程方面很糟糕,所以您可能希望使用Java或Go

试图让多线程轮询数据库是一个坏主意,因为您会得到竞争条件和锁争用。我们公司所做的就是对数据库进行一次线程轮询。当它找到一批要发送的电子邮件时,它将它们推送到消息队列中(我们使用ActiveMQ,但还有其他一些)。消息队列使多个线程可以轻松地从队列中提取项目。然后,多个并发电子邮件发送应用程序线程可以轻松地并行处理成批电子邮件


注意:您是否意识到,即使部分或全部电子邮件未能发送,您的代码也会将池标记为
sent=1

只是出于好奇。为什么要使用cronjob?还不如简单地在用户提交时发出邮件?至少这样,您的服务器不会“免费”运行cronjobs,您也不会遇到堆积cronjob进程的问题。如果If语句在
ELSE
中没有任何内容,那么就不需要对
ELSE
进行编码,也不需要使用cronjob,只需每24小时运行一次。想象一下,有100多个用户都在发帖。您的服务器将很快被标记为垃圾邮件服务器。我不希望用户在每次更新时每天都有50多封邮件。每天只通知我一次。@icecub我最初是在用户提交时发送电子邮件的,但你会点击提交,如果发送的数量很大,它会在作业完成时旋转,不会给他们发送成功消息,说明他们的条目已提交约5秒。在研究中,人们建议在后台使用cronjob,以免伤害用户体验并让他们等待。这就是为什么我这样做的原因,但现在我也在考虑这个方法的问题。你的代码看起来很合理-与PHPMailer提供的邮件列表示例非常相似。绝对值得避免在提交页面时谈论SMTP。只是太唠叨太慢了。最好将其推入cron作业或守护进程。制作一个cron作业来检查前一个实例是否仍在运行并不难。谢谢Bill,你给了我很多我不知道的信息,我将进一步研究。我没有意识到关于sent=1的事情,如果我继续这样做,我需要移动更新查询,再次感谢。我的主要想法是,我认为它在一段时间内会很好地工作,但如果流量开始增加,我想做好准备,避免发生灾难