programming

Getting important notifications on my iPhone, even with DND turned on.

Photo by BENCE BOROS on Unsplash

Photo by BENCE BOROS on Unsplash

As a startup founder of an HR SaaS called HR Partner, I found myself being ‘on call’ virtually 24/7. I had hundreds of customers in countries all over the world, and this resulted in my email and support centre notifications going off around the clock.

Probably the best thing I ever did for my sanity was to turn on ‘Scheduled DND (Do Not Disturb)’ on my iPhone between the hours of 10pm and 6am. I could finally get some decent sleep. (Note: The second best thing I did was to hire a great support team to take care of customer tickets during that time).

However, some of those notifications were generated from our AWS monitoring system, and were caused by critical events such as a server going down, or becoming unavailable due to a DDoS attack or similar. Because we don’t yet have a full DevOps or security team in place, I still deal with those incidents myself.

However, on the iPhone, DND is exactly that - a blanket ‘do not disturb’ which shuts off all notifications from all apps. That was, until iOS 12 came out, which included a feature which allowed ‘critical notifications’ to still get through.

But the problem with that was that critical notifications were deemed as being from government agencies or weather services, that had to send through important notifications about events that could be life threatening. Not all apps could have the ability to post critical notifications.

But then this week, I noticed that a third party notification app called Pushover was actually approved for sending critical notifications. This got me to thinking - could I send certain important event activity from AWS to myself using this service? The answer was YES, and it was quite simple to do so, using and AWS lambda function as the go between.

Here is how you can do it too.

Set up a Pushover account

Head on over to Pushover.net and set yourself up an account. You can stay on the free tier as long as you send less than 7500 messages per month. Seeing as I only expected about 2 or 3 messages per week, based on our normal activities, this is well under their limit and I can stay on the free tier forever.

The have an iPhone app that you will also need to download so you can receive notifications. The cost of this app is USD$5 (via in app purchase once your 7 day trial is over) which is a tiny price to pay for the convenience of critical notifications.

Once you set up an account, you will get a User Key, that you will need, and you will also have to set up an Application on the platform, which will give you an Application API Token, that you will also need for the integration steps below.

Pushover_Setup_1.png


To get the application API token, you will need to click on the Application once you create it.

Pushover_Setup_2.png

Create a Node.JS Project

Now we can create a small (talking about 20 lines of code) Node.js project which will handle the Pushover notifications from AWS. But wait, why are we creating the project locally first instead of just creating the code in AWS Lambda directly?

Well, the short answer is that we will be using a node module called ‘pushover-notifications’. This isn’t in the standard collection of modules that Lambda has access to, so we need to create the project offline, with all the requisite module dependencies, then ZIP and upload the package to Lambda.

So, create an empty folder on your local drive, and within that folder, run these two commands:

npm init
npm install pushover-notifications

And now, you can create a file called index.js in here, with the following contents:

var Push = require('pushover-notifications')

exports.handler = function(event, context) {

    var p = new Push( {
      user: process.env['PUSHOVER_USER'],
      token: process.env['PUSHOVER_TOKEN'],
    })
    
    var snsMessage = JSON.parse(event.Records[0].Sns.Message);
  
    var alertSubject = snsMessage.AlarmDescription;
    var alertMessage = snsMessage.NewStateValue + "\n" + snsMessage.NewStateReason + "\n" + snsMessage.Trigger.MetricName + " " + snsMessage.Trigger.Namespace;

    var msg = {
      title: alertSubject,
      message: alertMessage,
      priority: 2,
      retry: 30,
      expire: 300,
      url: "https://<link to rebbot server via API>",
      url_title: "Reboot Server"
    }

    p.send( msg, function( err, result ) {
      if ( err ) {
        throw err
      }
    })

};

And once saved, you can ZIP up the entire folder (including the ‘node_modules’ folder and others that may be automatically created in here) and keep the ZIP file aside for the next step.

