Итак. Сама система генерации и рассылки писем на кучу адресов - дело элементарное. Давайте сейчас рассмотрим вопрос по исключению из последующих рассылок емайл адресов, которые вернулись назад, т.к. такого адреса не существует (ну или по какой либо другой причине). Изначально я хотел писать скрипт, который бы по 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, уж очень хитро фильтры спама она вскрывает, отсюда и эффективность.
ОтветитьУдалить