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

Привет! Меня зовут Олег Борискин. Я помогаю разрабатывать корпоративный мессенджер We.Teams в компании Webinar Group в качестве продакт-менеджера. Одна из моих задач — сделать так, чтобы уведомления в We.Teams исправно приходили, показывали важные события и синхронизировались между десктопом, вебом и приложениями на телефонах. 

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

Начало проработки логики уведомлений

У нас уже был готов счетчик новых сообщений внутри We.Teams: бейджи показывали количество непрочитанных сообщений и меняли цвет в зависимости от того, включены у пользователя уведомления или нет. Мне предстояло продумать логику отправки пуш-уведомлений на десктопе, в мобильных приложениях и веб-версии и связать ее с бейджами.

Система, которую нужно было продумать и связать на всех платформах
Система, которую нужно было продумать и связать на всех платформах

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

Пример уведомления на вебе: можно настроить вывод информации, но нельзя убрать кнопки или изменить форму самого уведомления
Пример уведомления на вебе: можно настроить вывод информации, но нельзя убрать кнопки или изменить форму самого уведомления

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

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

  • продумать все теории и потом приступить к практике;

  • начать сразу с практики — выкатить функционал, постепенно тестировать и собирать фидбек от пользователей, чтобы разработать свою уникальную систему в несколько итераций. 

Решили идти по второму пути.

Первый этап: шлём пуш-уведомления всегда

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

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

С какими полями мы работали?

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

  • Body — текст пуша. В нашем случае само сообщение в чате мессенджера.

  • Icon — иконка пуша, аватарка отправителя либо логотип сервиса.

Кроме этого, в пуш-уведомлениях есть еще поле subtitle, которое работает только на iOS, и кнопки, которые можно добавить на веб-уведомления с Chrome на Windows и у нативных мобильных приложений. Что касается ограничений на символы, то на каждой платформе свои требования. Мы пользуемся универсальной тактикой: обрезали Body после 150 символов, чтобы лаконично отображать в вебе, десктопе и мобильных приложениях. 

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

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

Все эти моменты я постарался учесть в схеме.

Схема, которая описывает логику отправки уведомлений в We.Teams на первом этапе разработки
Схема, которая описывает логику отправки уведомлений в We.Teams на первом этапе разработки

Сразу пример того, как базовый функционал рождает новые фичи. В We.Teams есть есть треды (комментарии под сообщениями), из которых уведомления приходят в трех случаях: 

  • пользователь написал сообщение, под которым оставили комментарии;

  • пользователя упомянули в треде;

  • пользователь прокомментировал сообщение и после этого получил новые сообщения.

Кнопка отключения уведомлений работает везде — даже в тредах, которые никак не касаются пользователя: можно отписаться от уведомлений в беседах, в которых и так не участвуешь. Мы не закладывали такой сценарий взаимодействия, но затем поняли, что пользователю важно не само действие «выключить уведомления», а статус «подписан на тред». Тогда он может быстро разобраться, будут приходить пуши или нет, а также отключить их или подписаться, если в этом есть необходимость.

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

Как это работает: техническое решение

У каждой платформы есть свой сервис пуш-уведомлений, на который можно присылать уведомления из приложения. Так, на iOS и macOS работает APNS, а на Windows — WNS. Взаимодействие с ними требовало отдельного подхода к разработке, поэтому мы обратились к универсальному Firebase Cloud Messaging, который изначально работал только с продуктами Google, но с 2019 года перешел на кросс-платформу. 

На этам этапе мы взаимодействуем только с ним, но вся инфраструктура для новых платформ готова — можем внедрить на сервер за несколько дней. 

В работе наших пуш-уведомлений участвуют три компонента: клиент, бэкэнд (микросервис уведомлений написанный на Go) и сервер FCM.

Уведомления, приходящие на клиента, включают в себя два поля: 

  • Notification — есть определенная структура, придерживаясь которой, можно отображать пуши без дополнительной обработки на стороне фронта или других платформ;

  • Data — нет определенной структуры, туда можно передать данные в свободной форме или структуре, и потом обработать на фронте. 

Оба поля комбинируются так, чтобы через Notification отправлять информацию, которая непосредственно отобразится в пуше, а через Data — вспомогательную информацию для последующей обработки. У нас в мессенджере через Data отправляется идентифицирующая информация о сообщении (ID сообщения, ID чата и другие), чтобы при клике на пуш мы понимали, куда нужно перейти.

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

Отправка пуш-уведомлений — это многоступенчатый процесс. Цепочка пуша длинная — на каждом этапе может произойти проблема. Например, вам написали в треде. Что в этот момент происходит на бэкенде: 

  1. Сначала идет обработка нового сообщения, оно сохраняется в базу данных и кладется в очередь событий для дальнейшей обработки.

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

  3. Готовое событие получает микросервис уведомлений. Он занимается только форматом сообщений для конкретных способов доставки и собственно отправкой.

Скорость отправки пуш-уведомления зависит от инфраструктуры. С увеличением пользователей есть риск снижения скорости доставки. Допустим, в какой-то момент в чате будет 1000 пользователей — если я напишу сообщение, то для системы это будет одно событие на 1000 адресатов, в котором нужно решить, отправлять пуш конкретному пользователю или нет. Очередь может быть большой — всё это нужно прислать без задержек, даже когда пользователей станет в разы больше, чем есть сейчас. Поэтому уже сейчас мы закладываем ресурс на оборудование и доработки.

Второй этап: шлём пуш-уведомления при определенных условиях

