До пуш-уведомлений удержать пользователей в мобильном приложении означало надеяться, что они сами его откроют. Email был единственным каналом повторного вовлечения. SMS - дорогим и навязчивым. Приложения были островами - после выхода пользователя не было способа достучаться до него, пока он не вернётся сам.
Apple представила Push Notification Service (APNS) вместе с iPhone OS 3.0 в июне 2009. Google ответила C2DM в 2010, затем Google Cloud Messaging (GCM) в 2012. Приложения внезапно могли достигать пользователей в любой момент.
Реализация была нетривиальной.
Как работал APNS
Apple Push Notification Service использовала постоянное TLS-соединение между вашим бэкенд-сервером и серверами Apple. Вы отправляли бинарный пейлоад; Apple доставляла его на устройство.
Устройство регистрируется:
// AppDelegate.m - регистрация в стиле iOS 5
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
UIRemoteNotificationTypeBadge |
UIRemoteNotificationTypeSound |
UIRemoteNotificationTypeAlert];
return YES;
}
// При успешной регистрации - отправляем токен на НАШ сервер
- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSString *token = [[deviceToken description]
stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]];
token = [token stringByReplacingOccurrencesOfString:@" " withString:@""];
[self registerTokenWithServer:token];
}
Сервер подключается к APNS:
APNS требовал постоянного бинарного TCP-соединения на порту 2195, аутентифицированного SSL-сертификатом от Apple. Никакого REST API. Чистый бинарный протокол:
class APNSSender {
public function send($deviceToken, $message, $badge = 0) {
$payload = json_encode([
'aps' => ['alert' => $message, 'badge' => $badge, 'sound' => 'default']
]);
$tokenBinary = pack('H*', $deviceToken);
$payloadLength = strlen($payload);
$notification = pack('C', 1) // Команда
. pack('N', time()) // Идентификатор
. pack('N', time() + 86400) // Истечение: 24 часа
. pack('n', 32) . $tokenBinary // Токен устройства
. pack('n', $payloadLength) . $payload; // Пейлоад
fwrite($this->socket, $notification);
}
}
APNS молча закрывал соединение при ошибке - некорректный токен, неправильный пейлоад - с кратким ответом об ошибке перед закрытием. Если не читать ответ об ошибке сразу после каждой записи - пропустишь его. Многие реализации отправляли тысячи уведомлений, прежде чем обнаруживали, что соединение умерло ещё на первом плохом токене.
Android: GCM был другим
Google Cloud Messaging использовал REST API. Намного проще со стороны сервера:
function sendGCM($registrationId, $title, $message) {
$payload = json_encode([
'registration_ids' => [$registrationId],
'data' => ['title' => $title, 'message' => $message],
'time_to_live' => 86400,
]);
$ch = curl_init('https://android.googleapis.com/gcm/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Authorization: key=' . $this->apiKey, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
]);
$response = json_decode(curl_exec($ch), true);
// GCM возвращает canonical_id если токен обновился - обновляем в БД
return $response;
}
Что пользователи реально открывали
Данные вовлечённости за первый год работы с пуш-уведомлениями:
| Тип сообщения | Процент открытий |
|---|---|
| «Ознакомьтесь с новинками!» | 1.8% |
| «Ваш заказ отправлен» | 34% |
| «[Имя], вам новое сообщение от [Имя]» | 41% |
| «Флеш-распродажа: скидка 20% на 2 часа» | 12% |
| Еженедельный дайджест | 6% |
Транзакционные уведомления (заказ отправлен, новое личное сообщение) имели на порядок более высокий процент открытий, чем рекламные рассылки. Это было очевидно в ретроспективе, но не до просмотра данных.
Урок: пуш-уведомления - это привилегия, не маркетинговый канал. Пользователи, чувствующие спам, отключали уведомления; iOS требовала явного разрешения. Приложения, злоупотреблявшие рассылками, видели, как доля разрешений падала до 40-50%. Приложения, использующие уведомления только для действительно важных личных событий, сохраняли долю разрешений выше 80%.
Как изменилось со временем
Firebase Cloud Messaging (FCM) объединил iOS и Android под единым API в 2016 году. Apple заменила бинарный протокол HTTP/2 API в том же году. Сложность поддержки двух отдельных пайплайнов, двух процессов управления сертификатами и двух задач опроса feedback-сервиса схлопнулась в один REST-эндпоинт.
Лучшее напоминание о 2012 годе: действительно сложные инфраструктурные задачи в итоге стандартизируются. Цените сложность, пока она существует - она учит вас важным вещам. Потом цените, когда кто-то строит абстракцию, делающую её неактуальной.