[CoLab] Update logic to handle the sending of email notifications
Notifications to be controlled as a tree of options, where the user can choose options such as 'disable', 'enable', 'daily digest' at any node in the tree, or they can have the node inherit from the node above. A simple tree of options might be something like this:
Notifications in general
Chat
General messages
Channel asdf1234
Channel qwer2345
Channel zxcv3456
@Mentions
Channel asdf1234
Channel qwer2345
Channel zxcv3456
Replies
Channel asdf1234
Channel qwer2345
Channel zxcv3456
Tasks
Assigned to me
Created by me
The user could go into their profile page and browse a tree like this to choose at any level how/whether they receive notifications for that branch of the tree. We might possibly suppress parts of the tree, for instance, not showing individual channels in their profile page.
Switching on/off individual channels via the profile page seems a bit laborious, and I think it would be a nicer experience if the user can just 'mute'/'unmute' a channel via a control in the chat pane, rather than having to find it in a tree in their profile page.
But the data structure I'm considering would allow any or all of these approaches.
We'd keep a table, perhaps called notification_user_options
.
It would have this structure:
id: ID!,
created: DateTime!,
updated: DateTime,
user_id: ID!,
object_id: ID,
path: [String!]!,
option: String!
Each row would represent some node in the tree of options, and the position in the tree would be specified by path. Some example paths might be:
['chat', 'mentions', 'qwer2345']
['chat', 'general', 'qwer2345']
['chat', 'mentions']
['task', 'assignee']
['chat']
[] // This is the global setting at the top of the tree, for all notification sources.
We would also keep the object_id
in its own column (for records containing an object_id
such as a channel ID), to enable efficient queries.
If a record doesn't exist for a particular node in the tree, that node is treated as inheriting the option from the node above. (Equivalently, the record may exist, and have option
set to 'inherit'.)
One benefit of this approach is that we don't have to generate rows for every node in the tree, e.g. when a new channel is created. We assume its setting is inherited unless there's a record present to tell us otherwise.
This is how we determine what option to apply for, e.g. a message that @mentions the user in a particular chat (ID qwer2345):
- Get all
notification_user_options
whereuser_id === userId
andobject_id
is eitherqwer2345
or null. - Exclude any records whose path is not some subset of
['chat', 'mentions', 'qwer2345']
. (Note, the path[]
is a subset of this.) - Order records by longest path first
- The first record containing an option other than 'inherit' determines the option to apply.
We still would have to have a mechanism for managing 'digest' options, such as '30MinuteDigest', 'dailyDigest', etc, which would require something like a chron job.