Итак. Сама система генерации и рассылки писем на кучу адресов - дело элементарное. Давайте сейчас рассмотрим вопрос по исключению из последующих рассылок емайл адресов, которые вернулись назад, т.к. такого адреса не существует (ну или по какой либо другой причине). Изначально я хотел писать скрипт, который бы по pop3 протоколу соединялся бы с сервером почты, забирал ее и дальше парсил, но, как известно, лень - двигатель прогресса. В результате немного гугла и организовалось простое и, главное, быстрое решение этой задачи.
В моем случае рассылка производится с выделенного сервера на котором находится сайт организации и завуалирована под новостную. Она и вправду новостная, можно подписываться и отписываться и т.д. но вот мой начальник в нее добавляет кучу адресов электронной почты людей, которые и не думали подписываться, так что кроме как спам рассылкой, это я назвать никак не могу :). На момент написания статьи их уже более 40 тысяч.
Так вот. В моем случае, база данных с адресами находится на сервере где и сайт. Оттуда же происходит рассылка писем. Сам же почтовый ящик домена, который привязан к сайту, находится у гугла посредством привязки домена. Т.е. возвраты почты идут на гугл и их оттуда нужно как то забирать чтобы проанализировать.
Для того чтобы получить необходимую нам почту будем использовать fetchmail, а для того, чтобы распарсить - procmail и небольшой скрипт на пхп. Для начала устанавливаем на сервере fechmail и procmail
sudo apt-get install fetchmail procmailfetchmail необходимо запускать от обычного пользователя и его файл настроек ( .fetchmailrc, который находится в домашней директории) должен быть с правами 710. соответственно:
sudo chmod 710 /usr/home/USER_NAME/.fetchmailrc sudo chown USER_NAME /usr/home/USER_NAME/.fetchmailrcдалее редактируем этот ./fetchmailrc, а точнее, прописываем у него почтовый сервер сервер гугла и почтовый аккаунт (на который нам будут приходить возвраты). Вот пример моих настрок:
-----------~/.fetchmailrc---------------------------
#File : .fetchmailrc set postmaster "local_user" poll pop.gmail.com with proto POP3 and options no dns user 'MYEMAIL@MYDOMEN.COM' is 'GMAIL_USER' here options ssl password "PASSWORD"----------------------------------------------------
теперь настроим procmail, который будет фильтровать почту и ложить на различные почтовые ящики (сохранять в различные файлы). В домашней директории необходимо создать файл настроек procmail - ~/.procmailrc. его содержимое я любезно позаимствовал на сайте документации по gentoo, добавив в него всего лишь одно правило. На всякий случай привожу файл полностью.
-----------~/.procmailrc----------------------------
MAILDIR=$HOME/MuttMail ##проверьте правильность пути LOGFILE=$HOME/.procmaillog LOGABSTRACT=no #VERBOSE=on... используется только для отладки VERBOSE=off FORMAIL=/usr/bin/formail NL=" " ##условные строки начинаются с :0 ##не записывайте комментарии в строки условия ##отредактируйте ненужные условия! ##строки условий начинаются с *, а регулярные выражения ваши лучшие друзья ##условия добавленные после * попадают прямо в egrep ##строка следущая за условиями, в следующем регистре является именем почтового ящика #отлавливание копий, используя formail :0 Whc: .msgid.lock | $FORMAIL -D 16384 .msgid.cache :0 a $MAILDIR/duplicates #люди которые всегда пишут с одного почтового адреса :0 * ^From:.*(craig\@hotmail|renee\@local.com) $MAILDIR/friends #выборка некоторого спама :0 * ^Subject:.*(credit|cash|money|debt|sex|sale|loan) $MAILDIR/spam #никаких html писем :0 * ^Content-Type:.*html $MAILDIR/junk :0 * ^From:.*@freshmeat\.net freshmeat :0 * ^From:.*MAILER-DAEMON@ubuntu\-1004\-lucid\-64\-minimal returned-emails ########################################### # последние условие: складирует остальную # # почту в почтовый ящик по умолчанию # ########################################### :0 * .* default # конец файла----------------------------------------------------
тут я лишь добавил условие:
:0 * ^From:.*MAILER-DAEMON@ubuntu\-1004\-lucid\-64\-minimal returned-emailsкоторое говорит, что письма содержащие в поле From адрес MAILER-DAEMON@ubuntu-1004-lucid-64-minimal (это от моего сервера) помещать в ящик returned-emails
теперь, если запустить fetchmail командой:
fetchmail -k -m "/usr/bin/procmail -d %T"то в директории ~/MuttMail создаться файл returned-emails с почтой, которая была послана нашим сервером но по какой-то причине вернулась. ключ -k указывает fetchmail что почту необходимо оставлять на сервере. Теперь нам нужно получить из него email адреса, на которые была почта так неудачно послана. Для этого я написал небольшой скрипт на php.
------------wrong_emails.php------------------------
#!/usr/bin/php query("UPDATE `".$config['ros_db']."`.`ems_email_list` SET `wrong_email`='1', `date_wrong`=NOW() WHERE `email`='".$value."'"); $updated_rows+=mysql_affected_rows(); } // show result echo "Strings readed: ".$str_counter."\nWrong emails: ".count($wrong_emails)." Updated in DB: ".$updated_rows."\n"; }else{ echo "Error: Can't open ".$_EMAILS_FILE." file.\n"; } ?>----------------------------------------------------
здесь мы просто читаем построчно файл полученной от procmail почты и ищем в нем строки "----- The following addresses had permanent fatal errors -----", в следующей за ней строке содержится адрес электронной почты, на который не смогла быть доставлена рассылка. Мы его запоминаем в массив $wrong_emails а после того, как просмотрели весь файл - соединяемся с базой данных, в которой у нас содержаться email адреса рассылки и помечаем полученные адреса как wrong_email, а также, дату, когда мы этот адрес пометили как неверный. Дата нам нужна для того, чтобы потом в базе можно было эти адреса отыскать. Удалять эти адреса из базы нам нежелательно, дабы не добавить их потом снова (я же говорил, что это не совсем нормальная рассылка - пользователи не сами подписываются а добавляются моим начальством через скрипт экспорта, эти адреса они выдирают откуда только возможно. даже есть специальный сканер для визиток и т.д.).
Подключаемый файл "class.rose_mysql.php" - это небольшой класс обертки над mysql из моего личного проекта - панели управления для MMO Ragnarok Online сервера: http://ro-se.sourceforge.net. Вот его код:
<?php //////////////////////////////////////////////////////////////////////////////// // // Ragnarok Online Site Engine (ROse) // Copyright (c) 2006-2012 Fantik (aka Tamahome, aka Uvadzucumi) // //////////////////////////////////////////////////////////////////////////////// // ROSE MySQL Class // // Methods: // private function __construct($db_host, $db_user, $db_password, $db_name='', $debug=false) // // public static function connect($db_host, $db_user, $db_pass, $db_name='', $debug = false) // // mysql escape row, check MQ // TODO - check MQ moved to get POST data in ROSE main class // public function escape($source, $remove_html_tags=false) // // create and run insert query: // $table - database table // $data - associated array with insert "field_name"=>"field_value" // public function insert_one($table, $data) // // create and run update query // $table - table name // $data - associated array with insert "field_name"=>"field_value" // $id - update records uniq value // $id_field_name - updated records uniq field - default `id` // public function update_one($table, $data, $id, $id_field_name="`id`"); // // Return array contained all query result // public function GetAll($query); // class ROSE_MYSQL{ private $_dbh; private $_debug = false; private static $_instance; private $_error; private $_last_result; private $_magic_quotes; private function __construct($db_host, $db_user, $db_password, $db_name='', $debug=false){ $this->_debug = $debug; $this->_dbh = @mysql_connect($db_host, $db_user, $db_password); if ($this->_dbh == false){ // if not connected if(mysql_error()){ $this->_error = mysql_error(); }else if(isset($php_errormsg)){ $this->_error = $php_errormsg; }else{ $this->_error = 'Error conection to database: check setup parameters.'; } }else{ // if connected // Select database, if needed if($db_name!=""){ if(!@mysql_select_db($db_name,$this->_dbh)){ die('Error select detabase '.$db_name); } } $this->query("SET NAMES 'utf8' COLLATE 'utf8_general_ci'"); return $this->_dbh; } $this->_magic_quotes=get_magic_quotes_gpc(); } // run sql $query, return sql resource public function query($query){ if($this->_debug){ echo "".$query."\n";} if(is_resource($this->_last_result)){ // clear previous result mysql_free_result($this->_last_result); } $result = mysql_query($query, $this->_dbh); if(!$result){ $this->_error=mysql_error(); if($this->_debug){ echo $this->_error."\n"; echo "query:".$query."\n"; } } $this->_last_result=$result; return $result; // false, true, resource } // connect to database public static function connect($db_host, $db_user, $db_pass, $db_name='', $debug = false){ if(self::$_instance === null){ self::$_instance = new self($db_host, $db_user, $db_pass, $db_name, $debug); } return self::$_instance; } // mysql escape row, check MQ // TODO - check MQ moved to get POST data in ROSE main class public function escape($source, $remove_html_tags=false){ if($this->_magic_quotes){ // remove quotes if MQ enabled $str = stripslashes($source); } if($remove_html_tags){ // remove html tags if needed // $source=htmlspecialchars($source, ENT_QUOTES); $source=str_replace('<','<',$source); $source=str_replace('>','>',$source); } return mysql_real_escape_string($source); } // create and run insert query: // $table - database table // $data - associated array with insert "field_name"=>"field_value" public function insert_one($table, $data){ $query = "INSERT INTO $table SET "; $insert = array(); foreach($data as $field => $value){ if($value!='NOW()'){ $insert[] = "`$field` = '".$this->escape($value)."'"; }else{ $insert[] = "`$field` = $value"; } } $query .= implode(', ', $insert); return $this->query($query); } // create and run update query // $table - table name // $data - associated array with insert "field_name"=>"field_value" public function update_one($table, $data, $id, $id_field_name="`id`"){ $query = "UPDATE $table SET "; $update = array(); foreach($data as $field => $value){ if($value!='NOW()'){ $update[] = "`$field` = '".$this->escape($value)."'"; }else{ $update[] = '`'.$field.'` = '.$value; } } $query .= implode(', ', $update); $query .= " WHERE ".$table.".".$id_field_name." = $id"; return $this->query($query); } // public function delete($table, $id, $id_field_name='`id`'){ $where=''; if(is_array($id)){ foreach($id as $id_value){ if($where!=''){ $where.=' OR '; } $where.="$id_field_name = '".(int)($id_value)."'"; } }else{ $where="$id_field_name = '".(int)($id)."'"; } return $this->query("DELETE FROM $table WHERE ".$where); } // Return array contained all query result public function GetAll($query){ $ret=array(); $result=$this->query($query); if($result){ $num=mysql_num_rows($result); for($n=0;$n<$num;$n++){ $ret[]=mysql_fetch_assoc($result); } return $ret; }else{ return false; } } // Return array contained first query result public function GetOne($query){ $ret=false; $result=$this->query($query); if($result){ return mysql_fetch_assoc($result); }else{ return false; } } // create where string with logic operator public function create_where($fld_name,$values,$logic_operator=' OR '){ $where=''; foreach($values as $v){ if($where!=''){ $where.=$logic_operator; } $where.='`'.$fld_name."`='".$v."'"; } return $where; } } ?>
Осталось только упомянуть, что для запуска всего этого добра я использую нижеследующий баш скрипт, который, при желании, можно повесить на хрон.
----------------parse_mail.sh-----------------------
#!/bin/bash fetchmail -k -m "/usr/bin/procmail -d %T" ./wrong_emails.php rm ~/MuttMail/returned-emails exit----------------------------------------------------
Вот, пожалуй, и все, что касается относительно отслеживания неверных email адресов из списка нашей рассылки.
Вот как по мне http://StandartSend.ru Отличный сервис для массовой рассылке писем, использую его постоянно и пока нареканий нет, очень качественно и грамотно всё делают, рекомендую попробовать
ОтветитьУдалитьКлассный пост. Очень хорошо расписано всё. Надо поробывать. Я делаю через mutt и smtp. Инструкция здесь
ОтветитьУдалитьhttp://itc-life.ru/massovaya-rassylka-iz-konsoli-s-pomoshhyu-mutt/
Сам имею предпочтение к программе StandartMailer, брал отсюда - http://StandartMailer.ru, уж очень хитро фильтры спама она вскрывает, отсюда и эффективность.
ОтветитьУдалить