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:

NameAttributes

id

notification id

typestring, format depends on db
auto-generatedtrue

serviceName

name of the service

typestring
requiredtrue

channel

name of the delivery channel. Valid values: inApp, email, sms.

typestring
requiredtrue
defaultinApp

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.

typestring
requiredfalse

userId

authenticated user id. When sending unicast push notification, either userChannelId or userId is required.

typestring
requiredfalse

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.

typestring
requiredtrue
defaultnew

created

date and time of creation

typedate
auto-generatedtrue

updated

date and time of last update

typedate
auto-generatedtrue

isBroadcast

whether it's a broadcast message. A broadcast message should omit userChannelId and userId, in addition to setting isBroadcast to true

typeboolean
requiredfalse
defaultfalse

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.

typeboolean
requiredfalse
defaultfalse

validTill

expiration date-time of the message. Applicable to inApp notification only.

typedate
requiredfalse

invalidBefore

date-time in the future after which the notification can be dispatched.

typedate
requiredfalse

message

an object whose child fields are channel dependent:
  • for inApp, NotifyBC doesn't have any restriction as long as web application can handle the message. subject and body are common examples.
  • for email: from, subject, textBody, htmlBody
    • type: string
    • these are email template fields.
  • for sms: textBody
    • type: string
    • sms message template.
Mail merge is performed on email and sms message templates.
typeobject
requiredtrue

httpHost

This field is used to replace token {http_host} in push notification message template during mail merge and overrides config httpHost.

typestring
requiredfalse
default<http protocol, host and port of current request> for push notification

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
  • true - async without callback
  • false - sync
  • a string containing callback url - async with a POST call to the supplied callback url upon completion
When posting to a service with large number of subscribers, it is highly recommended to set the API call to asynchronous, i.e. setting the value to true or supplying a callback.
typestring or boolean
requiredfalse
defaultfalse

data

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
  • to replace dynamic tokens in message template fields
  • to match against filter defined in subscription field broadcastPushNotificationFilter, if supplied, for broadcast push notifications to determine if the notification should be delivered to the subscriber
typeobject
requiredfalse

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
  • simple
    province == 'BC'
  • calling jmespath's built-in functions
    contains(province,'B')
  • calling custom filter functions
    contains_ci(province,'b')
  • compound
    (contains(province,'BC') || contains_ci(province,'b')) && city == 'Victoria'
All of above filters will match data object {"province": "BC", "city": "Victoria"}
typestring
requiredfalse

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.

typearray
requiredfalse
auto-generatedtrue

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.

typearray
requiredfalse
auto-generatedtrue

dispatch

this is an internal field to track the broadcast push notification dispatch outcome. It consists of up to four arrays

  • failed - a list of objects containing subscription IDs and error of failed dispatching
  • successful - a list of strings containing subscription IDs of successful dispatching
  • skipped - a list of strings containing subscription IDs of skipped dispatching
  • candidates - a list of strings containing IDs of confirmed subscriptions to the service. Dispatching to a subscription is subject to filtering.
typeobject
requiredfalse
auto-generatedtrue

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

      1. URL-encoded stringified JSON object (see example below); or
      2. in the format supported by qsopen in new window, 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

  • 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 Documentsopen in new window

      • parameter name: where
      • required: false
      • parameter type: query
      • data type: object

      The value can be expressed as either

      1. URL-encoded stringified JSON object (see example below); or
      2. in the format supported by qsopen in new window, 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
  • outcome

    NotifyBC performs following actions in sequence

    1. if it's a user request, error is returned

    2. 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.

    3. for push notification, if field httpHost is empty, it is populated based on request's http protocol and host.

    4. the notification request is saved to database

    5. 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.

    6. if it's an async broadcast push notification, then response is sent back to user but steps 7-12 below is processed separately

    7. for unicast push notification, the message is dispatched to targeted user; for broadcast push notification, following actions are performed:

      1. number of confirmed subscriptions is retrieved

      2. the subscriptions are partitioned and processed concurrently as described in config section Broadcast Push Notification Task Concurrency

      3. when processing an individual subscription,

        1. 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.
        2. 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.

    8. 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.

    9. For broadcast push notifications, if guaranteedBroadcastPushDispatchProcessing is true, then field dispatch.successful is populated with a list of subscriptionId of the successful dispatches.

    10. For push notifications, the bounce records of successful dispatches are updated

    11. the updated notification is saved back to database

    12. 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

    13. 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
  • outcome

    • for user requests, NotifyBC performs following actions in sequence
      1. for unicast notification, if the notification is not targeted to current user, error is returned
      2. all fields except for state are discarded from the input
      3. 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
      4. the notification identified by id is merged with the updates and saved to database
      5. HTTP response code 204 is returned, unless there is error.
    • admin requests are allowed to update any field

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
  • 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
  • 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.