Прямой ответ
Предпочтения уведомлений определяют, какие сообщения и какими каналами получает пользователь. Модель строится на паре «тип события — канал», а не на одном общем переключателе.
CREATE TABLE notification_preferences (
user_id bigint NOT NULL,
event_type text NOT NULL,
channel text NOT NULL,
is_enabled boolean NOT NULL,
updated_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (user_id, event_type, channel)
);
Проверка перед отправкой:
SELECT is_enabled FROM notification_preferences
WHERE user_id = $1 AND event_type = $2 AND channel = $3;
Отсутствие записи трактуется по значению по умолчанию, которое определяется типом события.
| Тип события | Значение по умолчанию | Отключение пользователем |
|---|---|---|
| Транзакционное (заказ, платёж) | Включено | Не допускается |
| Безопасность (вход, смена пароля) | Включено | Не допускается |
| Продуктовое (новости, акции) | По согласию | Допускается |
| Системное (обслуживание) | Включено | Допускается частично |
Транзакционные сообщения и уведомления о безопасности не отключаются, поскольку относятся к выполнению договора и защите учётной записи. Продуктовые сообщения требуют согласия и отключаются в один шаг.
Отдельный элемент — согласие на маркетинговые сообщения. Согласие фиксируется с датой и способом получения, поскольку требуется подтверждение основания отправки; о требованиях к отказу — в статье про unsubscribe.
Значения по умолчанию и наследование настроек
Хранить явную запись для каждой пары «событие — канал» избыточно: у пользователя десятки типов событий и несколько каналов. Практика — значение по умолчанию на тип события и явная запись только для отклонений.
Три уровня определения настройки.
Значение по умолчанию на тип события. Определяется характером события: транзакционные включены, продуктовые требуют согласия.
Явная настройка пользователя. Отклонение от значения по умолчанию сохраняется отдельной записью.
Общий переключатель канала. Пользователь отключает канал целиком, например SMS, что перекрывает настройки по типам событий, кроме обязательных.
Практическое следствие: проверка перед отправкой выполняется по трём уровням, и обязательные сообщения отправляются независимо от общего переключателя канала.
Отдельный вопрос — применение настройки к уже созданным уведомлениям. Изменение предпочтения влияет на новые уведомления, а созданные ранее отправляются по настройке на момент создания. Обратное поведение приводит к тому, что отключение настройки не отменяет уже поставленные в очередь сообщения, и пользователь получает их после изменения.
Изменения предпочтений фиксируются в журнале; о журналировании действий — в статье про журнал действий пользователя.
Выбор канала при нескольких доступных определяется резервированием; о выборе канала — в статье про каналы уведомлений.
Тихие часы и частота уведомлений
Предпочтения по типу события не покрывают два ограничения, которые влияют на восприятие уведомлений сильнее, чем состав сообщений.
Тихие часы. Сообщение, отправленное ночью по местному времени получателя, воспринимается как навязчивое независимо от содержания. Ограничение задаётся интервалом, вне которого некритичные уведомления откладываются до его окончания.
Ограничение частоты. Поток уведомлений за короткий интервал приводит к отключению канала целиком, поэтому число некритичных сообщений на пользователя в единицу времени ограничивается.
Практическое следствие: проверка перед отправкой включает не только разрешение по типу события, но и соответствие ограничениям времени и частоты. Отложенное сообщение сохраняет время отправки и уходит после окончания тихих часов.
Критичные сообщения исключаются из обоих ограничений, поскольку задержка уведомления о безопасности снижает его ценность. Перечень исключений определяется характером события, а не настраивается произвольно.
Отдельный аспект — определение местного времени. Часовой пояс пользователя хранится явно, поскольку вычисление по адресу отправки ненадёжно. Отсутствие сохранённого часового пояса трактуется как значение по умолчанию, и некритичные уведомления в таком случае отправляются без учёта времени.
Практика: реализация предпочтений уведомлений
1. Разделяйте типы событий, а не только каналы. Один переключатель на все сообщения лишает пользователя выбора и приводит к полному отключению.
2. Выделяйте обязательные сообщения. Транзакционные уведомления и уведомления о безопасности не отключаются, и это отражается в интерфейсе.
3. Храните явные отклонения от значения по умолчанию. Запись для каждой пары избыточна, поэтому сохраняется только отличие.
4. Требуйте согласия на продуктовые сообщения. Отправка без согласия нарушает требование, а фиксация согласия подтверждает основание.
5. Проверяйте предпочтение перед отправкой, а не при создании. Настройка могла измениться между созданием уведомления и отправкой.
6. Применяйте изменение настройки к новым уведомлениям. Уже созданные сообщения отправляются по настройке на момент создания.
7. Фиксируйте изменения предпочтений в журнале. Разбор обращения требует истории изменений, а не только текущего состояния.
Рекомендации:
- Не скрывайте обязательные уведомления в общем списке. Пользователь должен видеть, какие сообщения нельзя отключить и почему.
- Учитывайте часовой пояс при отправке. Сообщение в ночное время воспринимается как навязчивое, поэтому учитывается местное время.
- Предоставляйте отключение в один шаг. Многошаговая процедура отключения приводит к жалобам вместо отписки.
- Различайте отключение канала и отписку от рассылки. Отключение канала не отменяет обязательные сообщения, отписка касается только продуктовых.
- Документируйте перечень типов событий. Добавление типа события требует определения значения по умолчанию и отнесения к обязательным либо опциональным.
Практический сценарий: пользователь продолжал получать письма после отписки
Контекст: пользователь отписался от рассылки, но продолжал получать письма о новостях.
- Диагностика. В таблице предпочтений отсутствовала запись для типа события «новости», поэтому проверка возвращала значение по умолчанию — включено. Отписка сохранилась в отдельной таблице подписок, которая не проверялась при отправке.
- Причина. Две независимые модели: подписка на рассылку и предпочтения уведомлений. Отправка проверяла только предпочтения, а запись об отписке находилась в другой таблице и не влияла на решение.
- Действия. (а) Объединили проверку: отправка продуктовых сообщений требует наличия согласия и отсутствия отписки; (б) перевели значение по умолчанию для продуктовых событий на «требуется согласие», чтобы отсутствие записи не означало разрешение; (в) добавили ссылку отписки в каждое продуктовое письмо, чтобы отключение выполнялось из самого письма; (г) включили журнал изменений предпочтений для разбора обращений; (д) добавили тест, проверяющий отсутствие отправки после отписки.
- Результат. Отписка прекращает отправку продуктовых сообщений, а отсутствие явного согласия трактуется как отсутствие разрешения.
Вывод: значение по умолчанию для продуктовых сообщений — «требуется согласие», поскольку отсутствие записи не должно означать разрешение на отправку. Две независимые модели подписки и предпочтений приводят к расхождению, поэтому проверка выполняется по единому правилу.
FAQ
Можно ли отключить транзакционные уведомления? Нет. Сообщения о заказе, платеже и безопасности относятся к выполнению договора и защите учётной записи, поэтому не отключаются.
Что означает отсутствие записи о предпочтении? Для транзакционных событий — отправку, для продуктовых — отсутствие согласия и запрет отправки.
Когда применяется изменение настройки? К новым уведомлениям. Уже созданные сообщения отправляются по настройке на момент создания.
Нужно ли хранить историю изменений предпочтений? Да. Разбор обращения требует подтверждения того, какая настройка действовала на момент отправки.
Источники: GDPR, PostgreSQL constraints, RFC 8058 (List-Unsubscribe) — ссылки в шапке.