Pull to refresh

Отложенная отправка писем через SMTP на PHP и REDIS

Все мы сталкивались с задачей отправки писем на php. Есть конечно функция mail(), но:
  • Как выяснилось, не все почтовые сервисы принимают письма, сгенерированные php из данной функции
  • Скорость работы оставляет желать лучшего
  • Невозможность отложенной отправки

Попробуем написать свою систему отложенной отправки писем, без использования специализированного ПО, наподобие, Gearman.

Что понадобится?
  • Redis (лично я юзаю версию redis-2.2.8)
  • Либа для работы с Redis (например php_redis)
  • php
  • Доступ к smtp хостинг — провайдера

Итак, начнём.

Ставим Redis:

  cd  /install/install_redis/
	 
  wget http://redis.googlecode.com/files/redis-2.2.8.tar.gz

  tar xvzf redis-2.2.8.tar.gz

  cd redis-2.2.8

  make

  cd src
 


В конфигурационном файле (redis.conf) достаточно прописать:
daemonize yes
pidfile /var/run/redis.pid
port 6179


Создаём скрипт автозагрузки redis: nano /etc/init.d/redis
Прописываем в него:
 #!/sbin/runscript

 depend() {
        need net
 }


 svc_start() {
        ebegin "Starting redis"

               start-stop-daemon --start --quiet --pidfile /var/run/redis.pid --exec /install/install_redis/redis-2.2.8/src/redis-server /install/install_redis/redis-2.2.8/redis.conf

        eend $?
 }

 svc_stop() {
        ebegin "Stopping redis"

                start-stop-daemon --stop --quiet --pidfile /var/run/redis.pid --exec /install/install_redis/redis-2.2.8/src/redis-server

        eend $?
 }

 svc_restart() {
        ebegin "Restarting redis"

                start-stop-daemon --stop --quiet --pidfile /var/run/redis.pid --exec /install/install_redis/redis-2.2.8/src/redis-server

                start-stop-daemon --start --quiet --pidfile /var/run/redis.pid --exec /install/install_redis/redis-2.2.8/src/redis-server /install/install_redis/redis-2.2.8/redis.conf

        eend $?
 }
 


Делаем файл исполняемым, прописываем в автозагрузку и запускаем:
 chmod +x /etc/init.d/redis
 rc-update add redis default
 /etc/init.d/redis start

Теперь работаем с php:

Напишем небольшой класс для добавления наших писем в очередь:
require('redis.php');  // Подключаем файл - библиотеку для работы с redis (например php_redis)

	class post_mess_user  
	 {
		 public
		 function post_email_user($array) 
		  {global $redis;
			 
			 if(!$redis)
			  {
				 $redis = new php_redis();
			  }
			  
			 if($redis->ERROR_CONNECT)
			  {
				  echo 'Ошибка соединения с Redis; тип: '.$redis->ERROR_CONNECT[0].', код ошибки: '.$redis->ERROR_CONNECT[1];
			  }
			 else
			  {  
				  $redis->list_rpush('mails',base64_encode(serialize($array)));
			  }
			 
		  }
	 }
 


Таким образом, добавить письмо в очередь можно, подключив данный класс (вместе с классом для redis):
   $user_mail = new post_mess_user ();

    $array=array();
    $array['from']='From';  
    $array['from_email']='support@support.ru';
    $array['to']='To';
    $array['tema']='Подтверждение регистрации';
    $array['mess']='Текст';
	
   $user_mail->post_email_user($array);   
 