To explain the code in more detail, the first line is just including the pushover-notifications module. Then the next bit called exports.handler is the standard function that AWS Lambda will call (with the SNS parameters passed in the event variable).

Next, we instantiate an instance of the Push() object called p, which is where we pass the Pushover API token and User token. Note: For security reasons, these tokens are stored as environment variables instead of being embedded within the code. You will need to set up these environment variables as part of setting up the Lambda function in the next section.

The next line after this just extracts the JSON stringified SNS notification details that are sent to the Lambda function from Amazon SNS and stores it in a variable called snsMessage.

Then we set up the subject and actual message body. These can be anything you like, but in this case, we are extracting the description and particular values that have been passed to us from SNS which contain things like the actual error text and service names etc.

Penultimately, we set up an object called msg which contains the packet of data to send to Pushover. This includes the subject and message body we created above, but more importantly, this also specifies the type of message. The priority variable is key. Normal messages are priority 0, but these messages are treated as non-urgent and won’t come through when DND mode is turned on on the phone.

Priority 1 messages are marked ‘critical’ and WILL come through, but priority 2 messages are deemed ‘critical’ AND will also persist until they are acknowledged by the recipient. THIS is the type of message I wanted - as the notifications are critical enough to warrant me responding to them and not just glancing and ignoring them.

For priority 2 messages, you also have to specify the retry period (which I set to 30 seconds) and the expire period (which I set to 5 minutes, or 300 seconds). This means that if I don’t acknowledge the message, it will keep pinging me every 30 seconds for up to 5 minutes. Long enough to wake me from a deep slumber at 2am!

At the bottom of this object, you can also specify a URL and a link name that gets sent with the notification. This is really useful, and I used it to embed a special link that our team has which will call an AWS API Gateway function to reboot the server(s) automatically. Perhaps I will detail how I set this up on another blog post in the future, but for now, lets just stick to the notifications.


Set Up A Lambda Function

Now we will need to set up a serverless function in AWS which will transmit certain SNS (Simple Notification Service) messages to Pushover.

This is all done quite simply and easily via your AWS console. Just choose the ‘Lambda’ function from the AWS ‘Services’ drop down

Pushover_Lambda_1.png

And in the next section click the ‘Create Function’ button.

Pushover_Lambda_2.png

Now we choose ‘Author from scratch’ and we give the function a meaningful name such as sendAlertToPushover, and we specify ‘Node.js 12.x’ as the Runtime engine, and click ‘Create Function’.

On the next screen, we won’t actually type in any code. Instead, we will replace the existing code by clicking on the ‘Actions’ button and selecting ‘Upload a .zip file’.

Pushover_Lambda_4.png

Specify the name of the ZIP file you created containing the Node.js project files that you created in the previous step. This will upload all the files to Lambda and show you the code in the editor window.

Don’t forget to scroll down a little and set up the environment variables that the project needs:

Pushover_Lambda_5.png

Ok, that should be all for this part of the setup.

Setting Up The SNS Service

Ok, nearly there. Now we need to set up the Amazon SNS (Simple Notification Service) which is the conduit that AWS uses to send messages from one service to another.

From the ‘Services’ menu in the AWS Console again, find the SNS service and select it.

Then in the SNS dashboard, choose ‘Topics’ from the left hand menu, and click on ‘Create Topic’.

Pushover_SNS_1.png

Next, we need to give the topic a meaningful name, such as ‘criticalPushoverMessage’ or similar.

Once done, click on ‘Create Topic’ at the bottom of the screen (leave everything else as default for now).

Pushover_SNS_2.png

Once you save this topic, you will be taken to this screen, where you can click ‘Create Subscription’ to tell AWS how to handle the topic messages.

Pushover_SNS_3.png

And on the subscription creation screen, we need to change the ‘Protocol’ to ‘AWS Lambda’ and then on the ‘Endpoint’ we need to choose the Lambda function that we created in the step above.

Now we can click ‘Create Subscription’ at the bottom of the screen to save it.