После тестирования концепция «лучше отправить, чем не отправить» сменилась на «лишний раз ничего не отправлять, но всегда доносить важное». Уведомления важны, но инфошум не нужен. 

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

При отслеживании действия пользователя ориентируемся на активность, а не онлайн-статус. Теперь уведомления больше не приходят, если открыто то же окно чата или если вкладка была закрыта. 

Изначально в логике было заложено, что онлайн-статус влияет на отправку пушей. Мы отправляли уведомления, даже если человек закрыл вкладку — онлайн-статус длится у нас 3 минуты. Сначала думали, что это клево, но потом поняли, что нет.

  • Теряется ощущение контроля. Человек закрыл вкладку, но ему всё равно приходят пуши — это странно.

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

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

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

Встроили пуш-уведомления в систему навигации. Когда переходишь по пушу, то ожидаешь увидеть в мессенджере то сообщение, на которое нажал. 

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

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

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

При клике по кнопке «вниз» мы начинаем подгрузку, статус которой показываем под названием канала или чата
При клике по кнопке «вниз» мы начинаем подгрузку, статус которой показываем под названием канала или чата

Второй момент — обновления бейдж-каунтера. Раньше при переходе в чат с 99 сообщениями мы делали все эти сообщения прочитанными. Теперь, как только пользователь зашел на сайт, с бэка приходит информация о том, сколько непрочитанных сообщений у него в каждом чате. Далее, когда он читает сообщения, отправляется запрос на бэк и приходит ответ — за счет этого пересчитываем каунтеры.

Разделили уведомления на пользовательские и системные. Каждое событие должно обрабатываться в зависимости от контекста его назначения. Важно понимать назначение уведомления: что вы пытаетесь им донести. Уведомления о сообщении и начале звонка — это два разных типа событий. Звонок более редкое событие и чаще более важное. Ему нужна отдельная форма, звук, логика.

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

Также есть особенности интеграции. Webinar Meetings, внутренний продукт, с которым у нас интеграция, не заканчивает встречу самостоятельно — участникам нужно ее закрыть. Получается, если пользователь не завершил мероприятие и вышел, то будет временной промежуток между реальным концом беседы и завершением звонка в Webinar Meetings.

Доработали дизайн. На втором этапе пуши стали корректно отображать имя и фамилию в поле Title и содержание — в Body. Мы научились подтягивать аватарки в веб-пуши на Windows.

Текст в Body мы обрезаем теперь по словам, но итоговая строка не длиннее прежнего лимита в 150 символов. Делаем это на сервере, но не каждый клиент может отображать такое количество символов: зависит от размера экрана, шрифтов и других параметров. В любом случае, сколько знаков отображать, теперь решает клиент: 80, 100, 120. Всё, что выше лимита, будет сопровождаться троеточием.  

На втором этапе я пользовался сервисами, чтобы смотреть, как выглядят пуши с дизайнерской точки зрения. Например, есть Push Notification Preview Tool — можно посмотреть пуши сразу на всех популярных платформах, но не показывает родной (нативный) вид пуша, зато помогает подобрать формулировки. Альтернативный вариант — Push Notification Preview Tool, но в бесплатной версии можно посмотреть, только как выглядят пуши в веб-браузере.

Добавили плашку с призывом включить уведомления. В некоторых браузерах, например в Safari, по умолчанию уведомления выключены. Важно соблюсти баланс в призывах, дать доступы, поэтому показываем плашку «Разрешите уведомления», когда пользователь только что авторизовался и еще не успел дать доступ. На мобильных платформах это решается нативным модальным окном.

Лайфхак: для проверки доступов я использовал команду “new Notification('Привет!')” в консоли. Если пуш не отобразился, значит, не доступов на уровне вкладки или браузера. 

Планы на будущее

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

Работа продолжается. Вот какие задачи решаем прямо сейчас.

  • Делаем настройку всех уведомлений на уровне продукта из одного центра уведомлений. Хотим дать возможность пользователям настроить отправку уведомлений под свои задачи. 

  • Прорабатываем звуковое сопровождение и вибрацию. Звуки и вибрации на телефоне должны воспроизводиться по очереди и не пересекаться, чтобы пользователь не испытывал дискомфорт. Прорабатываем понятие очереди и отрезка времени, в котором может звучать уведомление.

  • Разрабатываем механику для сайлент-пушей. Когда пользователь прочитал сообщение или сообщение было изменено, то пуш должен либо уйти, либо измениться. Особенно если у вас несколько платформ, как в нашем случае. 

Если возникли вопросы, то задавайте их в комментариях — с удовольствием отвечу. 

Комментарии (4)


  1. jiamak
    03.08.2023 10:08

    для проверки доступов я использовал команду “new Notification('Привет!')” в консоли.

    Странное решение, может лучше

    if (!("Notification" in window)){}


    1. Boriskinooo Автор
      03.08.2023 10:08

      Привет!
      В данном случае это способ быстрой проверки -- ходят пуши или нет


      1. jiamak
        03.08.2023 10:08

        Тогда согласен, не так понял текст предложения.


  1. dom1n1k
    03.08.2023 10:08

    Схема ну очень странная.


    Самое бросающееся в глаза: почему проверки "пользователь состоит в чате" и "в чате отключены уведомления" проверяются раньше чем "в чате произошло событие"? Если событие не триггернулось, то всю эту цепочку проверок даже запускать смысла нет, разве не так?


    И почему надо проверять состоит ли человек в чате, как будто событие произошло у человека, а не в чате? Не логичнее просто проитерировать список участников чата, в котором что-то произошло?