Bash-скрипт для анализа логов Postfix. Считает статистику по доменам, адресам,
relay-серверам, маршрутам, объёмам трафика и ошибкам доставки — из файлов
/var/log/mail.log*, из журнала systemd или из потока на stdin.
| Команда | Что показывает |
|---|---|
in |
топ доменов отправителей (успешные доставки) |
out |
топ доменов получателей (успешные доставки) |
senders |
топ адресов отправителей |
recipients |
топ адресов получателей |
relay_in |
relay для входящей почты (local / virtual / lmtp / pipe) |
relay_out |
relay для исходящей почты (транспорт smtp) |
relay_ips |
топ IP-адресов relay-серверов |
routes |
маршруты: домен отправителя → получателя → relay |
sizes |
топ доменов отправителей по объёму трафика (МБ) |
avg_sizes |
средний размер письма по доменам (КБ) |
failed |
все неуспешные доставки (bounced + deferred + expired) |
bounced |
только отскоки (status=bounced) |
deferred |
только отложенные (status=deferred) |
reasons |
топ причин неуспешной доставки (DSN + ответ сервера) |
summary |
сводная статистика за выбранный период |
raw |
разобранные записи в TSV — для собственных пайплайнов |
--today— только сегодняшние письма--last-hour— за последний час--last-24h— за последние 24 часа--since EXPR/--until EXPR— произвольное окно (любое выражение GNUdate:"2 days ago","2026-08-01 09:00","last monday")
- таблица (по умолчанию) — выровненные колонки с поддержкой UTF-8
--csv— для Excel / Google Sheets, с корректным экранированием--json— валидный JSON для интеграций--tsv— «сырые» строки дляcut,awkи прочих утилит
- файлы
/var/log/mail.log*,/var/log/maillog*,/var/log/mail/mail.log*(по умолчанию) --log PATH— произвольный путь или glob, опцию можно повторять--journal—journalctl -u postfix--stdin— поток на стандартном вводе- сжатые файлы
.gz,.bz2,.xz,.zst,.Zраспаковываются на лету
- ОС: Linux/Unix, Bash 4+
- awk: любой —
mawk,gawkилиbusybox awk(скрипт не использует GNU-расширения вроде трёхаргументногоmatch()илиmktime()) - Утилиты:
sort,date(GNU),stat; для сжатых логов —gzip/bzip2/xz/zstd - Права: чтение лог-файлов, обычно нужен
sudo
git clone https://github.com/Anton-Babaskin/mail_analyzer.sh.git
cd mail_analyzer.sh
chmod +x mail_analyzer.shmail_analyzer.sh <команда> [опции] [файл-лога ...]
Полная справка: ./mail_analyzer.sh help
# Топ-100 входящих и исходящих доменов
sudo ./mail_analyzer.sh in
sudo ./mail_analyzer.sh out
# Топ-20 получателей за сутки
sudo ./mail_analyzer.sh out -n 20 --last-24h
# Куда уходит трафик и сколько его
sudo ./mail_analyzer.sh sizes --today
sudo ./mail_analyzer.sh avg_sizes
# Разбор проблем доставки
sudo ./mail_analyzer.sh failed --last-hour
sudo ./mail_analyzer.sh reasons --last-24h
# Экспорт
sudo ./mail_analyzer.sh in --today --csv > incoming.csv
sudo ./mail_analyzer.sh routes --last-24h --json > routes.json
# Произвольное окно и свой файл лога
./mail_analyzer.sh summary --log /backup/mail.log.1.gz --since "2026-08-01" --until "2026-08-08"
# Журнал systemd вместо файлов
sudo ./mail_analyzer.sh out --journal --today
# Собственный пайплайн поверх raw
sudo ./mail_analyzer.sh raw --today | awk -F'\t' '$4 == "deferred" { print $8 }' | sort | uniq -c=== ТОП-100 ВХОДЯЩИХ ДОМЕНОВ (откуда приходят письма) ===
Количество | Домен отправителя
-----------|---------------------
2 | example.com
2 | gmail.com
1 | mail.ru
=== ТОП-100 МАРШРУТОВ ПИСЕМ ===
Количество | Отправитель -> Получатель -> Relay
-----------|-----------------------------------
1 | example.com -> yandex.ru -> mx.yandex.ru
1 | gmail.com -> example.com -> virtual
=== СВОДНАЯ СТАТИСТИКА ===
Метрика | Значение
------------------------------------|---------------------
Записей о доставке | 8
Успешно доставлено | 6
Отскоки (bounced) | 1
Отложено (deferred) | 1
Доля успешных, % | 75.0
Уникальных доменов отправителей | 3
Общий объём, МБ | 2.53
Период (первая запись) | 2026-08-20 09:00:03
Период (последняя запись) | 2026-08-20 12:00:01
{
"title": "ТОП-100 ИСХОДЯЩИХ ДОМЕНОВ (куда отправляются письма)",
"generated_at": "2026-08-20 12:20:43",
"count": 5,
"rows": [
{"count": 2, "domain": "example.com"},
{"count": 1, "domain": "gmail.com"}
]
}- Выбор источника. Файлы находятся по glob и читаются от самых старых к самым
новым (по времени изменения) — это важно, чтобы
qmgr-строка с отправителем встретилась раньше строки о доставке. - Парсинг. Строка вида
postfix/<transport>[pid]: <QID>: ...разбирается черезindex()/substr(), без GNU-расширений awk. Поддерживаются традиционные syslog-метки (Aug 20 12:00:00), метки RFC 3339 (2026-08-20T12:00:00+03:00, в том числе с суффиксомZ) и префикс приоритета syslog (<22>). - Связывание.
postfix/qmgrдаёт отправителя и размер письма по QID, строкиsmtp/lmtp/local/virtual/pipe— получателя, relay и статус. Поqmgr: <QID>: removedзапись из памяти удаляется, поэтому потребление памяти не растёт с размером лога. - Фильтрация. Временное окно применяется к записям о доставке;
qmgr-строки обрабатываются всегда, иначе на границе окна терялся бы отправитель. - Агрегация и вывод. Подсчёт, сортировка по убыванию, ограничение
-nи рендер в таблицу / CSV / JSON / TSV.
| # | Поле | Описание |
|---|---|---|
| 1 | ts |
unix-время записи (0, если метка не разобрана) |
| 2 | dir |
in / out / other |
| 3 | transport |
smtp, lmtp, local, virtual, pipe, error, … |
| 4 | status |
sent, bounced, deferred, expired |
| 5 | from_addr |
адрес отправителя (envelope) |
| 6 | from_domain |
домен отправителя ((bounce) для пустого отправителя) |
| 7 | to_addr |
адрес получателя |
| 8 | to_domain |
домен получателя |
| 9 | relay_host |
имя relay-сервера |
| 10 | relay_ip |
IP relay-сервера |
| 11 | size |
размер письма в байтах (0 — неизвестен) |
| 12 | dsn |
код DSN |
| 13 | reason |
ответ сервера для неуспешных доставок |
./tests/run_tests.shНабор из 32 тестов на фикстуре tests/sample_mail.log: разбор лога, временные
фильтры, форматы вывода, метки времени, источники данных и коды возврата.
Тесты проходят на mawk, gawk, gawk --posix и busybox awk — скрипт
берёт awk из PATH, так что проверить другую реализацию можно, подставив её
в PATH первой.
Линтер:
shellcheck -s bash mail_analyzer.sh tests/run_tests.sh- Права доступа: для
/var/log/mail.log*обычно нуженsudo. Если часть файлов недоступна, скрипт скажет об этом и продолжит с остальными. - Многократные получатели: Postfix пишет размер письма один раз на очередь,
поэтому в
sizesписьмо с несколькими получателями учитывается по разу на каждую доставку — так же, как оно и уходит в сеть. inиoutсчитают все успешные доставки:in— по домену отправителя,out— по домену получателя. На шлюзе-релее это даёт картину «откуда» и «куда», а разделение по транспорту доступно вrelay_in/relay_outи в полеdirвыводаraw.- Неразобранные метки времени: при активном фильтре такие записи отбрасываются, а их количество печатается в stderr.
- ➕ Команды
senders,recipients,relay_ips,reasons,summary,raw,deferredв дополнение кin,out,relay_in,relay_out,routes,sizes,avg_sizes,failed,bounced - ➕ Временные фильтры
--today,--last-hour,--last-24h,--since,--until - ➕ Экспорт в CSV, JSON и TSV; ограничение вывода
-n/--top - ➕ Источники данных:
--log,--journal,--stdin, сжатые логи.gz/.bz2/.xz/.zst - 🐛 Скрипт больше не требует
gawk: убраны трёхаргументныйmatch()и другие GNU-расширения, из-за которых на Debian/Ubuntu (гдеawk— этоmawk) анализ падал сsyntax error - 🐛 Убрано небезопасное
$(ls -1tr ...)без кавычек — имена файлов больше не разбиваются по пробелам - 🐛 Записи о доставленных письмах больше не накапливаются в памяти: состояние
очереди освобождается по
qmgr: removed - 🐛 Понятные ошибки и коды возврата вместо пустого вывода: нет логов, нет прав, неизвестная команда, некорректная дата
- ➕ Поддержка меток RFC 3339, длинных QID, нескольких инстансов Postfix
- ➕ Набор тестов
tests/run_tests.shи чистыйshellcheck
- ✅ Базовый анализ входящих/исходящих доменов
- ✅ Поддержка сжатых логов
- Сделайте fork репозитория
- Создайте ветку (
git checkout -b feature/amazing-feature) - Убедитесь, что проходят
./tests/run_tests.shиshellcheck -s bash mail_analyzer.sh - Зафиксируйте изменения и отправьте Pull Request
MIT — подробности в файле LICENSE.
⭐ Если проект оказался полезным, поставьте звездочку!