使 PHP 的 mail() 异步

Making PHP's mail() asynchronous

我有 PHP 的 mail() 使用 ssmtp,它没有 queue/spool,并且与 AWS SES 同步。

我听说我可以使用 SwiftMail 来提供线轴,但我无法像目前使用 mail() 那样想出一个简单的方法来使用它。

我想要用最少的代码来提供异步邮件。邮件发不成功我不在乎,但是有日志就好了

有什么简单的提示或技巧吗?缺少 运行 一个完整的邮件服务器?我在想 sendmail 包装器可能是答案,但我无法解决 nohup.

你有很多方法可以做到这一点,但处理线程不一定是正确的选择。

  • register_shutdown_function:响应发送后调用关闭函数。它不是真正的异步,但至少不会减慢您的请求。关于实现,请参见示例。
  • Swift pool: 使用symfony,可以轻松使用spool.
  • Queue:在队列系统中注册要发送的邮件(可以用 RabbitMQ、MySQL、redis 或任何东西完成),然后 运行 消耗队列的 cron。可以用像 MySQL table 这样简单的东西来完成,其中字段如 fromtomessagesent(布尔值设置为 true 当您发送电子邮件时)。

示例register_shutdown_function

<?php
class MailSpool
{
  public static $mails = [];

  public static function addMail($subject, $to, $message)
  {
    self::$mails[] = [ 'subject' => $subject, 'to' => $to, 'message' => $message ];
  }

  public static function send() 
  {
    foreach(self::$mails as $mail) {
      mail($mail['to'], $mail['subject'], $mail['message']);
    }
  }
}

//In your script you can call anywhere
MailSpool::addMail('Hello', 'contact@example.com', 'Hello from the spool');


register_shutdown_function('MailSpool::send');

exit(); // You need to call this to send the response immediately

将 AWS SES 与 PHPMailer 结合使用。

这种方式非常快(每秒数百条消息),并且不需要太多代码。

$mail = new PHPMailer;
$mail->isSMTP();                                      // Set mailer to use SMTP
$mail->Host = 'ssl://email-smtp.us-west-2.amazonaws.com';  // Specify main and backup SMTP servers

$mail->SMTPAuth = true;                               // Enable SMTP authentication

$mail->Username = 'blah';                 // SMTP username
$mail->Password = 'blahblah';                           // SMTP password


$mail->SMTPSecure = 'tls';                            // Enable TLS encryption, `ssl` also accepted
$mail->Port = 443; 

不确定我是否正确解释了您的问题,但我希望这对您有所帮助。

Pthreads 是你的朋友:)
这是我在生产应用程序中制作的示例

class AsynchMail extends Thread{
    private $_mail_from;
    private $_mail_to;
    private $_subject;

    public function __construct($subject, $mail_to, ...) {
        $this->_subject = $subject;
        $this->_mail_to = $mail_to;
        // ... 
    }
    // ...
    // you must redefine run() method, and to execute it we must call start() method
    public function run() {
        // here put your mail() function
        mail($this->_mail_to, ...);
    }
}

测试脚本示例

$mail_to_list = array('Shigeru.Miyamoto@nintendo.com', 'Eikichi.Kawasaki@neogeo.com',...);
foreach($mail_to_list as $mail_to) {
    $asynchMail = new AsynchMail($mail_to);
    $asynchMail->start();
}

如果您需要进一步的帮助来安装和使用 PHP
中的线程,请告诉我 对于日志系统,我强烈建议您使用 Log4PHP :功能强大且易于使用和配置
对于发送邮件,我也强烈建议您使用PHPMailer

一个简单的方法是调用异步处理邮件的代码。

例如,如果您有一个名为 email.php 的文件,其代码如下:

// Example array with e-mailaddresses
$emailaddresses = ['example1@test.com', 'example2@example.com', 'example1@example.com'];

// Call your mail function
mailer::sendMail($emailaddresses);

然后您可以在像

这样的正常请求中异步调用它
exec('nice -n 20 php email.php > /dev/null & echo $!');

请求将完成,无需等待 email.php 完成发送电子邮件。也可以在处理电子邮件的文件中添加日志记录。

变量可以传递到调用文件名和 > /dev/null 之间的 exec 中,例如

exec('nice -n 20 php email.php '.$var1.' '.$var2.' > /dev/null & echo $!');

使用 escapeshellarg(). In the called file these variables can be used with $argv

