Cron-ify Web Push Notifications

Recently when working on a progressive web app to track pushups, I wanted to add a feature to receive daily reminder notifications. Push notifications for websites are pretty well supported everywhere except Safari/iOS, allowing website developers to send notifications to their users even when the website isn't open on the device (awesome!).

However, for my pushup project, I ran into challenges trying to implement daily push notifications because my application was a static website hosted on Github Pages. This meant the code was limited to being implemented client-side. After doing some research, it appears there once was a "Notification Trigger" API that was trialled in Chrome to support regular notification intervals, but it has since been removed.

The next alternative that is suggested is to use periodic background sync in the service worker, but this gives you almost no guarantees about when your notifications will be delivered, and how frequently (1-3 days).

Ideally, there would be a way to set a notification frequency, with a "cron"-style interval. Without a great client-side approach to solving this problem, I instead opted to write my own open-source service available for all developers to use!

Notification Scheduler Service

In short, I created a service hosted at notification.kaiser.lol (source here) which allows you to create a push subscription for a given interval. There are no guarantees about the uptime or availability of the service, but I do use it for my pushup app, so I will notice if it goes down.

You can incorporate this service into your client-side applications to deliver fixed-interval push notifications to your website users.

Prerequisites

Before receiving push notifications, your application needs a running service worker. This will be needed further down to receive push notification events even when your website is not open on the device.

Creating a Subscription

I've wrapped up the API into a single lib file that exposes a notificationScheduler method on the window object. You can include it as follows.

<script src="https://notification.kaiser.lol/lib.js"></script>
<script>
  // ensure the service worker is ready
  navigator.serviceWorker.ready.then(function () {
      // create the notificatio subscription, this will prompt the user for permission
      notificationScheduler({ interval: '0 8 * * *' })
      .then(_ => {
        // notification subscription created
      }).catch(error => {
        // failed to set notifications
      });
  });
</script>

If you would instead prefer to directly include the code, or want to make any modifications, you can take the code straight from lib.js (it's pretty small!).

For those unfamiliar with cron-style intervals, the above interval in the code sample "0 8 * * *" translates to:

  • 0: on the 0th minute past the hour (on the hour)
  • 8: on the 8th hour of the day (8 am)
  • *: for every day of the month
  • *: for every month of the year and
  • *: for every day of the week

If you would prefer to write in a human-readable format instead, that is also possible thanks to the library used.

Receiving a Scheduled Notification

Inside the service worker code of your application, you'll then want to handle the 'push' event. This example simply shows a basic notification saying 'Hello world', then upon clicking opens a new window at the root of the app. You can also focus on an existing open window if you would prefer.

self.addEventListener('push', function(e) {
  e.waitUntil(
    sw.registration.showNotification('Hello world')
  );
});

self.addEventListener('notificationclick', function(e) {
  e.notification.close();
  clients.openWindow("/")
});

The showNotification call above is what actually results in the notification being displayed. Here is an example of what that looks like for my pushup app on Mac.

The full set of options available for showNotification is quite broad, see the docs.

Multiple Subscriptions

In the event you have multiple subscriptions at different intervals and need to differentiate them, the payload sent to the 'push' event listener will contain the original interval you specified. The lib.js file does not support this, but the raw API calls inside lib.js do.

If you need to disambiguate notifications further (i.e. two notifications at the same interval) you'll have to handle that client-side in the service-worker push event.

Summary

Feel free to go ahead and use the above API and let me know what you think! If you have a problem, create a GitHub issue. If you like the service drop a star on the project itself.

Benjamin Kaiser

Benjamin Kaiser

Software Engineer working on the SharePoint team at Microsoft. I'm passionate about open source, slurpees, and Jesus.
Gold Coast, Australia

Post Comments