How to implement notifications and reminders in your applications using Reactions

Demonstrates how you can use Serialized Reactions to trigger notifications, reminders and domain logic from your events.

This guide demonstrates how you can use Serialized Reactions to make your events trigger backoffice notifications, customer reminders and domain logic.

The requirements

Let's say we plan to source aggregates of type reservation in Serialized. We have a couple of different domain events, among them the ReservationMade event, which we pretend means a customer made some kind of reservation. The reservation has to be paid before a certain date, or the reservation will be canceled.

Our business requirements are:

1) A notification should be sent to the backoffice team on each reservation.

2) A reminder should be sent to the customer if the reservation isn't paid two days before the due date.

3) The reservation should be canceled if it's not paid on the due date.

Our ReservationMade event will contain a field called paymentDueDate calculated by our domain logic based on the reservationDate. This field will be useful for us when setting up the Reactions!

Requirement 1 - The internal notification

We'll start by defining the Reaction for the internal notification. We will assume we have a service exposing an endpoint (/notify-backoffice) that we can use for sending the actual message. When defining the Reaction we give it a descriptive name and declare which feed (aggregate type) it should subscribe to and what event it should react to.

The actual API request will look like this:

curl -i https://api.serialized.io/reactions/definitions \
  --header "Content-Type: application/json" \
  --header "Serialized-Access-Key: <YOUR_ACCESS_KEY>" \
  --header "Serialized-Secret-Access-Key: <YOUR_SECRET_ACCESS_KEY>" \
  --data '
  {
    "reactionName": "new-reservation-notifier",
    "feedName": "reservation",
    "reactOnEventType": "ReservationMade",
    "action": {
      "actionType": "HTTP_POST",
      "targetUri": "https://your-notification-service.com/notify-backoffice"
    }
  }
  '

After you have stored this definition in your Serialized project, all future events of type ReservationMade of the aggregate type reservation will trigger an outgoing POST to the service endpoint specified above.

The payload included in the request is the actual event together with some extra metadata. See here for details.

Requirement 2 - The reminder

The next Reaction definition is a bit more advanced, as it will be delayed (or scheduled). We will assume we have access to an endpoint called /send-payment-reminder and like in the case above, we start by specifying the feed name and the event type.

In addition to that, we specify when the Reaction will trigger by pointing out the field paymentDueDate as the triggerTimeField. As our requirement is to send the reminder two days before the actual due date, we set the offset to -P2D. The offset pattern is defined in the ISO-8601 duration format (PnDTnHnMn.nS).

See here for all the details.

Note: It could be a better choice to let the domain logic dictate the trigger time of the reminder. If the event would include a field called reminderDate or similar, that field could have been used without an offset in this definition.

An implicit requirement we have is that we should not send a reminder if the customer has paid for the reservation. We achieve that by simply including the event PaymentReceived in the cancelOnEventTypes field.

The actual API request will look like this:

curl -i https://api.serialized.io/reactions/definitions \
  --header "Content-Type: application/json" \
  --header "Serialized-Access-Key: <YOUR_ACCESS_KEY>" \
  --header "Serialized-Secret-Access-Key: <YOUR_SECRET_ACCESS_KEY>" \
  --data '
  {
    "reactionName": "payment-reminder",
    "feedName": "reservation",
    "reactOnEventType": "ReservationMade",
    "triggerTimeField": "paymentDueDate",
    "offset": "-P2D",
    "cancelOnEventTypes": ["PaymentReceived"],
    "action": {
      "actionType": "HTTP_POST",
      "targetUri": "https://your-notification-service.com/send-payment-reminder"
    }
  }
  '

Requirement 3 - The cancellation

With this Reaction definition it's our goal to trigger a cancellation of the unpaid reservation. Just like before, we use the field paymentDueDate but in this case we don't have to use an offset. The cancellation should of course not be triggered if the reservation was paid, so we will use the same field cancelOnEventTypes as before.

The endpoint cancel-reservation is expected to perform the actual cancellation logic which most likely would result in an UnpaidReservationCanceled event or similar.

curl -i https://api.serialized.io/reactions/definitions \
  --header "Content-Type: application/json" \
  --header "Serialized-Access-Key: <YOUR_ACCESS_KEY>" \
  --header "Serialized-Secret-Access-Key: <YOUR_SECRET_ACCESS_KEY>" \
  --data '
  {
    "reactionName": "reservation-canceller",
    "feedName": "reservation",
    "reactOnEventType": "ReservationMade",
    "triggerTimeField": "paymentDueDate",
    "cancelOnEventTypes": ["PaymentReceived"],
    "action": {
      "actionType": "HTTP_POST",
      "targetUri": "https://your-reservation-service.com/cancel-reservation"
    }
  }
  '

Storing a ReservationMade event

When you have all three Reactions in place you are ready to store a reservation event! Note that you can browse both triggered and scheduled events using the Serialized Console.

Use the following command to make a reservation:

curl -i https://api.serialized.io/aggregates/reservation/c5c3ffea-a501-40bd-a2f2-6879daf34c83/events \
  --header "Content-Type: application/json" \
  --header "Serialized-Access-Key: <YOUR_ACCESS_KEY>" \
  --header "Serialized-Secret-Access-Key: <YOUR_SECRET_ACCESS_KEY>" \
  --data '
  {
     "events":[
        {
           "eventId":"97ed0e1d-bdb9-4354-94f8-faf481be227c",
           "eventType":"ReservationMade",
           "data":{
              "customerId": "test-customer-1",
              "reservationDate": "2020-11-05T12:13:14Z",
              "amount": 12345,
              "paymentDueDate": "2020-11-08"
           }
        }
     ]
  }
  '

Security and retries

All outgoing requests from Serialized are HMAC signed with a default signing key. The key can be customized per Reaction definition.

Serialized Reactions have a built-in retry mechanism and will be retried several times over the course of a day. A 2xx response is required for success, whereas a 4xx or 5xx response will be treated as an error and retried.

Please see the complete Reaction API documentation for all the details!