DSN (уведомление о доставке письма) для Exim, если родной DSN-патч не работает

Сразу условлюсь. Если есть возможность поставить родной DSN патч на exim, то лучше сделать это. К сожалению, после версии 4.63 это не так тривиально.

Для остальных случаев можно пойти обходным путём, который выглядит так.

1. Вносим изменения в главный конфиг

В начале добавляем строчки, чтобы иметь возможность подключать подпрограммы на Perl

perl_startup = do '/etc/exim4/exim.pl'
perl_at_start=yes

Все наши подпрограммы следует разместить в файле /etc/exim4/exim.pl
Затем, в самом начале условий по проверке данных (в acl_check_data) добавим подключение обоработчика

.include /etc/exim4/exim_dsn_perl.conf

2. Создаём обработчик писем

Создаём файл /etc/exim4/exim_dsn_perl.conf, в котором задаём обработчик писем с заголовками Return-Receipt-To и Disposition-Notification-To (именно они добавляются к письму, когда в почтовом клиенте пользователь ставит галку «уведомление о получении»)

warn
    condition    = ${if or {{def:h_Disposition-Notification-To:}{def:h_Return-Receipt-To:}}{yes}{no}}
    logwrite = DSN start from $sender_address to $recipients as $message_exim_id.
    set acl_m3  = ${perl{exim_dsn}{$message_exim_id}{$sender_address}{$recipients}{$h_Subject:}{$h_Content-Type:}}

Если будут проходить письма с этими волшебными заголовками, то в главный лог почтовика будет добавляться строчка DSN start (чтобы можно было отследить эти моменты) и будет вызываться подпрограмма exim_dsn, в неё передадутся:

  • ID сообщения
  • Адрес отправителя
  • Адреса получателей
  • Тема письма
  • Тип письма

3. Создаём подпрограмму постановки в очередь писем, требующих уведомления о доставки

Создадим файл /etc/exim4/exim.pl

#!/usr/bin/perl
 
sub exim_dsn{
    $id = shift;
    $sender = shift;
    $recipients = shift;
    $subject = shift;
    $mime = shift;
 
    open(A,">/var/spool/exim4/dsn/$id");
    print A 
"Message-ID: $id
Content-type: $mime
From: $sender
To: $recipients
Subject: $subject
";
    close(A);
    return 1;
}

Логика его работы прозрачна: «получить параметры письма, и создать файл очереди в /var/spool/exim4/dsn»

4.Создаём каталог для очереди и даём ему права на запись для Exim

mkdir /var/spool/exim4/dsn
chown Debian-exim:Debian-exim /var/spool/exim4/dsn
chmod 755 /var/spool/exim4/dsn

5. Перезапускаем Exim

/etc/init.d/exim4 restart

С этого момента /var/spool/exim4/dsn будет наполняться файлами, содержащими необходимую, для формирования отчета о доставке, информацию. Имя каждого файла совпадает с ID сообщения.

6. Создаём скрипт обработки очереди писем, требующих уведомления

Логика его работы такова

  • Прочитать информацию в каждом файле очереди
  • По ID сообщения, найти в главном логе Exim записи о его прохождении
  • Определить по записям его судьбу
  • Уведомить о его судьбе отправителя: доставлено, не доставлено, неизвестный статус сообщения (последние 2 с дампом лога)
  • Удалить файл из очереди
/etc/exim4/dsn.pl

#!/usr/bin/perl
use Encode qw/encode decode/;
 
$DSN_QUEUE_FOLDER = "/var/spool/exim4/dsn";
$LOG_FILENAME = "/var/log/exim4/mainlog";
$FROM = "adminrpn\@rpnmail.fmf.ru";
$MTA_COMMAND = "/usr/sbin/sendmail -t -i";
 
 
# Получаем имена файлов из очереди доставки
з очереди доставки
opendir($dd,$DSN_QUEUE_FOLDER);
A# Не обрабатываем то, чир не похоже на ID сообщения
атываем то, чир не похоже на ID с# Читаем файл очереди
omp($file);next unless $file=~m/^[\w\d\-]+$/gi;
    # Читаем файл о# Читаем метаданные письма из очереди