Pushover_SNS_4.png

That is it! You should see the following confirmation screen.

Pushover_SNS_5.png

Make sure the ‘Status’ says Confirmed. If so, you are all set to go.



Choosing Messages To Send

So the last step on the AWS side of things is to specify which messages you want to send over to Pushover to wake you up.

Monitoring Elastic Beanstalk

In our case, we host our servers on Elastic Beanstalk, and this service has a monitoring console where we can set up Alerts which we can send through whenever any events occur ‘outside normal operations’ - these can be things like very high sustained CPU activity, or network traffic dropping to zero, or the server status switching to ‘Degraded’ or ‘Severe’.

In our case, we want to trigger an alarm when the server CPU hits more than 90% for 15 minutes straight, so from the Elastic Beanstalk Monitoring console, we simply click the little ‘bell’ icon on the top right of the ‘CPU Utilization’ graph.

Pushover_EB_1.png

Now on the next screen you can set up the parameters which will trigger the alarm:

Pushover_EB_2.png

Give this alert a meaningful name such as ‘mainServerHighCPU’. It is also a good idea to give it a meaningful description, as this description can be used on the Pushover notification itself.

Then you can specify the monitoring period - we will select 15 minutes, as that is the window that we would like to monitor for the high CPU activity. And we will specify >= 90% as the threshold to look for. Feel free to modify to suit your own needs.

The important thing is in the ‘Notify’ field, you pick the SNS Notification Topic that we set up in the previous step above. Also, you need to tick ‘Alarm’ in the last section as we only want notifications when things hit an ‘Alarm’ state, and not when they go back to ‘OK’ or when they have insufficient data. (You may actually want notification when things go back to normal, so you can select it if you want, but I don’t normally care if things are OK or unclear, only when they break).

That is it! When you click the ‘Add’ button, the alert will be created in Cloudwatch, and the SNS Topic we set up will be listening for any changes in this alert. As soon as the alert conditions are met, the details of the alert will be send to SNS, which will then pass it over to the Lambda function we created, which in turn will send it to Pushover, and to our phones!

Monitoring EC2

You can also create alerts directly on an EC2 instance if you are not using Elastic Beanstalk. They work in a similar way. Here you can go to your EC2 console, then choose an EC2 instance from the list, and click on the ‘Monitoring’ tab.

Pushover_EB_3.png

You can then click on the ‘Create Alarm’ button, and tell the system what sort of alarm you want to set:

Pushover_EB_4.png

Don’t forget to choose the same SNS topic name we created earlier, then you can specify the parameters of the alarm. Here we want to trigger an alert when the Network Out falls below 10,000 bytes for a period of 5 minutes. That would usually mean the server has stopped responding to external requests for some reason.



Receiving Alerts

OK, we are all done. All we have to do is to wait until you have a server issue, then you should receive an alert like this:

Pushover_iPhone_1.png

The little red triangle next to the alert app signifies that this is a CRITICAL alert.

Note: The first time you receive a critical alert, you will be asked by iOS whether you wish the app to be able to send critical alerts. You have to answer ‘Yes’ to this prompt to receive future critical alerts.

Tapping on the alert should open Pushover where you can see more information on the error.

(Note that the red background denotes a critical alert!)

(Note that the red background denotes a critical alert!)

And further, opening up the alert will show you full details.

Pushover_iPhone_3.png

Note the link on the bottom of the alert which allows me to reset the server with just one tap. No need for me to even get out of bed to log into my AWS console in the middle of the night. I can get enough information to judge the severity of the alert, and I can choose to reboot the server right there and then to sort the issue out quickly.

Also note that there is a link to acknowledge the alert. This is because I set the alert in the Lambda function to be priority 2, so I have to acknowledge it or it will keep bugging me. If you don’t want to be constantly nagged, set it to priority 1 instead.


Conclusion

