Направление входящей почты Exim в ваш скрипт

Быстрые ссылки
Управление конфигурацией Exim
Создание маршрутизатора
Создание транспорта
Использование переменных окружения
Коды возврата
Вызов команды
Заключение
Введение
Exim — распространённый MTA (агент передачи почты) для Unix-подобных систем. Он гибок и позволяет настраивать маршруты и способы доставки. В этой статье показано, как перенаправить входящие сообщения в ваш собственный скрипт для дальнейшей обработки.
Предположим, что Exim уже установлен и принимает почту. Если вы начинаете с нуля, официальная вики Exim даёт подробные инструкции по установке.
Управление конфигурацией Exim
Подход к хранению конфигурации зависит от дистрибутива и способа установки. Exim, собранный из исходников, обычно использует файл
src/configure.defaultв качестве конфигурации по умолчанию.
Для установки через пакетный менеджер конфигурационный файл чаще всего находится по одному из путей:
/etc/exim/configили
/etc/exim.confЧтобы узнать точный путь, используйте:
exim -bP configure_fileDebian-подобные системы имеют особенности: конфигурация может быть в одном файле
/etc/exim4/exim4.conf.templateили разбита на части в каталоге
/etc/exim4/conf.dПосле изменений в split-конфигурации нужно выполнять
update-exim4.confдля сборки единого файла, который реально читает Exim. После правок перезапустите Exim:
service exim4 restartВажно: изменения в конфигурации не применяются без перезапуска/перезаписи файла, который читает Exim.
Создание маршрутизатора
Чтобы направить почту в приложение, сначала определите собственный router. Маршрутизаторы сопоставляют входящие сообщения условиям и выбирают, каким транспортом доставлять сообщение.
Маршрутизаторы обрабатываются в порядке их появления в конфигурационном файле. Любой router до вашего может перехватить сообщение раньше, поэтому поместите ваш router в правильное место (обычно в секцию ROUTERS CONFIGURATION).
Если вы используете split-конфигурацию в Debian, создайте файл в:
/etc/exim4/conf.d/routersФайлы объединяются в алфавитном порядке, поэтому выбирайте имя файла соответствующим образом (например, 40_custom_router).
Пример простого router:
example_router:
driver = accept
domains = example.com
transport = example_transportЭтот router принимает любые письма для домена example.com и передаёт их на транспорт example_transport.
Совет по отладки: временно добавляйте в лог log_message или включайте более подробный уровень логирования, чтобы увидеть, сработал ли ваш router.
Создание транспорта
После принятия сообщения router’ом его доставляет transport. Транспорты реализуют процесс фактической доставки: через SMTP, локальному пользователю, в файл или в pipe для вызова программы.
Порядок объявлений transport’ов не важен: router указывает конкретный transport, который будет использоваться.
Добавьте транспорт в секцию TRANSPORTS CONFIGURATION или в /etc/exim4/conf.d/transports на Debian.
Пример транспорта, вызывающего PHP-скрипт:
example_transport:
driver = pipe
command = /usr/bin/php /var/www/html/handle_incoming_email.php
user = www-data
group = www-dataВ этом случае Exim запускает указанный command и передаёт письмо в формате RFC на stdin команды. Команда исполняется от имени пользователя www-data.
Пример получения письма в PHP (чтение из stdin):
// Получение содержимого письма из стандартного ввода в PHP
$email = fopen("php://stdin", "r");
// To: user@example.com
// Subject: Demo Email
//
// This is an email.Проверьте корректность прав доступа к сценариям и окружению пользователя, под которым выполняется команда.
Важно: если ваш скрипт требует определённой оболочки или сложной последовательности команд, используйте use_shell в транспорте (см. раздел “Вызов команды”).
Переменные окружения
Exim задаёт ряд переменных окружения перед вызовом команды. Это удобно для получения метаданных без парсинга полного письма.
Типичные переменные:
- DOMAIN — домен, на который принято письмо.
- MESSAGE_ID — внутренний идентификатор Exim для сообщения.
- RECIPIENT — адрес получателя.
- SENDER — адрес отправителя.
Вы можете добавить собственные переменные через опцию environment в конфигурации транспорта. Формат — список пар ключ=значение через запятую:
example_transport:
environment = foo=bar,demo=exampleПримечание: значения не интерполируются оболочкой, если вы не используете use_shell.
Коды возврата и поведение Exim
Команда должна завершаться с кодом 0 для подтверждения успешной доставки. Любой ненулевой код считается ошибкой — отправителю придёт bounce.
Если вы хотите игнорировать код возврата, установите ignore_status в транспорте.
Опция temp_errors позволяет указать список кодов выхода, которые считаются временной ошибкой; тогда Exim отложит доставку и попробует снова позже.
return_fail_output = true добавит stdout вашего скрипта в текст bounce-сообщения — это можно использовать для передачи подробного объяснения отправителю.
Примеры настроек:
example_transport:
ignore_status
# или указать временные ошибки
temp_errors = 75,76
# включить возврат stdout в bounce
return_fail_output = trueВажно: не включайте черезмерно подробные сообщения в bounce для публичных почтовых систем — это может раскрыть внутреннюю информацию.
Вызов команды и таймауты
По умолчанию Exim вызывает команду напрямую. Если в транспорте установить use_shell, команда будет передана /bin/sh.
По умолчанию время выполнения команды ограничено одной часовой сессией (1 час). Таймаут считается ошибкой: команда будет убита и отправителю вернётся bounce. Значение таймаута можно изменить с помощью timeout. Если добавить timeout_defer, таймаут будет считаться временной ошибкой.
Пример:
example_transport:
timeout = 5m
timeout_deferКонкурентность: один и тот же скрипт может запускаться параллельно при одновременном приходе нескольких писем. Не используйте глобальные блокировки, которые запрещают параллельное выполнение; лучше проектировать обработчик так, чтобы он корректно работал в параллельных инстанциях, либо используйте внешнюю систему очередей.
Логирование и отладка
- Включайте логирование ошибок в самом скрипте (файлы, syslog, stderr).
- Временный режим: перенаправьте почту в локальный файл для тестов вместо вызова скрипта (driver = appendfile) или используйте тестовый router с ограниченным IP/доменом.
- Увеличьте уровень логов Exim для диагностики (exim -d или через конфиг).
- Тестируйте вызов команды вручную под тем же пользователем и окружением, что и Exim.
Важно: многие проблемы связаны с окружением (PATH, доступ к PHP-бинарю, права на файлы). Симулируйте окружение Exim при ручном тесте: sudo -u www-data /usr/bin/php /var/www/html/handle_incoming_email.php < sample.eml.
Тестовые случаи и приёмка
Критерии приёмки
- Сообщение, отправленное на адрес в домене, обрабатывается и приводит к коду выхода 0.
- Скрипт корректно получает исходное письмо на stdin в формате RFC и сохраняет/обрабатывает требуемые заголовки и тело.
- В случае ошибки скрипт возвращает код и Exim либо отдаёт bounce, либо откладывает доставку согласно настройкам.
- Логирование содержит достаточные сведения для диагностики причины отказа.
Примеры тест-кейсов
- Простое письмо с телом и заголовками. Ожидаем: код 0 и запись в базу/файл.
- Большое письмо (>10 МБ) — проверить поведение, таймауты и память.
- Одновременное прибытие 10 писем — проверить параллельное выполнение и корректность обработки.
- Скрипт возвращает ненулевой код, включено
temp_errors— сообщение откладывается. - Скрипт пишет в stdout подробности и
return_fail_output = true— отправитель получает понятный bounce.
Рекомендации по безопасности и надёжности
- Минимизируйте права пользователя, от которого запускается скрипт (least privilege).
- Проверяйте и валидируйте внешний ввод (заголовки, вложения).
- Ограничьте размер принимаемых сообщений и завершающие таймауты.
- Разделяйте обработку: быстрый фильтр в Exim + асинхронная очередь для тяжёлой обработки.
Примечание: передавать управляющие данные в stdout для включения в bounce стоит осторожно, чтобы не раскрывать чувствительной информации.
Модель принятия решений (мини-методология)
- Определите цель: что должно сделать приложение с письмом (лог, автоответ, запись в БД, преобразование).
- Спроектируйте контракт: какой вход (RFC-формат на stdin), какие переменные окружения нужны, ожидаемые коды возврата.
- Настройте router, укажите transport = pipe с нужными опциями.
- Реализуйте и локально протестируйте скрипт под тем же пользователем и окружением.
- Деплойте изменения в отдельной тестовой зоне, наблюдайте логи и корректируйте таймауты/переменные.
Роль-ориентированные чек-листы
Для администратора:
- Проверить путь конфигурации Exim:
exim -bP configure_file. - Разместить router в правильной секции/файле.
- Перезапустить Exim и проверить логи на ошибки.
Для разработчика приложения:
- Обеспечить приём письма из stdin в формате RFC.
- Логировать ошибки и возвращать понятные коды выхода.
- Тестировать под пользователем, от которого запускает Exim.
Для тестировщика:
- Подготовить набор .eml файлов с разными кейсами.
- Проверить поведение при ошибках/таймаутах.
- Убедиться, что bounce-сообщения информативны при необходимости.
Галерея крайних случаев (Edge-case gallery)
- Письмо с повреждёнными заголовками: как ведёт себя парсер.
- Вложение с вирусом: интеграция с антивирусом на этапе обработки.
- Почта с множеством получателей: маршрутизация на несколько вызовов/параллельная обработка.
- DNS-сбоии/серверы бумеранг: внешние задержки при обратной проверке.
- Большие потоки спама: защита от DDoS/ограничение количества одновременно запущенных процессов.
Примеры альтернативных подходов
- Вместо pipe писать в очередь (RabbitMQ, Redis) и обрабатывать письма асинхронно отдельными воркерами.
- Использовать LMTP/SMTP локальный агент, который принимает и сразу пересылает сообщения на приложение.
- Доставлять в файл (appendfile), а затем периодически считывать файлы и обрабатывать их.
Когда pipe — плохой выбор: если обработка занимает длительное время или требует тяжёлых ресурсов. В таких случаях предпочтительнее асинхронная очередь.
Краткое резюме
Exim позволяет гибко направлять входящие сообщения в ваши приложения через router + transport с драйвером pipe. Обратите внимание на переменные окружения, коды возврата, таймауты и права пользователей. Тщательно протестируйте сценарии ошибок и параллельную обработку.
Важное: проектируйте обработчик так, чтобы он был отказоустойчивым, безопасным и предсказуемым при высоких нагрузках.
Фактбокс
- По умолчанию таймаут выполнения команды в Exim — 1 час.
- Команда получает полностью RFC-совместный текст письма на stdin.
- Переменные окружения (DOMAIN, RECIPIENT, SENDER, MESSAGE_ID) доступны до выполнения команды.
Глоссарий: MTA — Mail Transfer Agent (агент передачи почты); router — модуль маршрутизации внутри Exim; transport — модуль доставки сообщения.
Список литературы и дальше читать
- Официальная документация Exim (wiki и справочник параметров).
Похожие материалы
Как сменить MAC-адрес в Windows и Ubuntu
Incogni: защита личных данных от дата‑брокеров
Как смотреть фильмы Marvel на Disney+ — порядок выхода
Автоматическое пробуждение ПК в Windows 10
Резервное копирование WhatsApp в Google Drive