НОВОСТИ Zero Inbox. Гайд по наведению порядка в почте

Alvaros
Онлайн
Регистрация
14.05.16
Сообщения
21.452
Реакции
101
Репутация
204
Моему почтовому ящику на gmail много лет. Более десяти лет самостоятельного существования, а также в нем лежат архивы из других почтовых систем. Все эти годы я использовал его так как и нужно использовать умные продукты:

  • Не сортировал письма, так как для этого есть хороший поиск
  • Не удалял письма с большими вложениями, так как для этого есть куча пространства
  • Не категоризировал письма и позволял сервису обучаться на моих привычках, для автоматических эвристик


И это было чудесное время, пока мне не захотелось навести в нем порядок.


Задача навести порядок не была самоцелью, скорее меня начало раздражать что весь inbox завален каким-то мусором: заказы из магазинов, рекламные письма, обновления от почты, все это вперемешку с периодическими дайджестами и личной перепиской.


51783ec448d2d88be14eb4c042c4391f.png

^^ Это не настоящий скрин моей почты. Просто картинка для превью.


Задача



Прежде чем переходить к действиям я постарался сформулировать задачу которую хочу решить, получилось следующее:

  • Навести порядок в Лейблах. Сгруппировать их в логические блоки, добавить нужные и удалить лишние
  • Создать правила для автоматического присвоения ярлыков от основных источников. Пересмотреть правила попадания в Inbox для этих правил
  • Отписаться от лишних рассылок
  • Удалить письма которые посчитаю мусорными


Во втором пункте, говоря про "правила попадания в inbox", я имею ввиду, что новые письма, которые попадают под фильтры, могут сразу переходить в архив, но, при этом, остаются непрочитанными. Их не будет видно во "входящих", но к ним всегда можно вернуться. Это удобно для различных уведомлений или дайджестов.


Наведение порядка в Лейблах



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


3aebd735a16a755e8364fdbd06bdb6a6.png



Внутри каждого из этих лейблов есть пачка sub-labels и для избежания путаницы я решил что все письма будут лежать на уровне label/sub-label.


На скриншоте отображена конфигурация на момент написания статьи, хотя изначально было по-другому:


  • Я пытался разграничить письма по доменам и хостингу в разные фильтры Services/Domains & Services/Hosting, но в процессе понял что это одинаковая сущность и нет смысла её дробить. Оставил только Services/Hosting, как более общую, на мой взгляд


  • Notifications/Receipts & Notifications/Shops. Изначально, Receipts я заводил чтобы туда падали уведомления от различных платежных систем, такси и прочих сервисов. Уже в процессе сортировки я понял что сам путаюсь куда какое письмо относится и, вероятно, нужно эти две категории слить в одну.


    Уведомление от магазина "Кони и заводы": Ваш счет оплачен. Вот куда это? В магазины или в чеки? В итоге сам придумал логическую разницу — в Receipts отправляется все что не привязано к магазинам, либо не является физическим предметом. Стало понятно, так и оставил.


  • Work/Unsorted & Services/Anything. Эти лейблы появились как раз из-за того что я решил все хранить в sub-label. Туда отправляются письма которые не попадают под правила кластеризации и их слишком мало чтобы выносить в отдельный ярлык

Первый подход



В первую очередь я занялся самым простым: дайджестами, нотификациями от крупных сервисов и уведомлениями от сервисов которыми часто пользуюсь. Тут никакой автоматизации не требуется, абсолютно механическая работа. Я просто открыл две вкладки с почтой: в первой работал со списком писем, во второй настраивал фильтры и занимался сортировкой.


Руки быстро привыкают к одинаковой последовательности действий и за пару часов мне удалось разобрать большой массив писем.


