web-dev-qa-db-fra.com

Rendre asynchrone mail () de PHP

J'ai mail() de PHP en utilisant ssmtp qui n'a pas de file d'attente/spool et qui est synchrone avec AWS SES.

J'ai entendu dire que je pourrais utiliser SwiftMail pour fournir un spool, mais je ne pouvais pas élaborer une recette simple pour l'utiliser comme je le fais actuellement avec mail().

Je veux que le moins de code possible fournisse un courrier asynchrone. Peu m'importe si l'e-mail ne parvient pas à envoyer, mais ce serait bien d'avoir un journal.

Des trucs ou astuces simples? À court d'exécuter un serveur de messagerie complet? Je pensais qu'un wrapper sendmail pourrait être la solution, mais je ne pouvais pas travailler Nohup.

14
hendry

php-fpm

Vous devez exécuter php-fpm pour fastcgi_finish_request pour être disponible.

echo "I get output instantly";
fastcgi_finish_request(); // Close and flush the connection.
sleep(10); // For illustrative purposes. Delete me.
mail("[email protected]", "lol", "Hi");

Il est assez facile de mettre en file d'attente n'importe quel code arbitraire à traiter après avoir terminé la requête à l'utilisateur:

$post_processing = [];
/* your code */
$email = "[email protected]";
$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();
}

Travailleur de fond hipster

Arrêtez instantanément une boucle et laissez la nouvelle demande y répondre. Je faisais cela sur des hôtes partagés avant que ce soit cool. (ce n'est jamais cool)

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' => '[email protected]',
  'subject' => 'foo',
  'message' => 'bar'
]);

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

echo "Expect an email in 10 seconds.";
9
Kit Sunde

Vous avez beaucoup de façons de faire cela, mais le traitement du fil n'est pas nécessairement le bon choix. 

  • register_shutdown_function: la fonction d'arrêt est appelée après l'envoi de la réponse. Ce n'est pas vraiment asynchrone, mais au moins cela ne ralentira pas votre demande. En ce qui concerne l'implémentation, voir l'exemple.
  • Swift Pool: en utilisant symfony, vous pouvez facilement utiliser le spool.
  • Queue: enregistre les mails à envoyer dans un système de file d'attente (peut être fait avec RabbitMQ, MySQL, redis ou autre), puis lance un cron qui consomme la file d'attente. Pourrait être fait avec quelque chose d'aussi simple qu'une table MySQL avec des champs tels que from, to, message, sent (booléen défini sur true lorsque vous avez envoyé l'email). 

Exemple avec 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', '[email protected]', 'Hello from the spool');


register_shutdown_function('MailSpool::send');

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

Utilisez AWS SES avec PHPMailer

Cette méthode est très rapide (des centaines de messages par seconde) et ne nécessite pas beaucoup de code.

$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; 

Je ne sais pas si j'ai bien interprété votre question, mais j'espère que cela vous aidera.

5
Sameer Srivastava

Pthreads est ton ami :)
Ceci est un exemple de la manière dont j’ai réalisé mon application de production

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, ...);
    }
}

TEST SCRIPT EXEMPLE

$mail_to_list = array('[email protected]', '[email protected]',...);
foreach($mail_to_list as $mail_to) {
    $asynchMail = new AsynchMail($mail_to);
    $asynchMail->start();
}

Faites-moi savoir si vous avez besoin d'aide supplémentaire pour l'installation et l'utilisation de threads en PHP
Pour la journalisation, je vous conseille vivement d’utiliser Log4PHP : puissant, facile à utiliser et à configurer
Pour l’envoi de mails, je vous conseille également vivement d’utiliser PHPMailer

2
Halayem Anis

J'utilise une exécution PHP asynchrone en utilisant beanstalkd .
Il s’agit d’une simple file de messages, très légère et facile à intégrer.

En utilisant le wrapper php suivant pour php https://github.com/pda/pheanstalk , Vous pouvez procéder de la manière suivante pour implémenter un gestionnaire de messagerie:

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();

Ensuite, le travail serait effectué dans un code placé dans un fichier php séparé.
Quelque chose comme:

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();

Vous pouvez exécuter ce fichier php séparément, en tant que processus php indépendant. Disons que vous l'enregistrez sous sender.php, il sera exécuté sous Unix comme:

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

Cette commande exécute le fichier et vous permet également de fermer la console ou de déconnecter l’utilisateur actuel sans arrêter le processus.
Assurez-vous également que votre serveur Web utilise le même fichier php.ini que votre interpréteur de ligne de commande php. (Peut-être résolu en utilisant un lien vers votre php.ini préféré)

J'espère que ça aide.

1
Evhz

Bienvenue sur async PHP https://github.com/shuchkin/react-smtp-client

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

$smtp = new \Shuchkin\ReactSMTP\Client( $loop, 'tls://smtp.google.com:465', '[email protected]','password' );

$smtp->send('[email protected]', '[email protected]', '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();
0
Sergey Shuchkin

Votre meilleur pari est avec un motif d'empilement ou de mise en file d'attente. C'est assez simple et peut être décrit en 2 étapes.

  • Stockez vos emails dans un tableau avec un drapeau envoyé sur votre sujet actuel.
  • Utilisez cron ou ajax pour appeler de manière répétée un fichier php de traitement de courrier qui obtiendra les 10 ou 20 premiers e-mails non envoyés de votre base de données, signalez-les comme envoyés et envoyez-les via votre méthode de publipostage préférée.
0
catbadger

Une méthode simple consiste à appeler le code qui traite vos messages de manière asynchrone.

Par exemple, si vous avez un fichier nommé email.php avec le code suivant:

// Example array with e-mailaddresses
$emailaddresses = ['[email protected]', '[email protected]', '[email protected]'];

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

Vous pouvez ensuite appeler cela de manière asynchrone dans une requête normale telle que

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

Et la demande se terminera sans attendre que email.php ait fini d'envoyer les courriels. La journalisation pourrait également être ajoutée au fichier contenant les courriers électroniques.

Les variables peuvent être passées dans l'exécutable entre le nom de fichier appelé et > /dev/null comme

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

Assurez-vous que ces variables sont en sécurité avec escapeshellarg () . Dans le fichier appelé, ces variables peuvent être utilisées avec $ argv

0
PWD