So that is it. I hope that this step by step tutorial is useful to you in setting up special alert notifications which will get through even if your phone is set to ‘Do Not Disturb’. Just be careful if you are out at a concert or a meditation camp where you truly don’t want to be disturbed. You can set notification hours within the Pushover app though, or even silence ALL notifications for ‘x’ minutes/hours, to further narrow down when you can be disturbed. Have fun!

Building a Status page for $5 per month

When we first built HR Partner, I wanted to have some sort of status page like most web apps do, to let our customers know about uptime availability and any scheduled maintenance that we had planned.

Our HR Partner status page at: status.hrpartner.io

Our HR Partner status page at: status.hrpartner.io

Looking at most of the commercially available offerings, I found that while excellent, they were quite expensive, when compared to the project management, accounting and bug tracking tools that we already subscribed to.  Being a relatively small, boot strapped startup, I didn't want to add to our already high monthly subscription burden too much at this stage.

Eventually, my search led me to Cachet, which is an open sourced version of a status page app, that seemed to have most of the features that the 'big boys' did.  End of the day, we managed to host Cachet on a virtual server for around $5 a month, and given that the cheapest commercial variant we found was $29 per month, I am happy that we got something working for a budget price that is hard to beat.

Given the buyout of one of the main commercial vendors StatusPage.io by Atlassian today, a lot of people have seen me post about my efforts and have emailed or PMd me to ask how we went about this, so this post will hopefully let you know the steps we took.

Hosting

Our main HR Partner web app is hosted by Amazon AWS, in their us-east-1 region.  Because we wanted some sort of redundancy in case of a major Amazon outage or regional catastrophe, we decided to host our status page on a Digital Ocean Droplet over on the West coast.  Different providers, different infrastructure, different areas.

So the first thing we did was to set up a VPS in Digital Ocean.  I picked the cheapest droplet they had, which was a $5 per month server running Ubuntu 14.04 (64 bit) with 512MB of RAM and 20GB of storage.  Cachet doesn't take much in the way of resources at all, so this was plenty for us.

The Stack

Once the Droplet was up and running, we just opened up a console to the server from within our DO control panel, and installed MySQL on it.  Digital Ocean have a great article on how to do this right here.  We simply followed the instructions step by step.

Next step was to follow the equally great instructions from the Cachet documentation right here to install Cachet on that VPS.

I believe the only tricky thing that we had to do was tweak the permissions within the Cachet folder.  I believe we had to chown the folder and all subfolders to the www-data user and group.

Configuring Cachet

Once we had Cachet installed as per above, we adjusted the .env file to use our preinstalled MySQL instance for the database, and also to use our normal Amazon SES service for the sending of emails.  I believe we had to also change the default queue driver for sending emails.  Here is what our config file looked like:

APP_ENV=production
APP_DEBUG=false
APP_URL=http://status.hrpartner.io
APP_KEY=***secret key here***

DB_DRIVER=mysql
DB_HOST=localhost
DB_DATABASE=cachet
DB_USERNAME=***yourdbusername***
DB_PASSWORD=***yourdbpassword***
DB_PORT=null

CACHE_DRIVER=apc
SESSION_DRIVER=APC
QUEUE_DRIVER=sync
CACHET_EMOJI=false

MAIL_DRIVER=smtp
MAIL_HOST=email-smtp.us-east-1.amazonaws.com
MAIL_PORT=25
MAIL_USERNAME=***yourSESuserIAM***
MAIL_PASSWORD=***yourSESkey***
MAIL_ADDRESS=status@hrpartner.io
MAIL_NAME="HR Partner Status"
MAIL_ENCRYPTION=tls

That was really about it!  (Oh, don't forget to let Amazon SES know about the email address that Cachet will be using to send emails as - in our case status@hrpartner.io.  Otherwise it won't pass the SES spam filtering).

Last thing was to tweak our Amazon Route 53 service to point status.hrpartner.io to our Digital Ocean VPS IP address.  Done!