DER/$file");@lines = <a>;chomp(@lines);close(A);
    # Чи# Ищем в логах письмо
ные письма из оч# Если письма в mainlog нет - пропускаем этот элемент очереди
);
    # Ищем в логах п# Если письмо доставлено
d_log($id);
    # Если письма в mainlog нет - пропускаем э# Если письмо НЕ доставлено
реди
    next unless scalar(@logs);
    # Если письмо достав# В прочих случаях
ccess($charset,$subject,$from,$to,\@logs) if check_success(\@logs);
    # Если письмо НЕ дост# Удаляем из очереди
_failed($charset,$subject,$from,$to,\@logs# Проверяем, что письмо успешно ушло на другой MTA
лучаях
    send_unknown($charset,$subject,$from,$to,\@logs) if !check_failed(\@logs) && !check_success(\@logs);
    # Удаляем из очереди
    unlink("$DSN_QUEUE_FOLDER/$file");
}
 
# Провер# Проверяем, что письмо не смогло уйти на другой MTA
а другой MTA
sub check_success{
    $arg = shift;
    @lines = @{$arg};
    foreach $line(@lines){
        return 1 if $line=~m/T=r# Шлём письмо об успехе
;
        return 1 if $line=~m/H=localhost\s+.*?/;
    }
    return 0;
}
 
# Проверяем, что письмо не смогло уйти на другой MTA
sub check_failed{
    $arg = shift;
    @lines = @{$arg};
    foreach $line(@lines){r# Шлём письмо о неудаче
*{1,}\s+/;}
    return 0;
}
 
# Шлём письмо об успехе
sub send_success{
    ($charset,$subject,$from,$to,$logs) = bj;
    @logs = @{$logs};
    $subject = "Доставлено для $to Тема:$subject";
    $body = "Письмо для $to доставлено на его почтовый сервер";
    send_mail($charset,$subject,$from,$body);
}
 
# Шлём письмо о неудаче
sub send_failed{
    ($charset,$subject,$from,$to,$logs) = bj;
    @logs = @{$logs};
    $subject = "НЕ ДОСТАВЛЕНО для $to Тема:$subject";
    $body = "Письмо для $to не было доставлено на его почтовый сервер\n\n";
    $body .= "Подр# Отсылка письма
я почтового администратора\n>".join("\n>",@logs);
    send_mail($charset,$subject,$from,$body);
}
 
# Шлём письмо о непонятном статусе в логе
sub send_unknown{
    ($charset,$subject,$from,$to,$logs) = ub;
    @logs = @{$logs};
    $subject = "НЕИЗВЕСТНЫЙ СТАТУС ДОСТАВКИ для $to Re:$subject";
    $body = "Статус дос# Получение метаданных из файла очереди
ён\n\n";
    $body .= "Подробности для почтового администратора\n>".join("\n>",@logs);
    send_mail($charset,$subject,$from,$body);
}
 
# Отсылка письма
sub send_mail{
    ($charset,$subject,$to,$body) = To;
    $headers = "From: ".encode('MIME-Header', decode('utf8', "Уведомление о доставке письма < $FROM>"))."\# получаем массив строк, относящиеся к данному сообщению
:".encode('MIME-Header', decode('utf8',$subject))."\n"$id"  $headers .= "Content-type: text/plain; charset=UTF-8;\n\n";
    open(A,"|$MTA_COMMAND");
    print A $headers.$body;
    close(B);
}
 
# Получение метадан

[свернуть]

7. Помещаем обработчик очереди в cron

* * * * * /usr/bin/perl /etc/exim4/dsn.pl

Добавить комментарий