За непосредственную отправку письма будет отвечать небольшой класс php:
class mail_smtp
 {
     function get_data($smtp_conn)
      {
          $data="";
          while($str = fgets($smtp_conn,515))
          {
             $data .= $str;
             if(substr($str,3,1) == " ") { break; }
          }
		
         return $data;
      } 
	    
     public 
     function post($from,$from_email,$to,$tema,$mess)
      {
	  $domain = mb_substr($from_email,mb_strpos($from_email,'@')+1);
		
          $header="Date: ".date("D, j M Y G:i:s")." +0700\r\n";
          $header.="From: =?windows-1251?Q?".str_replace("+","_",str_replace("%","=",urlencode(iconv('utf-8','windows-1251',$from))))."?= <$from_email>\r\n";
          $header.="X-Mailer: The Bat! (v3.99.3) Professional\r\n";
          $header.="Reply-To: =?windows-1251?Q?".str_replace("+","_",str_replace("%","=",urlencode(iconv('utf-8','windows-1251',$from_email))))."?= <$from_email>\r\n";
          $header.="X-Priority: 3 (Normal)\r\n";
          $header.="Message-ID: <172562218.".date("YmjHis")."@$domain>\r\n";
          $header.="To: =?windows-1251?Q?".str_replace("+","_",str_replace("%","=",urlencode(iconv('utf-8','windows-1251',$to))))."?= <$to>\r\n";
          $header.="Subject: =?windows-1251?Q?".str_replace("+","_",str_replace("%","=",urlencode(iconv('utf-8','windows-1251',$tema))))."?=\r\n";
          $header.="MIME-Version: 1.0\r\n";
          $header.="Content-Type: text/html; charset=windows-1251\r\n";
          $header.="Content-Transfer-Encoding: 8bit\r\n";

          $message = str_replace("\n","",$mess);
          $message=iconv('utf-8','windows-1251',$message);

          $smtp_conn = fsockopen("80.170.220.47", 25, $errno, $errstr, 10);

          $this->get_data($smtp_conn);

          fputs($smtp_conn,"EHLO localhost\r\n");

          $this->get_data($smtp_conn);

          $size_msg=strlen($header."\r\n".$text);

          fputs($smtp_conn,"MAIL FROM:<$from_email> SIZE=".$size_msg."\r\n");

          $this->get_data($smtp_conn);

          fputs($smtp_conn,"RCPT TO:<$to>\r\n");

          $this->get_data($smtp_conn);

          fputs($smtp_conn,"DATA\r\n");

          $this->get_data($smtp_conn);

          fputs($smtp_conn,$header."\r\n".$message."\r\n.\r\n");

          $this->get_data($smtp_conn);

          fputs($smtp_conn,"QUIT\r\n");

          $this->get_data($smtp_conn);
		 
	  return true; 		
      }
	 
 }

$mail_smtp = new mail_smtp();
 

Некоторые параметры, естественно, придётся заменить (например, ip — адрес smtp или пароль).

«Демон» на php (PostMail.php), который будет сидеть в процессах и отправлять наши письма:
ini_set("max_execution_time",'0');

require('classes/mail_smtp.php');  // Подключаем наш класс для отправки почты
require('redis.php');  // Подключаем файл - библиотеку для работы с redis (например php_redis)

while(1)
 {
	 usleep(100000);  // спим (менять под свои задачи)
	 
	 $array_0=$redis->list_lpop('mails');
	 
	 if($array_0)
	  {

		  $array=unserialize(base64_decode($array_0));
		  
		  if($array['to'])
		   {
			   if(!$mail_smtp->post($array['from'],$array['from_email'],$array['to'],$array['tema'],$array['mess']))
			    {
					$redis->list_rpush('mails',$array_0);
				}
								
		   }
		  
	  }
 }
 

Запустим наш скрипт PostMail.php: php -f /var/www/PostMail.php &
Можно добавить скрипт в автозагрузку.

Всё готово. Теперь скрипт PostMail.php будет ожидать новых писем в redis, а получив — сразу отправлять юзеру. (Скрипт на php показал себя надежно — память не течёт, цп не кушает. Работает без остановки уже 3 месяца — все хорошо).

P.S> Первый пост на Хабре, поэтому не судите строго. Старался писать коротко и по делу.
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.