Now it was all a matter of setting up Cachet with our components and needed to be reported on, and we were away.  All in all, I think the install and configuration took less than an hour to do.

BONUS: Auto update

Because HR Partner is a fairly complex app, with multiple sub apps for the API, reporting engine etc., deployment can take a while to do, and can result in slow performance for up to 15 minutes at a time while the virtual instances are updated and synchronised.

We use Amazon's Elastic Beanstalk command line tools to deploy changes, and at first our procedures meant that before we ran a deployment, we manually logged into our Cachet server to flag the services that would be down, then deployed, waited, and went back to Cachet to flag them 'green' again.

This was quite tedious, and I wondered if there was an automated way.  It turns out there is.  Cachet has a great JSON API, so what we did in our projects was to create a couple of files under the .ebextensions folder in our project folder.  These files contain the scripts that we wanted Elastic Beanstalk to run before and after deployment.  First, we created a file called 01_file.yml for the before script:

files:
"/opt/elasticbeanstalk/hooks/appdeploy/pre/02_cachetupdatestart.sh":
mode: "000755"
owner: root
group: root
content: |
#!/usr/bin/env bash
curl -H "Content-Type: application/json;" -H "X-Cachet-Token: [secret token]" -X PUT -d '{"status":2}' http://status.hrpartner.io/api/v1/components/2
curl -H "Content-Type: application/json;" -H "X-Cachet-Token: [secret token]" -X PUT -d '{"status":2}' http://status.hrpartner.io/api/v1/components/4
curl -H "Content-Type: application/json;" -H "X-Cachet-Token: [secret token]" -X PUT -d '{"status":2}' http://status.hrpartner.io/api/v1/components/5
curl -H "Content-Type: application/json;" -H "X-Cachet-Token: [secret token]" -X PUT -d '{"status":2}' http://status.hrpartner.io/api/v1/components/6
curl -H "Content-Type: application/json;" -H "X-Cachet-Token: [secret token]" -X PUT -d '{"status":2}' http://status.hrpartner.io/api/v1/components/8

Then we created a 02_file.yml for the after script:

files:
"/opt/elasticbeanstalk/hooks/appdeploy/post/02_cachetupdatefinish.sh":
mode: "000755"
owner: root
group: root
content: |
#!/usr/bin/env bash
curl -H "Content-Type: application/json;" -H "X-Cachet-Token: [secret token]" -X PUT -d '{"status":1}' http://status.hrpartner.io/api/v1/components/2
curl -H "Content-Type: application/json;" -H "X-Cachet-Token: [secret token]" -X PUT -d '{"status":1}' http://status.hrpartner.io/api/v1/components/4
curl -H "Content-Type: application/json;" -H "X-Cachet-Token: [secret token]" -X PUT -d '{"status":1}' http://status.hrpartner.io/api/v1/components/5
curl -H "Content-Type: application/json;" -H "X-Cachet-Token: [secret token]" -X PUT -d '{"status":1}' http://status.hrpartner.io/api/v1/components/6
curl -H "Content-Type: application/json;" -H "X-Cachet-Token: [secret token]" -X PUT -d '{"status":1}' http://status.hrpartner.io/api/v1/components/8
curl -H "Content-Type: application/json;" -H "X-Cachet-Token: [secret token]" -X POST -d '{"value":1}' http://status.hrpartner.io/api/v1/metrics/1/points

(Replace the [secret token] above with your unique Cachet API token.)

Now whenever we do an eb deploy command, the relevant status page components are marked 'yellow' for the duration of the deployment, then brought back up to 'green' again when completed.

Cheap As Chips

The only running cost for our status page is the $5 per month for the Digital Ocean hosting.  That is all.  We've been running this configuration for some months now with good results.  When revenue and usage gets to the point where we need to update this, then we may look at some of the commercial offerings, but for now, this setup works well for us.

I hope we have managed to inspire others to try the same.  As always, if you have feedback or suggestion on how we can do this better, I would love to hear from you.