Notification
The notification API encapsulates the backend workflow of staging and dispatching a message to targeted user after receiving the message from event source.
Depending on whether an API call comes from user browser as a user request or from an authorized server application as an admin request, NotifyBC applies different permissions. Admin request allows full CRUD operations. An authenticated user request, on the other hand, are only allowed to get a list of in-app pull notifications targeted to the current user and changing the state of the notifications. An unauthenticated user request can not access any API.
When a notification is created by the event source server application, the message is saved to database prior to responding to API caller. In addition, for push notification, the message is delivered immediately, i.e. the API call is synchronous. For in-app pull notification, the message, which by default is in state new, can be retrieved later on by browser user request. A user request can only get the list of in-app messages targeted to the current user. A user request can then change the message state to read or deleted depending on user action. A deleted message cannot be retrieved subsequently by user requests, but the state can be updated given the correct id.
Deleted message is still kept in database.
NotifyBC provides API for deleting a notification. For the purpose of auditing and recovery, this API only marks the state field as deleted rather than deleting the record from database.
undo in-app notification deletion within a session
Because "deleted" message is still kept in database, you can implement undo feature for in-app notification as long as the message id is retained prior to deletion within the current session. To undo, call update API to set desired state.
In-app pull notification also supports message expiration by setting a date in field validTill. An expired message cannot be retrieved by user requests.
A message, regardless of push or pull, can be unicast or broadcast. A unicast message is intended for an individual user whereas a broadcast message is intended for all confirmed subscribers of a service. A unicast message must have field userChannelId populated. The value of userChannelId is channel dependent. In the case of email for example, this would be user's email address. A broadcast message must set isBroadcast to true and leave userChannelId empty.
Why field isBroadcast?
Unicast and broadcast message can be distinguished by whether field userChannelId is empty or not alone. So why the extra field isBroadcast? This is in order to prevent inadvertent marking a unicast message broadcast by omitting userChannelId or populating it with empty value. The precaution is necessary because in-app notifications may contain personalized and confidential information.
NotifyBC ensures the state of an in-app broadcast message is isolated by user, so that for example, a message read by one user is still new to another user. To achieve this, NotifyBC maintains two internal fields of array type - readBy and deletedBy. When a user request updates the state field of an in-app broadcast message to read or deleted, instead of altering the state field, NotifyBC appends the current user to readBy or deletedBy list. When user request retrieving in-app messages, the state field of the broadcast message in HTTP response is updated based on whether the user exists in field deletedBy and readBy. If existing in both fields, deletedBy takes precedence (the message therefore is not returned). The record in database, meanwhile, is unchanged. Neither field deletedBy nor readBy is visible to user request.
Model Schema
The API operates on following notification data model fields:
Name | Attributes | ||||||
---|---|---|---|---|---|---|---|
id notification id |
| ||||||
serviceName name of the service |
| ||||||
channel name of the delivery channel. Valid values: inApp, email, sms. |
| ||||||
userChannelId user's delivery channel id, for example, email address. For unicast inApp notification, this is authenticated user id. When sending unicast push notification, either userChannelId or userId is required. |
| ||||||
userId authenticated user id. When sending unicast push notification, either userChannelId or userId is required. |
| ||||||
state state of notification. Valid values: new, read (inApp only), deleted (inApp only), sent (push only) or error. For inApp broadcast notification, if the user has read or deleted the message, the value of this field retrieved by admin request will still be new. The state for the user is tracked in fields readBy and deletedBy in such case. For user request, the value contains correct state. |
| ||||||
created date and time of creation |
| ||||||
updated date and time of last update |
| ||||||
isBroadcast whether it's a broadcast message. A broadcast message should omit userChannelId and userId, in addition to setting isBroadcast to true |
| ||||||
skipSubscriptionConfirmationCheck When sending unicast push notification, whether or not to verify if the recipient has a confirmed subscription. This field allows subscription information be kept elsewhere and NotifyBC be used as a unicast push notification gateway only. |
| ||||||
validTill expiration date-time of the message. Applicable to inApp notification only. |
| ||||||
invalidBefore date-time in the future after which the notification can be dispatched. |
| ||||||
an object whose child fields are channel dependent:
|
| ||||||
httpHost This field is used to replace token {http_host} in push notification message template during mail merge and overrides config httpHost. |
| ||||||
asyncBroadcastPushNotification this field determines if the API call to create an immediate (i.e. not future-dated) broadcast push notification is asynchronous or not. If omitted, the API call is synchronous, i.e. the API call blocks until notifications to all subscribers have been dispatched. If set, valid values and corresponding behaviors are
|
| ||||||
the event that triggers the notification, for example, a RSS feed item when the notification is generated automatically by RSS cron job. Field data serves two purposes
|
| ||||||
broadcastPushNotificationSubscriptionFilter a string conforming to jmespath filter expressions syntax after the question mark (?). The filter is matched against the data field of the subscription. Examples of filter
|
| ||||||
readBy this is an internal field to track the list of users who have read an inApp broadcast message. It's not visible to a user request. |
| ||||||
deletedBy this is an internal field to track the list of users who have marked an inApp broadcast message as deleted. It's not visible to a user request. |
| ||||||
dispatch this is an internal field to track the broadcast push notification dispatch outcome. It consists of up to four arrays
|
|
Get Notifications
GET /notifications
permissions required, one of
- super admin
- admin
- authenticated user
inputs
a filter containing properties where, fields, order, skip, and limit
- parameter name: filter
- required: false
- parameter type: query
- data type: object
The filter can be expressed as either
- URL-encoded stringified JSON object (see example below); or
- in the format supported by qs, for example
?filter[where][created][$gte]="2023-01-01"&filter[where][created][$lt]="2024-01-01"
Regardless, the filter will have to be parsed into a JSON object conforming to
{ "where": {...}, "fields": ..., "order": ..., "skip": ..., "limit": ..., }
All properties are optional. The syntax for each property is documented, respectively
- for where , see MongoDB Query Documents
- for fields , see Mongoose select
- for order, see Mongoose sort
- for skip, see MongoDB cursor.skip
- for limit, see MongoDB cursor.limit
outcome
- for admin requests, returns unabridged array of notification data matching the filter
- for authenticated user requests, in addition to filter, following constraints are imposed on the returned array
- only inApp notifications
- only non-deleted notifications. For broadcast notification, non-deleted means not marked by current user as deleted
- only non-expired notifications
- for unicast notifications, only the ones targeted to current user
- if current user is in readBy, then the state is changed to read
- the internal field readBy and deletedBy are removed
- forbidden to anonymous user requests
example
to retrieve notifications created in year 2023, run
curl -X GET --header 'Accept: application/json' 'http://localhost:3000/api/notifications?filter=%7B%22where%22%3A%7B%22created%22%3A%7B%22%24gte%22%3A%222023-01-01%22%2C%22%24lt%22%3A%222024-01-01%22%7D%7D%7D'
the value of the filter query parameter is URL-encoded stringified JSON object
{ "where": { "created": { "$gte": "2023-01-01", "$lt": "2024-01-01" } } }
Get Notification Count
GET /notifications/count
permissions required, one of
- super admin
- admin
- authenticated user
inputs
a where query parameter with value conforming to MongoDB Query Documents
- parameter name: where
- required: false
- parameter type: query
- data type: object
The value can be expressed as either
- URL-encoded stringified JSON object (see example below); or
- in the format supported by qs, for example
?where[created][$gte]="2023-01-01"&where[created][$lt]="2024-01-01"
outcome
Validations rules are the same as GET /notifications. If passed, the output is a count of notifications matching the query
{ "count": <number> }
example
to retrieve the count of notifications created in year 2023, run
curl -X GET --header 'Accept: application/json' 'http://localhost:3000/api/notifications/count?where=%7B%22created%22%3A%7B%22%24gte%22%3A%222023-01-01%22%2C%22%24lt%22%3A%222024-01-01%22%7D%7D'
the value of the where query parameter is URL-encoded stringified JSON object
{ "created": { "$gte": "2023-01-01", "$lt": "2024-01-01" } }
Create/Send Notifications
POST /notifications
permissions required, one of
- super admin
- admin
inputs
- an object containing notification data model fields. At a minimum all required fields that don't have a default value must be supplied. Id field should be omitted since it's auto-generated. The API explorer only created an empty object for field message but you should populate the child fields according to model schema
- parameter name: data
- required: true
- parameter type: body
- data type: object
- an object containing notification data model fields. At a minimum all required fields that don't have a default value must be supplied. Id field should be omitted since it's auto-generated. The API explorer only created an empty object for field message but you should populate the child fields according to model schema
outcome
NotifyBC performs following actions in sequence
if it's a user request, error is returned
inputs are validated. If validation fails, error is returned. In particular, for unicast push notification, the recipient as identified by either userChannelId or userId must have a confirmed subscription if field skipSubscriptionConfirmationCheck is not set to true. If skipSubscriptionConfirmationCheck is set to true, then the subscription check is skipped, but in such case the request must contain userChannelId, not userId as subscription data is not queried to obtain userChannelId from userId.
for push notification, if field httpHost is empty, it is populated based on request's http protocol and host.
the notification request is saved to database
if the notification is future-dated, then all subsequent request processing is skipped and response is sent back to user. Steps 7-11 below will be carried out later on by the cron job when the notification becomes current.
if it's an async broadcast push notification, then response is sent back to user but steps 7-12 below is processed separately
for unicast push notification, the message is dispatched to targeted user; for broadcast push notification, following actions are performed:
number of confirmed subscriptions is retrieved
the subscriptions are partitioned and processed concurrently as described in config section Broadcast Push Notification Task Concurrency
when processing an individual subscription,
- if the subscription has filter rule defined in field broadcastPushNotificationFilter and notification contains field data, then the data is matched against the filter rule. Notification message is only dispatched if there is a match.
- if the notification has filter rule defined in field broadcastPushNotificationSubscriptionFilter and subscription contains field data, then the data is matched against the filter rule. Notification message is only dispatched if there is a match.
If the subscription failed to pass any of the two filters, and if both guaranteedBroadcastPushDispatchProcessing and logSkippedBroadcastPushDispatches are true, the subscription id is logged to dispatch.skipped
Regardless of unicast or broadcast, mail merge is performed on messages before dispatching.
the state of push notification is updated to sent or error depending on sending status. For broadcast push notification, the dispatching could be failed only for a subset of users. In such case, the field dispatch.failed contains a list of objects of {userChannelId, subscriptionId, error} the message failed to deliver to, but the state will still be set to sent.
For broadcast push notifications, if guaranteedBroadcastPushDispatchProcessing is true, then field dispatch.successful is populated with a list of subscriptionId of the successful dispatches.
For push notifications, the bounce records of successful dispatches are updated
the updated notification is saved back to database
if it's an async broadcast push notification with a callback url, then the url is called with POST verb containing the notification with updated status as the request body
for synchronous notification, the saved record is returned unless there is an error saving to database, in which case error is returned
example
To send a unicast email push notification, copy and paste following json object to the data value box in API explorer, change email addresses as needed, and click Try it out! button:
{ "serviceName": "education", "userChannelId": "foo@bar.com", "skipSubscriptionConfirmationCheck": true, "message": { "from": "no_reply@bar.com", "subject": "test", "textBody": "This is a test" }, "channel": "email" }
As the result, foo@bar.com should receive an email notification even if the user is not a confirmed subscriber, and following json object is returned to caller upon sending the email successfully:
{ "serviceName": "education", "state": "sent", "userChannelId": "foo@bar.com", "skipSubscriptionConfirmationCheck": true, "message": { "from": "no_reply@bar.com", "subject": "test", "textBody": "This is a test" }, "created": "2016-09-30T20:37:06.011Z", "updated": "2016-09-30T20:37:06.011Z", "channel": "email", "isBroadcast": false, "id": "57eeccf23427b61a4820775e" }
Update a Notification
PATCH /notifications/{id}
This API is mainly used for updating an inApp notification.
permissions required, one of
- super admin
- admin
- authenticated user
inputs
- notification id
- parameter name: id
- required: true
- parameter type: path
- data type: string
- an object containing fields to be updated.
- parameter name: data
- required: true
- parameter type: body
- data type: object
- notification id
outcome
- for user requests, NotifyBC performs following actions in sequence
- for unicast notification, if the notification is not targeted to current user, error is returned
- all fields except for state are discarded from the input
- for broadcast notification, current user id in appended to array readBy or deletedBy, depending on whether state is read or deleted, unless the user id is already in the array. The state field itself is then discarded
- the notification identified by id is merged with the updates and saved to database
- HTTP response code 204 is returned, unless there is error.
- admin requests are allowed to update any field
- for user requests, NotifyBC performs following actions in sequence
Delete a Notification
This API is mainly used for marking an inApp notification deleted. It has the same effect as updating a notification with state set to deleted.
DELETE /notifications/{id}
- permissions required, one of
- super admin
- admin
- authenticated user
- inputs
- notification id
- parameter name: id
- required: true
- parameter type: path
- data type: string
- notification id
- outcome: same as the outcome of Update a Notification with state set to deleted.
Replace a Notification
PUT /notifications/{id}
This API is intended to be only used by admin web console to modify a notification in new state. Notifications in such state are typically future-dated or of channel in-app.
permissions required, one of
- super admin
- admin
inputs
- notification id
- parameter name: id
- required: true
- parameter type: path
- data type: string
- notification data
- parameter name: data
- required: true
- parameter type: body
- data type: object
- notification id
outcome
NotifyBC process the request same way as Create/Send Notifications except that notification data is saved with id supplied in the parameter, replacing existing one.