确保这些变量是安全的

最好的选择是使用堆叠或假脱机模式。它相当简单,可以分两步描述。

  • 将您的电子邮件存储在 table 中,并在您的当前线程上标记已发送。
  • 使用 cron 或 ajax 重复调用邮件处理 php 文件,该文件将从您的数据库中获取前 10 或 20 封未发送的电子邮件,将它们标记为已发送并实际通过您的收藏夹发送它们邮寄方式。

我正在使用 beanstalkd 异步 php 执行。
它是一个简单的消息队列,非常轻量级且易于集成。

为 php 使用以下 php 包装器 https://github.com/pda/pheanstalk 您可以执行以下操作来实现电子邮件工作者:

use Beanstalk\Client;
$msg="dest_email##email_subject##from_email##email_body";

$beanstalk = new Client(); 
$beanstalk->connect();
$beanstalk->useTube('flux'); // Begin to use tube `'flux'`.
$beanstalk->put(
    23,  // Give the job a priority of 23.
    0,   // Do not wait to put job into the ready queue.
    60,  // Give the job 1 minute to run.
    $msg // job body
);
$beanstalk->disconnect();

然后这项工作将在放置在单独 php 文件中的代码中完成。
类似于:

use Beanstalk\Client;
$do=true;

try {
    $beanstalk = new Client();
    $beanstalk->connect();
    $beanstalk->watch('flux');

} catch (Exception $e ) {
    echo $e->getMessage();
    echo $e->getTraceAsString();
    $do = false;
}

while ($do) {
    $job = $beanstalk->reserve(); // Block until job is available.
    $emailParts = explode("##", $job['body'] );

    // Use your SendMail function here

    if ($i_am_ok) {
        $beanstalk->delete($job['id']);
    } else {
        $beanstalk->bury($job['id'], 20);
    }
}
$beanstalk->disconnect();

您可以 运行 将此 php 文件作为一个独立的 php 进程。假设您将其保存为 sender.php,在 Unix 中它将是 运行 为:

php /path/to/sender/sender.php & && disown

此命令将 运行 文件,并且还允许您在不停止进程的情况下关闭控制台或注销当前用户。
还要确保您的网络服务器使用与 php 命令行解释器相同的 php.ini 文件。 (可能会用你喜欢的link来解决php.ini

希望对您有所帮助。

php-fpm

您必须 运行 php-fpm 才能使用 fastcgi_finish_request

echo "I get output instantly";
fastcgi_finish_request(); // Close and flush the connection.
sleep(10); // For illustrative purposes. Delete me.
mail("test@example.org", "lol", "Hi");

在完成对用户的请求后,很容易排队处理任意代码:

$post_processing = [];
/* your code */
$email = "test@example.org";
$subject = "lol";
$message = "Hi";

$post_processing[] = function() use ($email, $subject, $message) {
  mail($email, $subject, $message);
};

echo "Stuff is going to happen.";

/* end */

fastcgi_finish_request();

foreach($post_processing as $function) {
  $function();
}

潮人后台

立即使 curl 超时并让新请求处理它。我在共享主机上这样做之前很酷。 (一点都不酷)

if(!empty($_POST)) {
  sleep(10);
  mail($_POST['email'], $_POST['subject'], $_POST['message']);
  exit(); // Stop so we don't self DDOS.
}

$ch = curl_init("http://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);

curl_setopt($ch, CURLOPT_TIMEOUT, 1);
curl_setopt($ch, CURLOPT_NOSIGNAL, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, [
  'email' => 'noreply@example.org',
  'subject' => 'foo',
  'message' => 'bar'
]);

curl_exec($ch);
curl_close($ch);

echo "Expect an email in 10 seconds.";

欢迎使用异步PHP https://github.com/shuchkin/react-smtp-client

$loop = \React\EventLoop\Factory::create();

$smtp = new \Shuchkin\ReactSMTP\Client( $loop, 'tls://smtp.google.com:465', 'username@gmail.com','password' );

$smtp->send('username@gmail.com', 'sergey.shuchkin@gmail.com', 'Test ReactPHP mailer', 'Hello, Sergey!')->then(
    function() {
        echo 'Message sent via Google SMTP'.PHP_EOL;
    },
    function ( \Exception $ex ) {
        echo 'SMTP error '.$ex->getCode().' '.$ex->getMessage().PHP_EOL;
    }
);

$loop->run();