К сожаления я не записывал прогресс чтобы поделиться подробной статистикой :(


Во время этих действий я активно пользовался поиском в почте, так что рекомендую ознакомиться с документацией по фильтрам:

  • Все входящие без лейблов
  • Весь архив без лейблов

Второй подход



После решения самых простых задач мне пришлось решать каким образом дальше организовать процесс.


Реальность была такова, что у меня оставалось несколько десятков тысяч писем во "входящих" и отсутствие понимания как их кластеризовать.


Было необхоидмо это как-нибудь автоматизировать. Я предположил что наверняка есть готовые скрипты, которые умеют подключаться по imap к почтовому ящику и делать что-нибудь. Поверхностный поиск на github навел меня на репозиторий:





Это набор скриптов на Python 2.x (на момент действий описываемых в статье), который позволяет распарсить формат и загрузить письма в Elasticsearch.


В есть подробнейшее описание, от идеи до примеров использования.


После того как письма будут загружены в хранилище — можно использовать любые агрегатные функции и строить аналитику по своему почтовому ящику.

Где взять mbox?


  • Если у вас Google Mail, то идете на , там запрашиваете выгрузку вашего почтового ящика и через несколько часов получаете готовый архив
  • Можно поискать скрипты для выгрузки сообщений, что-нибудь в стиле .
  • Подключиться почтовым клиентом к ящику и сделать выгрузку через него —

MongoDB



Поигравшись некоторое время с Elasticsearch, мне захотелось модифицировать данный скрипт под работу с MongoDB. Это связано с тем что у меня нет опыта работы с Elasticsearch и мне не хотелось заниматься его изучением, как минимум, до тех пор пока не завершен процесс уборки в ящике.


Первым делом я модифицировал исходный скрипт чтобы он загружал данные в MongoDB, убедившись что концепт работает, я переписал скрипт, попутно проведя рефакторинг, обновление зависимостей и перевод на Python 3.x.





Скрипт умеет все что умеет его аналог, только работает с MongoDB как с основным хранилищем. После загрузки данных, используя aggregation framework, можно строить любую аналитику по вашему почтовому ящику.


Usage: cli.py [OPTIONS] FILENAME

Print FILENAME.

FILENAME path to mbox file

Options:
--mongodb TEXT Connection string for mongodb instance [default:
mongodb://root:[email protected]]
--db-name TEXT MongoDB database name [default: google-mail]
--collection-name TEXT MongoDB collection name [default: mails]
--init BOOLEAN Force deleting and re-initializing the MongoDB
collection [default: False]
--body BOOLEAN Will index all body content, stripped of HTML/CSS/JS
etc. Adds fields: "body" and "body_size" [default:
False]
--help Show this message and exit.


Например, чтобы сгруппировать письма во "входящих" по отправителю, нужно выполнить следующий запрос (значение в labels может отличаться, в зависимости от ваших языковых настроек):


db.mails.aggregate([
{ $match: { labels: { $in: ['inbox'] } } },
{ $group: { _id: "$from", total: { $sum : 1 } } },
{ $sort : { "total": -1 } }
])


Результат будет в сгруппированном и отсортированном виде.


{ "_id" : "****@****", "total" : 3360 }
{ "_id" : "****@****", "total" : 2240 }
{ "_id" : "[email protected]", "total" : 360 }
{ "_id" : "****@gmail.com", "total" : 342 }
{ "_id" : "[email protected]", "total" : 337 }
{ "_id" : "[email protected]", "total" : 318 }
{ "_id" : "[email protected]", "total" : 229 }
{ "_id" : "****", "total" : 223 }
{ "_id" : "[email protected]", "total" : 190 }
{ "_id" : "****", "total" : 133 }
{ "_id" : "****", "total" : 129 }
{ "_id" : "****", "total" : 119 }
{ "_id" : "[email protected]", "total" : 115 }
{ "_id" : "[email protected]", "total" : 104 }
{ "_id" : "[email protected]", "total" : 96 }
{ "_id" : "****", "total" : 95 }
{ "_id" : "[email protected]", "total" : 91 }
{ "_id" : "[email protected]", "total" : 74 }
{ "_id" : "[email protected]", "total" : 70 }
{ "_id" : "[email protected]", "total" : 66 }
Type "it" for more
>


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


Для экспорта необходимо добавить параметр $out к нашему запросу. Тогда он запишет результат запроса в отдельную коллекцию.


db.mails.aggregate([
{ $match: { labels: { $in: ['inbox'] } } },
{ $group: { _id: "$from", total: { $sum : 1 } } },
{ $sort: { "total": -1 } },
{ $out: "export" }
])


После этого, с помощью утилиты mongoexport, можно выгрузить всю коллекцию в виде csv файла.


mongoexport -d google-mail \
-c export \
-u root \
-p example \
--authenticationDatabase admin \
--fields "_id,total" \
--type=csv \
--sort='{total:-1}' \
-o ~/path/to/file.csv


Далее, отредактируем по шаблону строчки в csv, добавив ссылки на страницу с поиском по отправителю:




SQL to MongoDB Cheatsheet



89aa3759b382f857738aeabbf037f22f.png



— отдельно хочу порекомендовать таблицу с сопоставлением запросов на sql и в MongoDB. Может быть очень полезна, если до этого опыта работы с MongoDB не было.


Занимательный факт:



В версии python 2.7, для чтения mbox файла, имеется метод mailbox.Unixmailbox, а в версии 3.1 его уже нету, и предлагается использовать mailbox.mbox


При этом, версия 2.7 для разбора писем используется построчное чтение, а в версии 3.1 сначала генерируется карта всех позиций (начало письма -> конец письма) и только после этого можно совершать навигацию по письмам.


С одной стороны крутое улучшение, с другой, на моем ноутбуке и почтовом ящике размером в несколько гигабайт, каждый запуск скрипта приводил к нагрузке CPU под 100% и ожиданию в несколько минут пока он соизволит распарсить весь файл и начнет работать с ним.

Python 3.1






def _generate_toc(self):
"""Generate key-to-(start, stop) table of contents."""
starts, stops = [], []
self._file.seek(0)
while True:
line_pos = self._file.tell()
line = self._file.readline()
if line.startswith('From '):
if len(stops) < len(starts):
stops.append(line_pos - len(os.linesep))
starts.append(line_pos)
elif not line:
stops.append(line_pos)
break
self._toc = dict(enumerate(zip(starts, stops)))
self._next_key = len(self._toc)
self._file_length = self._file.tell()

Python 2.7






def _search_start(self):
while 1:
pos = self.fp.tell()
line = self.fp.readline()
if not line:
raise EOFError
if line[:5] == 'From ' and self._isrealfromline(line):
self.fp.seek(pos)
return

def _search_end(self):
self.fp.readline() # Throw away header line
while 1:
pos = self.fp.tell()
line = self.fp.readline()
if not line:
return
if line[:5] == 'From ' and self._isrealfromline(line):
self.fp.seek(pos)
return


Если бы у меня был доступ к аналитике всего мира, мне было бы очень интересно посмотреть сколько энергии было потрачено в мире из-за этого изменения.


Финальный рывок



Используя механизмы разработанные во "втором подходе", мне удалось за несколько вечеров перебрать все оставшиеся письма в ящике.


Если посмотреть на диаграмму распределения, то видно что у нас есть сотня-другая отправителей от которых очень много сообщений (их я разобрал в "первом подходе") и огромный хвост не системных сообщений.


На масштабе не видно, там, в среднем, < 100 сообщений от отправителя и плавно убывает до 1.


fe277d36c7c0e4e8d99b01e2079d4575.png

На графике отображено распределение писем. Каждая точка — это уникальный отправитель. По шкале Y отображается количество писем от этого отправителя


Разгребание этого хвоста оказалось унылой механической работой, гораздо скучнее чем программировать. Фильтруешь по отправителю через поиск и принимаешь решение:

  • Не нужно. ⟶ Удалить
  • Нужно. Можно создать фильтр на будущее. ⟶ Создаем фильтр. Проставляем лейбл. Архивируем.
  • Нужно. Фильтр не требуется. ⟶ Проставляем лейбл. Архивируем.

Выводы



C момента завершения процесса, до написания поста прошел почти месяц, поэтому я могу поделиться взвешенными выводами.


  • Во-первых, теперь "Inbox" воспринимается совсем по другому. Все что в нем есть — это незавершенные дела. Если рассылка, то нужно её прочитать или отписаться или удалить. Если переписка — то значит надо довести её до логического завершения и отправить в архив.


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


  • В-третьих, пока я разбирался с этим всем, я отписался от огромного количества рассылок.


d0aa64771d5d1c5b3c1444b4f8a6c4fa.png


Занимательный факт:



По почтовым сообщениям можно видеть четкую корреляцию как месенджеры заходили в нашу жизнь. В начале 201x годов, у меня есть множество писем, где мы шарим друг-другу фотографии и различные файлы или ссылки, а потом они все пропадают, вероятно тогда мы узнали про WhatsApp.

Ха-ха. Лох.



Уже постфактум, наведя порядок в ящике и опубликовав скрипт я наткнулся на аналогичное решение написанное на Go. Самое печальное, что перед тем как переписывать на Python 3.x, я бегло посмотрел что есть в Go на тему парсинга mbox, но, в тот раз, не нашлось ничего вразумительного.


Про Zero Inbox начал писать пост в блог, по пути понял что в некоторых местах можно было сделать лучше.
Теперь не могу решиться: оставить как есть и добавить параграф «Ха-ха. Лох.» или сделать по нормальному, хоть и без цели, но и написать сразу об этом.

— Виктор Диктор ( )



Предположив что придется самому разбираться с чтением и парсингом mbox формата, я не стал изучать этот вопрос и сфокусировался на Python версии.


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





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



 
Сверху Снизу