I guess the first question Memento Mori raises is why? but the explanation is quite extensive on the homepage, I'd rather not spend more time on it. I think it's worth digging a little more in what the project is, product wise, and how it was set up.

Memento Mori is a service that sends scheduled web notifications to remind that sooner or later you are kicking the bucket. With the soft touch of thoughtful quotes about death — fun, uh?

I wanted it to be up and running as soon as possible — in the end it took a few hours across a couple of nights to set it up. I spent way more time refining the copy, finding the quotes and testing. Speaking of which, thanks to who spent time proof reading or waiting for the notifications to test the scheduling *.

For the nature of the service, I wouldn't notice it failing unless all of a sudden I realised I didn't receive notifications. Which could happen after a long time, once shipped and out of my radar. I needed it to be reliable.

I kept myself away from cron jobs or anything I had personally to maintain. I was also wary about creating a service worker from scratch for the notifications. While surely not impossible, it wouldn't have been as no-brain, or fast, as I wished for.

I started looking around for services fullfill my needs.

The first, great, piece of the puzzle was One Signal.

The notifications

One Signal logo

One signal, according to the website, looks exactly like what I was after: "Cross platform push notification delivery. No limits on devices, notifications, or integrations. 100% Free".

As much as I don't believe in free lunches, I appreciate companies in their VC funded stages might offer some and who am I to disregard a free opportunity ;)

The setup was pretty straighforward. I had to create another app for my local environment, a step not documented or explained very well in the docs, but smooth otherwies. Following the instructions, I got it running in no time.

Memento Mori notification example

The scheduling & the data

That was easy.

Next step was to figure out how to deal with scheduling the notifications. As I didn't want to deal with maintaining a solution myself, I could look at lambdas, but Ifttt or Zapier were worth a shot. The latter has an integration with One Signal which made it the obvious choice.

I wanted to send a notification every few days (2 to 5), but also at a random hour: they should be unexpected and unpredictable as the subject of the project is.

Zapier is a little less user friendly than Ifttt. I assume it's because its flexibility can't be dumbed down too much, but they made a good job to make it comprehensible though. Zaps on Zapier are what recipes are for Ifttt, but while the latter focuses on reusability, a Zap could be configured much more in depth to adapt to almost any need.

Luckily Zapier, besides One Signal integration, offers a scheduling step and the ability to run code (python or node 4, without npm dependencies).

Zapier task interface

How could I make sure the queue of quotes was exhausted, before any duplicate was notified? Last thing I wanted was to to keep track of what I already sent updating the records in the db or anything similar.

That logic had to be elsewhere. Structuring the data in a declarative way seemed a good option. Taking advantage of the firebase api, I could "plot" my quotes on a timeline, using the id to declare when the quote was due to be sent.

For example, the firebase endpoint for a notification scheduled for the 2nd of March could look like https://my-amazing-app.firebase.io/quotes/302.json.

The piece of code on Zapier runs daily at midnight and looks up in firebase for a quote with the id matching the current date. If so, it would trigger One Signal notifications, with the quote and a random delivery time.

var month = inputData.month;
var day = ("0"+inputData.day).slice(-2);
var id = month+""+day;

var delayOptions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
var amPm = ['AM', 'PM'];

var delayOptionsRndmIdx = Math.floor(Math.random()*delayOptions.length);
var amPmRndmIdx = Math.floor(Math.random()*amPm.length);
var sendAt = delayOptions[delayOptionsRndmIdx] + amPm[amPmRndmIdx];

return fetch("https://my-firebase-amazing-app.firebaseio.com/quotes/"+id+".json")
  .then(function(res) {
    return res.json();
  })
  .then(function(json) {
    if (json) {
      json.id = id;
      json.sendAt = sendAt;
      callback(null, json);
    } else {
      throw new Error('Quote not found');
    }
  })
  .catch(callback);

I ended up obfuscating the id, to avoid exposing the URL pattern: I didn't want to spoiler too much if users started playing with them. Now IDs are forced back to numbers, converted in base 16 and multiplied by a constant color code.

EDIT: Zapier started failing on the scheduler task with a significant amount of timeouts. I will have to move to a cron script. It's a shame but I can still keep the OneSignal integration with a webhook — 20/03/2018

The site

With these pieces together I sorted the "infrastructure", now I needed a decent UI.

Considered the nature and the topic of the project, I wanted it to be a slow process, as opposite to how the web is consumed nowadays. During a talk about his work at GDS, Paul Annett mentioned how people have different expectations when the task they are performing touched a sphere as personal as death. Dealing with bureaucracy after the a relative decease is obviously very different than paying taxes, but unless you had experienced it or thought about it, it's not obvious how a website should react to the emotional conditions of its users.

Not sure my project deserves any of this attentions but nevertheless I wrote a long piece, trying to both set the mood and explain that "Why?" question I mentioned before. As a foreign, non-native speaker, I don't aim for perfection, but I'm happy with a result that holds back people from discovering what the site is about, until they are engaged in the storytelling.

I invested some time in researching a decent typography setup and then I picked Gutemberg.

Gutemberg website

It's a little brutal in its usage of *, which forced me to reset some max-widths around, but the end result is pleasant and appropriate to the needs.

Iubenda, to sort the privacy policy, was the last, very important, piece missing.

And now what?

I don't expect many people to sign up. I'd be surprised. From a personal perspective is was weirldy satisfactory, though, somewhat cathartic. I like wiring up third party tools and exploring non tech heavy solutions, giving up control.

The current amount of quotes are enough to cover until September, I will have to look a few more. Luckily it's plenty of them, I just need to make sure they make sense in the context of Memento Mori.

After that, it'll be a self sustaining project: I don't expect anyone to remember they got the same notification last year, at a different time of the day.

The Code

Memento Mori: https://github.com/cedmax/memento-mori




* In case you don't: Matt, Fabio, Mattia, Luca, Lori, Matteo. In order of appearance. Back to the post.