Обработка отлупов после рассылки

Вы собираетесь выполнять рассылки и уже настроили свой почтовый сервер. Вы опытный администратор и понимаете, что рано или поздно обработка отлупов станет очередной Вашей задачей. А может Вы давно уже рассылаете почту своим подписчикам и регулярно наблюдаете в логах

said: 550 Message was not accepted — invalid mailbox

или что-то подобное и хотите удалять из базы рассылок адреса на которые нет возможности качественно доставлять почту (на примере с postfix, при удачной доставке ответ принимающей стороны должен быть 250 2.0.0 Ok), тем более, что удаление таких адресов является требованием больших мейл-провайдеров

Таким образом, каждый отлуп должен быть обработан. Я это делаю в два этапа:

  • сбор отлупов по критерию
  • удаление из базы рассылок

Все предлагаемое в статье работает на Linux Gentoo. В качестве мейлера — postfix. Почта отсылается от info@myportal.tld, поле Reply-To в хидерах сообщений отсутствует. Соответственно отлупы приходят на info@myportal.tld. Также адрес info@myportal.tld открыт для обратной связи пользователей и на него могут идти вполне валидные сообщения. Пользователь info в системе локальный (procmail не работает с виртуальными), непривилегированный, в качестве шела — nologin

Обработка отлупов по критерию

Сначала нужно установить пакет procmail который будет фильтровать всю входящюю почту. Затем внесем изменения в main.cf postfix’а добавив строку

mailbox_command = /usr/bin/procmail -a "$EXTENSION" DEFAULT=/home/$USER/Maildir/

Далее нужно понимать по каким критериям будет обработан отлуп. Для этого я проанализаровал множество отлупов и выделил причины по которым почтовики отказываются принимать почту. Даю список:

Account blocked
Addresses failed
Address rejected
Autoanswer
Autoreply
Connection timed out
Host or domain name not found
Invalid mailbox
IP has been found on a block
Mailbox is frozen
Mailbox is full
Mailbox limit
Mailbox over quota
Mailbox unavailable
Maildir has overdrawn his diskspace quota
Mail quota for user
No mailbox
No such user
Operation timed out
Out of disk space
Overdrawn his diskspace quota
Over quota
Quota exceeded
Recipient address rejected
Recipient not found
Recipient unknown
RecipNotFound
Server configuration problem
Spam message rejected
Spam points
Temporarily blocked
Unknown user
Unrouteable address
User not found
User unknown

Итого 35 фраз по которым будет фильтровать procmail

В домашней папке пользователя info создаем файл .procmailrc. Содержимое файла разбито на блоки и сопровождается комментариями

В начале файла пишем следующие:

LOGFILE=$HOME/log/procmail.log
VERBOSE=NO
SHELL=/bin/sh
PATH=/bin:/usr/bin:/usr/sbin

Тут все ясно: все действия пишем в лог; «болтливость» отключена; задан шел; указаны пути поиска программ

Теперь сами правила обработки (в терминах promail’а — рецепты)

:0 h
* ^From.*(relay.wnet.ua|nullsender@meta.ua)
/dev/null

Согласно этому рецепту procmail посмотрит в хидер сообщения. И если то, что стоит в поле From удовлетворяет условию, то такое письмо улетает в /dev/null. Если то, что стоит в поле From не удовлетворяет условию, то просматривается следующий рецепт

:0 h
* ^Subject.*Auto(answer|reply).*
/dev/null

Согласно этого рецепта в /dev/null улетает все письма в теме которых присутствуют слова «Autoanswer» или «Autoreply». Если это условие не выполняется, то просматривается следующий рецепт

:0
* ^FROM_DAEMON
{
 :0:
 * B ?? .*(account (blocked|that you tried to reach is disabled)|address(es failed| rejected)|(invalid|no) mailbox|mailbox (is frozen|unavailable)|message rejected|(no such|unknown) user|(host|recipient|user) (not found|unknown)|RecipNotFound|unrouteable address).*
 $HOME/Mailbox/unknown.mbox

 :0:
 * B ?? .*(mailbox (full|is full|limit)|(diskspace|mail|over) quota|out of disk space|quota exceeded).*
 $HOME/Mailbox/quota.mbox

 :0 E
 /dev/null
}

В данном рецепте, если письмо пришло от MAILER-DAEMON (что попадает под шаблон FROM_DAEMON читаем в man procmailrc), то выполняются дальнейшая проверка условий в фигурных скобках. Просматривается тело письма. Если условие выполняется в первом рецепте, то отлуп записывается в файл unknown.mbox и обработка рецептов заканчивается. Аналогично отрабатывает следующий рецепт и происходит запись в файл quota.mbox. Я намеренно отделил сбор в quota.mbox в надежде, что пользователь почистит свой ящик :-). Если отлуп не соответствует ни одному из условий, то отработает последний рецепт и такой отлуп улетит в /dev/null. Вместо /dev/null можно написать

! admin@myportal.tld

и тогда отлупы будут падать в почтовый ящик администратора для дальнейшего анализа

:0
* !^FROM_DAEMON
/var/mail/manager

Но наш адрес info@myportal.tld предназначен для обратной связи пользователей. И письма не отлупы должен получать наш менеджер. Об этом говорит этот рецепт

Удаление из базы рассылок

#!/bin/sh

# SHOW ALL FROM REDIS
#redis-cli -s /tmp/redis.sock --raw lrange invalid_mails 0 -1

cd /home/info

### A-LA (USER UNKNOWN|QUOTA)
cat Mailbox/*.mbox | grep '^To:' | grep -v 'info@dtkt.ua' | awk '{print $2}' | tr -d \<\> | grep -v '<' | uniq | \
xargs redis-cli -s /tmp/redis.sock rpush invalid_mails

cat /dev/null > Mailbox/quota.mbox
cat /dev/null > Mailbox/unknown.mbox

### DELETE ALL INVALID MAILS FROM DB
for invalid_mail in `redis-cli -s /tmp/redis.sock --raw lrange invalid_mails 0 -1`;
do
 mysql -u user --password="*******" -e "DELETE FROM myportal.mailing_users WHERE </span><span style="color: #993366;">email = '$invalid_mail'";</span>
done

### CLEAR FROM ALL
redis-cli -s /tmp/redis.sock del lrange invalid_mails

exit 0

Данный скрипт собирает адреса из файлов quota.mbox и unknown.mbox и записывает их в базу invalid_mails бд redis, обнуляет файлы *.mbox, выполняет mysql-запрос на удаление адресов из бд рассылок и в завершении удаляет базу invalid_mails. Запускается раз в сутки по крону

Александр Черных
системный администратор

Статьи по теме