Условия: есть веб-приложение, которое на различные события рассылает пользователям письма с уведомлением, например о том, что на главной странице любимого портала появилась новая статья.
Задача: надо дать возможность пользователям использовать почту без захода на сайт для того, что бы общаться в комментариях к этой статье.
Итак, мне приходит письмо «Ура, новая статья», я жму Reply, пишу ответ, отправляю письмо и робот добавляет мой комментарий в базу. Вопросы начинаются уже здесь: что именно должно прийти в письме, что бы мне было удобно отвечать прям из почты, не тратить время на загрузку сайта? Полагаю, что резонно включить весь текст статьи с её картинками.
Итак, здоровенное письмо пришло, текст прочитан, оценен, комментарий написан, письмо ушло. Возникает следующий вопрос: как робот будет понимать, к какой именно статье комментарий? Проще всего вставлять перед статьёй (или после неё) строчку вида:
Статья №123456
и надеяться, что пользователь не сотрёт её. Опыт крупных сервисов показывает, что не стирают.
Есть ещё вариант: общаться со своим SMTP сервером напрямую, записывать все Message-ID, которые пишутся в заголовках почты и отслеживать их в заголовке In-Reply-To вернувшегося письма. Способ мне видится чрезвычайно сложным и так же чрезвычайно ненадёжным.
Третий вариант — отслеживание по заголовку, но тогда прийдётся включать ID в заголовок: название статьи — вещь ненадежная в плане уникальности.
Ещё вариант от читателей. Проставляем from: noreply@service.com, и reply-to: x-page-3214@service.com
Штука надежная и хорошо выглядит.
Следующая проблема, с которой сталкиваемся — как отрезать текст свежего комментария от того, что почтовый клиент оформил квотированием. Обрезать строчки с «>» вначале просто. Но клиент ведь вставляет строчку в которой написано когда это было написано. Пока что я обучил 4-м основным клиентам (Outlook, Gmail, Apple Mail, The Bat), но скорее всего имеет смысл просто убивать последнюю строчку без > перед блоком квотированного текста.
Вопрос читателям: может кто-то сталкивался с такой задачей и знает решение лучше, или всё таки прийдется собирать почту с тысячи разных клиентов и обучать парсер на ней?
Дальше дело техники: по полю From ищем автора, ему приписываем извлечённый текст и добавляем к статье комментарий, о чём извещаем всех подписанных пользователей, причём вставляем комментарий в тело сообщения.
С From есть небольшая проблема: его легко можно подделать =) Хорошо было бы заставить почтовый сервер сохранять тот заголовок RCPT FROM, который сообщается SMTP-серверу при отсылке письма. Или подкладывать в каждое письмо ключ вида salt+MD5(salt+email+commentable_id+key). Какое решение лучше?
Теперь поговорим о серверой части. Например, в рецептах к Ruby on Rails есть совет настроить рельсовый скрипт, как procmail. Более вредного и дурного совета придумать сложно: если на каждое письмо поднимать рельсовый процесс (или любой другой сервер приложения), то при более-менее хорошем потоке писем потребуется гуглобаржа. Гораздо гуманнее по отношению к серверу написать демона, который будет обращаться к POP3-серверу, считывать почту, парсить её и обрабатывать. Очередь писем всё равно нужна, так что пусть ей занимается отлаженный быстрый сервер на C =)
Тут сразу возникает механизм простого распараллеливания нагрузки между кучей демонов по чтению почты: UIDL команда POP3 серверу (Courier как POP3 сервер, Postfix — MDA) отдает идентификаторы писем с их timestamp-ами. Соответственно, запускаем N демонов и каждый берет только те письма, которые пришли в t: t % N == m, где 0 <= m < N — номер демона.
Однако, демон может упасть. Да и рестартовать его стоит регулярно. Я сделал так: по крону раз в минуту запускается процесс который проверяет наличие демона и если демон мертв, то его поднимают. При этом сам демон прочитав все письма засыпает на 60 секунд, что бы лишнего не мучать почтовый сервер. После 10 циклов он умирает, что бы память не текла.
Итак, резюме:
1) в исходящие письма вписываем текст того, на что отвечаем
2) подкладываем в каком-то виде идентификатор того, на что надо отвечать
3) закладываемся на неподделанность поля From
4) опытным методом учимся отделять новый текст от старого в возвращающихся письмах: сколько клиентов, столько форматов
5) для чтения почты пишем демона
6) проверяем его существование по крону или любым механизмом типа monit/god и т.п.