Что нам стоит сервис email-маркетинга построить? Взгляд изнутри, часть первая

в 4:45, , рубрики: celery, django, email, html, javascript, jquery, knockoutjs, python, Анализ и проектирование систем, Программирование, Разработка веб-сайтов

Насколько сложно построить полноценный сервис email-маркетинга? Что для этого нужно предусмотреть? Какие подводные камни могут встретиться на пути пытливых умов разработчиков?

Что нам стоит сервис email-маркетинга построить? Взгляд изнутри, часть первая - 1

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

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

Кратко о себе

Я пишу на Python вот уже 5 лет, в основном использую Django, PostgreSQL, умею готовить JavaScript на уровне jQuery + KnockoutJS. В свободное от основной работы время занимаюсь фрилансом и собственными интернет-проектами, об одном из которых сейчас и планирую рассказать. Занимаюсь я этим проектом уже около года.

Цель проекта

В самом начале мною была поставлена достаточно простая цель — создать работающее решение для отправки транзакционных писем и email-рассылок с функциями отслеживания открытий, переходов, невозможности доставки письма, жалоб на спам. Использовать я это решение планировал в других своих проектах, поскольку Яндекс ПДД (почта для домена), которую я использовал до этого, такими функциями не обладала, а они были нужны.

Тогда еще не шла речь о том, чтобы дать это решение в виде SaaS всем пользователям в Интернете.

Задачи

  • Понять, как работает отслеживание событий в email-рассылках, разобраться с трекингом.
  • Придумать решение, которое будет работать под средними нагрузками (2-3 миллиона писем в месяц). Почему именно 2-3 миллиона? Я считаю, что такой объем необходим, чтобы окупать такой проект (затрачиваемое время + материальные ресурсы типа серверов).
  • Реализовать удобный интерфейс для аналитики массовых и транзакционных рассылок.

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

Технологии

Использовать я решил те технологии, которые знаю — Python, Django, PostgreSQL, KnockoutJS, LESS, py.test.

Дополнительно в процессе работы над проектом я неплохо разобрался в Celery и микросервисной архитектуре.

На этом я предлагаю закончить вступительную часть и перейти к самому интересному — практике.

Как работает трекинг email-сообщений?

Что нам стоит сервис email-маркетинга построить? Взгляд изнутри, часть первая - 2

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

Получить ответы на эти вопросы вы можете только с помощью трекинга или систем типа Яндекс.Метрики (ну или спросив своих получателей лично).

Отслеживание открытий писем

Сегодня стандартным подходом для отслеживания открытий писем является внедрение специального пикселя в письмо — вы можете увидеть этот пиксель в большей части рекламных писем в вашем почтовом ящике, если посмотрите исходники письма. Он может выглядеть примерно так:

<img src="http://api.mailhandler.ru/message/track/<UNIQUE_EMAIL_ID>/OPENED/" width="1px" height="1px" border="0"/>

Понятно, что при запросе на URL, указанный в атрибуте src изображения, должно происходить добавление события, означающего, что письмо с id равным UNIQUE_EMAIL_ID было открыто.

Однако не всё так просто. Очень часто в src изображения указывают URL, ведущий на какой-либо php-скрипт и не думают о том, что почтовый сервис очень хочет получить в ответ валидные для изображения заголовки, а так же само изображение. Если почтовый сервис по этой причине разочаровывается в вашем пикселе, он просто вырежет его из письма и вы не узнаете о том, открыл ваш адресат письмо или нет.

Для того, чтобы этого не произошло, следует добавить корректные заголовки ответа и отдать валидное изображение клиенту. Реализация на Django Rest Framework может выглядеть примерно так:

class TrackMessageView(APIView):
    renderer_classes = [JPEGRenderer]

    @property
    def pixel(self):
        return open(os.path.join(settings.STATIC_ROOT, 'site/img/pixel.jpg'), 'rb')

    def get(self, request, *args, **kwargs):
        manager = BaseManager()
        message = manager.get_message_by_unique_id(self.kwargs['unique_id'])
        if message:
            manager.track_message(message)
            return Response(self.pixel.read(), status=201)
        return Response(status=404)

Отслеживание переходов по ссылкам в письме

Что нам стоит сервис email-маркетинга построить? Взгляд изнутри, часть первая - 3

Думаю, у пытливого читателя не должно возникнуть проблем с реализацией такого типа трекинга. В общем и целом — каждая ссылка в письме заменяется на ссылку через специальный сервис перенаправления, который создает событие типа «Переход по ссылке». Дополнительно можно к каждой ссылке добавлять уникальный идентификатор — тогда вы сможете реализовать «тепловую карту» письма. Это очень полезная функция, например, для А/Б тестирования.

Реализация на Python выглядит достаточно просто:

REDIRECT_URL_TEMPLATE = '%s/message/redirect/%s/'
HREF_REGEXP = r'(?<=href=("|'))(http|https)([^"']+)(?=("|'))'
...

def replace_links(message):
      redirect_url = REDIRECT_URL_TEMPLATE % (settings.API_URL, message.unique_id)
      message.html_body = re.sub(HREF_REGEXP, r'%s?next=23' % redirect_url, message.html_body)
      ...

Отслеживание невозможности доставки писем

А вот с этим все намного интереснее.

Каждый раз, когда почтовый сервер не может доставить ваше письмо, в ответ на адрес отправителя уходит отчет о невозможности доставки с описанием причины (иногда подробным, иногда — так себе). Для обработки этих входящих писем я использовал подход, который заключается в пробросе входящего письма на Python скрипт обработчика через /etc/aliases.

Пример кусочка письма для анализа:

Final-Recipient: rfc822; ****@****.ru
Original-Recipient: rfc822; ****@****.ru
Action: failed
Status: 4.4.1
Diagnostic-Code: X-Postfix; connect to ****.ru[xx.xx.xxx.xxx]:25: Connection refused

Сам скрипт пытается более-менее интеллектуально понять причину невозможности доставки письма и создает событие Soft-Bounce (письмо в данный момент не может быть доставлено, но вы сможете попробовать еще разок) или Hard-Bounce (письмо не будет доставлено никогда, например потому что ящик не существует).

Тут важно сделать небольшую ремарку о том, как собственно нужно реагировать на такие события согласно правилам почтовых сервисов типа Mail.ru, Yandex и др.

сервисы, осуществляющие рассылки на основе подписки, должны безусловно удалять из базы подписчиков или принимать меры по приостановке рассылок на адреса, которые генерируют ошибку протокола SMTP: 550 user not found (отслеживание валидности базы получателей — необходимое условие для поддержания положительной репутации рассыльщика);

» Ссылка на список правил

Таким образом, мне необходимо было предусмотреть «выключение» подписчиков, на адреса которых невозможно доставить почту. В итоге я пришел к тому, что выключаю подписчика из всех списков подписчиков на сервисе.

Ну вот, с трекингом вроде бы разобрались.

Немного статистики

В данный момент через мой сервис отправляется около 150 000 писем в месяц. Много это, или мало? Наверное мало, учитывая объемы, которые я себе задал в рамках обозначенных задач.

Из них:

  • 20% — открыты (это достаточно большой процент, на самом деле, спасибо транзакционной почте)
  • 13% — переходы по ссылкам
  • 9% — Hard/Soft bounce

P.S.

В следующих статьях я расскажу о том, как и чем я обрабатываю эти данные, расскажу о тонкостях использования celery в подобных проектах, а так же остановлюсь на том, что я планирую делать с этим сервисом дальше.

Спасибо за внимание!

Автор: astrikovd

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js