Я работаю в компании igooods, которая занимается сборкой продуктов в крупнейших гипермаркетах страны, а также их доставкой нашим довольным клиентам. Я работаю в команде Fulfillment, которая отвечает за разработку бэкенда для приложений наших сборщиков, а также курьеров. Мы периодически проводим время в уютных чатах и делимся фотографиями наших домашних питомцев, а также отправляем push-уведомления в наши приложения.  Push-уведомления об изменении состояния заказа, чтобы своевременно уведомлять наших клиентов. Новостные push-уведомления, которыми ведает отдел маркетинга. В отличие от SMS push-уведомления — это бесплатно или почти бесплатно. Push-уведомления для наших курьеров, обеспечивающие дальнейшую коммуникацию с нашими линейными сотрудниками. Мобильное приложение получает уникальный push-токен для устройства.  Передает его на наш бэкенд, где мы матчим push-токен с данными клиента.  Телефон клиента засыпает. Он не активен, однако по-прежнему слушает сигналы сотовой сети и на нем включен мобильный интернет.  Чтобы отправить push-уведомление клиенту, бэкенд отправляет её на сервер владельца платформы, откуда push-уведомление будет перенаправлено на целевое устройство. Устройство спит и неактивно, однако активен baseband processor. Именно baseband processor циклически слушает сигнал сотовой сети, ожидая получить и обработать пакеты для устройства, и будит телефон. В одном из таких UDP-пакетов приходит наше push-уведомление.  В дело вступает наше приложение и принимает push-уведомление, обрабатывает его и отображает с минимум информации.  Минимум информации — это заголовок push-уведомления, а также тело пуша. Push-уведомления можно кастомизировать, добавив в них картинки или звуковое сопровождение, зашив переходы по внешним и внутренним ссылкам, добавив теги и разные другие функции, которые хорошо описаны в документации к push-уведомлениям. По клику на такую нотификацию загружается наше приложение и отображается нужный для пользователя экран. Push, как и SMS, может потеряться. Нет гарантии доставки, а, тем более, гарантии последовательной доставки push-уведомлений, что может быть критически важным именно для вашего бизнеса.  Потери могут случиться во время пиковой нагрузки. Например, отдел маркетинга совершил массовую рассылку push-уведомлений клиентам, но они начали теряться. При разработке необходимо знать и учитывать, что ваше приложение и выстроенная инфраструктура для отправки push-уведомлений должны самостоятельно отслеживать такие потери. Платформ больше одной, и их количество лишь растет. Например, после того, как Huawei отделился от Google, чтобы отправлять нотификации на устройства Huawei, необходимо интегрироваться с их собственным сервисом для отправки push-уведомлений.  Существует довольно обширный рынок агрегаторов, которые предлагают вам писать меньше кода, т.е. получить интеграцию "из коробки". Firebase Firebase, а именно Firebase Cloud Messaging, — бесплатный сервис системы Firebase, который работает с push-уведомлениями. Сервис покрывает  платформы Android и iOS и имеет под собой ряд интеграций, например, Google Analytics, Google Metrics, Zapier. Благодаря Firebase Cloud Messaging push-уведомления можно отправлять:  индивидуально получателю по его уникальному push-токену,  батчем (batch request),  в топики, когда все устройства, подписанные на данный топик, принимают push-уведомления и отображают их. OneSignal OneSignal имеет бесплатные (для малых объемов) и платные тарифы, покрывает платформы Android и iOS и поможет покрыть интеграцию с Huawei. OneSignal имеет значительно больше интеграций под капотом:  Google Analytics Zapier Mixpanel Amplitude Segment.com Adobe Audience Благодаря OneSignal push-уведомления можно отправлять: индивидуально получателю,  в сегменты получателей, т.е. выборке ваших клиентов, по внешнему ID, т.е. можно связать своих клиентов и зарегистрировать их в системе, связав по внешнему ID, который впоследствии будет использоваться для отправки push-уведомлений. Pusher Сервис Pusher имеет бесплатные и платные тарифы, покрывает Android и iOS. Ably Сервис Ably, частью которого является отправка push-уведомлений, имеет бесплатные и платные тарифы, покрывает платформы Android и iOS. PushBots PushBots имеет только платные тарифы, однако позволяет покрывать в том числе и устройства с Huawei. Мы начинали с OneSignal. Компоненты нашей системы выглядят следующим образом: Монолит, который является продюсером событий в брокер сообщений.  Кластер Kubernetes, в котором крутятся поды с сервисами для отправки push-уведомлений, являющиеся консьюмерами событий из брокера сообщений. Далее совершаются сетевые запросы к API OneSignal.  Как это работает Нам необходим gem и аккаунт в OneSignal. Нам также необходимо получить уникальный ключ доступа для того, чтобы совершать запросы к API OneSignal. Именно этот ключ мы и устанавливаем в настройках проекта. Поскольку наш сервис является консьюмером событий из брокера сообщений, мы такие события принимаем, валидируем их схемы. Если события корректны, мы инициируем дальнейшую отправку push-уведомлений, после чего сообщаем что событие было успешно принято и обработано. Функцией операции по отправке push-уведомления является создание полезной нагрузки push-уведомления, а также постановка в очередь отложенной задачи для ее дальнейшего выполнения. Если посмотреть на payload push-уведомления, он прямо отражает тело последующего запроса к API OneSignal. Конечной точкой является выполнение отложенной задачи и для бэкграунд-процессинга у нас используется gem que. Функция отложенной задачи заключается в том, чтобы совершить POST-запрос к API OneSignal и отправить нотификацию конечному получателю, а, если при выполнении запроса произойдут какие-либо ошибки, корректно обработать их. Пользовательские рассылки у нас бывают двух типов: e-mail push-уведомления В случае push-уведомлений, агрегаторы являются точкой стыка для интеграции маркетинговых инструментов. Например, маркетологи igooods в своей работе используют сервис Altcraft. В админке они могут создавать шаблоны push-уведомлений и позже использовать их в календаре, выбирая дату, время и выборку пользователей для отправки push-уведомлений.  Кроме того, маркетологи могут создавать сценарии и цепочки отправки push-уведомлений, опираясь на те или иные показатели, и позже следить за конверсией и различными аналитическими данными.  Однако всегда присутствует какой-то бизнес-нюанс. Мы использовали e-mail рассылки, но не использовали рассылки push-уведомлений. Именно с такой задачей маркетологи пришли в отдел бэкенд-разработки. Необходимо было провести рисерч и выполнить дальнейшую интеграцию, чтобы отдел маркетинга мог рассылать новостные массовые push-уведомления. Нас ждал неприятный сюрприз. Altcraft не обеспечивает интеграцию с OneSignal, поэтому нам предстояла смена агрегатора. Соответственно, после проведенного рисерча, мы собрали бизнес-требования, поставили бизнес-задачи, написали ADR и описание новой системы с учетом последующей разработки функционала отправки push-уведомлений уже и для линейных сотрудников. Мы приняли решение переехать на Firebase Cloud Messaging, то есть сменить агрегатор. Результаты переезда позволили нам сделать определенные выводы: В процессе смены агрегатора push-токены придется собирать заново. Текущий агрегатор выдает push-токены собственной системы, а не push-токены интеграций, которые имеются у него "под капотом".  Обновление давно не запускавшихся приложений не сыграет вам на руку, поскольку новые SDK в мобильных билдах не сможет принять и обработать чужой пуш. В момент перехода придется поддерживать несколько SDK в мобильных приложениях. Это может стать проблемой, если вам об этом не известно. Решить подобную уже сложившуюся проблему можно, только если пользователь (так называемый lost-клиент) запустит приложение на телефоне, и в этот момент вы получите его уникальный push-токен и наладите, возможно, единственный канал коммуникации с ним. В момент интеграции и разработки мы выяснили один китайский нюанс — FCM не имеет интеграции с Huawei, и мы не можем отправлять пуши на устройства Huawei.  ПРИМЕЧАНИЕ. В момент разработки системы push-уведомлений наше клиентское приложение не было представлено в Huawei AppGallery. Поэтому для нас это не являлось проблемой, а для вас может стать. Этот момент стоит учитывать. FCM предоставляет довольно разумные лимиты даже в бесплатном аккаунте — 18 миллионов пушей в минуту. Однако необходимо учитывать, что это лимит на 30 возможных зарегистрированных уникальных приложений в вашем аккаунте. Кроме того, у Google существуют определенные правила троттлинга, с которыми стоит ознакомиться. Для интеграции мы изучили различные гемы. Таких гемов немного, и их реализация довольно проста. Поэтому мы решили не использовать сторонние решения и написать реализацию внутри нашей системы.  В результате она выглядит следующим образом: Итак, нам необходимо реализовать клиент для отправки запросов к Firebase Cloud Messaging. Для начала нужно реализовать клиент авторизации, который будет возвращать хедер для последующих запросов, а точнее актуальный токен доступа. Однако этот токен должен принадлежать именно тому проекту, в который вы собираетесь отправить push-уведомление. Соответственно, у нас есть список из проектов (до 30 штук). Внутри приложения в Ruby-коде данный проект будет выглядеть как обычный PORO-объект, который внутри содержит уникальные ключи доступа (credentials) конкретно для выбранного проекта.  Итак, как это реализовано внутри? Нам нужно реализовать базовый класс Credentials::Base, основной ответственностью которого является проверка токена на валидность, если токен уже сохранен в нашем приложении. В дочернем классе, который описывает выбранный проект, мы реализуем метод credentials_hash, и он прямо отражает структуру и содержимое JSON-файла с уникальными ключами доступа для вашего проекта, который (файл) вы получаете в админке Firebase при регистрации проекта.  Теперь мы имеем возможность получать креды проекта из клиента авторизации нашей системы указав его название. Подключаем гем googleauth и пишем клиент авторизации. Клиент должен вернуть токен доступа, если такой токен сохранен в системе. Если же его нет или токен не валиден, клиент должен выполнить сетевой запрос к googleauth, получить новые данные для доступа и сохранить их. Теперь мы можем выполнять запросы к Google API, получать актуальный токен доступа и использовать его. Такой токен выдается сроком действия 59 минут. Его нужно сохранять и использовать для последующих запросов до момента инвалидации. Поскольку наш сервис для отправки push-уведомлений развернут в Kubernetes, поды скейлятся и мы всегда имеем более одного инстанса работающего приложения, нам возможно требуется использовать стороннюю зависимость для кэширования, например Redis. Впоследствии шарить данные между всеми инстансами нашего приложения: Однако в ходе разработки мы выяснили, что каждый последующий запрошенный токен доступа не инвалидирует предыдущий, который выдается на 59 минут. Именно поэтому можно не использовать сторонние зависимости, и сохранять полученные токены в рамках каждого инстанса ваших приложений. Соответственно, теперь мы можем написать клиент для запросов к API Firebase Cloud Messaging: Мы используем гем faraday, faraday_middleware, создаем Faraday::Connection,  передавая опции для последующего запроса: После этого мы можем реализовать метод для отправки одной push-уведомления уникальному получателю: В примере продемонстрировано, как используется auth_client для получения нужного токена доступа для выбранного проекта.  После выполнения запроса мы обрабатываем статус ответа. Если статус 401,  инвалидируем токен выданный для данного проекта, чтобы повторный сетевой запрос выполнился успешно: Если произошли какие-либо ошибки или ошибки соединения, мы их корректно обрабатываем: В новой версии (v1) API Firebase Cloud Messaging не отражен способ отправки push-уведомлений более чем одному получателю. Такая возможность описана только в устаревшей (legacy) версии API Firebase Cloud Messaging. Новая версия (v1) предполагает отправку batch requests. Batch request — это выполнение одного сетевого запроса, в теле которого вы передаете склеенные подзапросы. Согласно документации FCM, можно включать до 500 таких подзапросов:  При использовании утилиты curl можно передать такие подзапросы в файле, указать нужный хедер ({ Content-Type: multipart/mixed }), а также что именно является разделителем (в данном случае — subrequest_boundary), и отправить запрос на /batch эндпоинт. То же самое можно написать, используя гем faraday:  Мы используем faraday, faraday/multipart, указываем обозначенные ранее хедеры и создаем объект Faraday::Connection, передавая в опциях для последующего запроса опцию multipart. Далее мы выполняем один сетевой запрос, получаем ответ. Тело ответа выглядит следующим образом: Ответы приходят на каждый подзапрос, содержащийся в нашем запросе. Эти ответы необходимо корректно распарсить и обработать в нашем Ruby-коде. При смене агрегатора нужно обратить внимание на платформы, которые он покрывает, а так же наличие интеграций с маркетинговыми и аналитическими инструментами "под капотом". Если ваш кейс похож на наш, то его минусом является отсутствие покрытия девайсов Huawei; а при отдельной интеграции с APNs придется собирать токены снова. Систему отправки push-уведомлений рекомендуется вынести в отдельный сервис, если это позволяет функционал системы. Если используете гем для интеграции с Firebase Cloud Messaging, проверьте его исходный код. Дополнительные ссылки: Видео "Что узнали рубисты igooods, делая push-уведомления" Слайды доклада