Programming

Setting up our Help Scout branding

We have been using the excellent Help Scout support and knowledgebase system here at my startup for many years, and recently, the customer success team has been going through and updating many articles that were out of date, and adding new articles for new features and enhancements that we have made recently. (I have posted in the past about how we linked up Help Scout to our SaaS app to enable support team members to see critical details on our customers when talking to them)

While working on the knowledgebase with the team, I thought that our look and feel of the help site didn’t really fit well with the bright and colourful branding of the rest of our web assets (i.e. our website and our blog).

Here is what the Help Scout main help page used to look like:

And here is how it looks now:

A million percent improvement, right? Now we use the same iconography and brand colors across our entire help site as well, which helps to tie everything together.

Once our design team got together to investigate how to improve things on Help Scout, we actually found out how easy it was. You see, Help Scout allows you to upload your own custom CSS style sheet to their server on your control panel. Just go to Manage -> Docs from your top menu, then choose your Document Library, then choose ‘Custom Code’ from the side menu.

Here you can upload your custom CSS.

HelpScout Upload Stylesheet.png

But what does that Custom CSS look like? Well, it can be as simple or as complicated as you like, but guess what? As part of our spirit of giving back to the community, we have released a custom CSS file right here on this post so that you can use it as a template for creating your own style. Completely free. We’d love to hear how you have used it to improve your own Help Scout documentation pages.

So feel free to download the custom css template and modify it to suit your own needs. Here are some hints and tips for you:

Choose Your Branding Colors

Just go through the CSS template we provided, and set your own preferred corporate colors for things like the category headings (line 89) and the list of articles under each category (line 99).

In our sample, I have just chosen some random color collection from Coolors as an example, so feel free to replace them as needed.

You can also change the button colors (line 396) and article heading colors (lines 320, 327 and 334) etc.

Set Your Category Images

This is probably the trickiest part of setting up your CSS - getting those cool image icons just above your category headings on the main page that all your customers will land on. It actually isn’t that hard (perhaps just a little tedious).

First, you will need to host all the images you need on a server somewhere on the internet. Help Scout cannot host your images on their system unfortunately, so you need to find somewhere like Github Pages, or set up a spot on your own webserver, or use Amazon S3 like we do to host your images.

Then, once done, you will need to find out the Category IDs that Help Scout gives each of your help categories. Every time you create a new category in Help Scout, they will allocate it a 3 digit unique code. How do you find out that code? Well, the easiest way is to simply browse that category in your knowledgebase:

HR Partner Help Categories.png

In the above example, I have gone to the ‘Custom Forms’ category, and by looking at the URL bar in my browser, I can see that the category URL is ‘150-custom-forms’, hence it has the ID ‘150’.

So, what I need to do in my CSS file is to create a ‘CSS Selector’ block which specifies the category ID like:

carbon.png

Then within that block, specify the background-image CSS property with the location of the image icon file on your hosting server. See lines 214 onwards within the sample CSS for what it looks like.


That is really about it! If you are a CSS guru, you can feel free to modify anything else you like in that file. As I said before, we’d love to see what you come up with. I guess even the Help Scout crew were happy with what we did given their response when I posted the results on Twitter last month…


Get your copy of the Help Scout Custom CSS Branding template here (right click and select ‘Save As…’). Have fun!

Disclaimer: CSS template is provided ‘as is’. No warranty expressed or implied. No guarantee as to the suitability for purposes is given. You use the CSS script and associated code at your own risk.

UPDATE: We have now embedded the CSS right here on this post as well:

/* Custom Help Scout StyleSheet - by Devan Sabaratnam (founder of HR Partner - www.hrpartner.io) */
/* Color pallette chosen from https://coolors.co/54494b-7e8287-9da39a-b98389-db2955 */

@import url('https://fonts.googleapis.com/css?family=Open+Sans:100,200,300,400,400i,500,700');
/* */

body {
    background: #fbfbfc;
}
/* Home Page Title */

#docsSearch h1 {
    font-size: 32px;
    font-weight: 300;
    text-align: center;
    margin-top: .4em;
    font-family: "Open Sans", sans-serif;
    color: #222256;
    padding: 10px;
}
.navbar .nav li {
    display: inline-block;
    float: none;
    font-family: "Open Sans", sans-serif;
    font-size: 13px;
    letter-spacing: .1em;
}
.navbar .nav li a, .navbar .icon-private-w {
    font-size: 14px;
    font-weight: 300;
}

/* Header Size */

a.brand > img {
    max-width: 100%;
    vertical-align: middle;
    border: 0;
    height: 60px;
    margin-left: 15px;
    width: auto;
}

.navbar .navbar-inner {
    /* background: #ffffff; */
    height: 60px;
}
.navbar .navbar-inner .container-fluid {
    padding: 0;
    height: 60px;
}
.navbar .brand {
    float: left;
    display: block;
    padding: 0px;
    margin-left: -20px;
    font-size: 20px;
    font-weight: 200;
    color: #777;
    text-shadow: 0 1px 0 #fff;
}
/* Home Page Search Bar Background */

#docsSearch {
    background: #ffffff;
    border-top: 1px solid #dadada;
    margin-bottom: 3em;
    padding: 1.5em 0;
}
/* Search Input Box */

input.search-query {
    padding-right: 14px;
    padding-right: 4px \9;
    padding-left: 14px;
    padding-left: 4px \9;
    margin-bottom: 0;
    -webkit-border-radius: 15px;
    -moz-border-radius: 15px;
    border-radius: 15px;
    font-family: "Open Sans",
    sans-serif;
    font-weight: 100;
    letter-spacing: 1px;
}
/* Home Page Category Title*/

.category-list h3 {
    color: #db2955;
    font-size: 20px;
    font-weight: 400;
    line-height: 1.3em;
    font-family: "Open Sans", sans-serif;
}

/* Category Styles */

.category-list .category p {
    color: #7e8287;
    font-family: "Open Sans", sans-serif;
    font-weight: 100;
    font-size: 15px;
    letter-spacing: 0.25px;
}
.category-list .category {
    background-color: #fff;
    background-position: top 20px center !important;
    background-repeat: no-repeat !important;
    background-size: 100px auto !important;
    box-shadow: 0 7px 4px -5px rgba(0, 0, 0, 0.05);
    box-sizing: border-box;
    min-height: 183px;
    padding: 120px 20px 15px;
    position: relative;
    width: 31.5%;
    transition: all 0.2s;
}
.category-list .category:hover {
    text-decoration: none;
    background: #f7f7f9;
}



.collection-category h2 {
    font-weight: 500;
    margin: 0 0 20px;
    text-align: center;
    padding-left: 0%;
    font-family: "Open Sans", sans-serif;
    /* text-transform: uppercase; */
    color: #7e8287;
    font-size: 22px;
    letter-spacing: 1px;
}

.collection-category h2 a {
    color: #3db890;
}

.collection-category .category-list {
    margin: 0 0 4em;
    text-align: center;
}



/* Home Page Link Style */

#serp-dd .result a, #serp-dd .result>li.active, #full-Article strong a, .collection a, .contentWrapper a, .most-pop-articles .popArticles a, .most-pop-articles .popArticles a span, .category-list .category .article-count, .category-list .category .article-count, .contentWrapper a {
    font-weight: 500;
    letter-spacing: .25px;
    color: #3db890;
    margin-top: 15px;
    text-transform: capitalize;
    text-decoration: none;
    font-family: "Open Sans",
    sans-serif;
    font-weight: 500;
}
#serp-dd .result a:hover, #serp-dd .result>li.active, #full-Article strong a, .collection a, .contentWrapper a, .most-pop-articles .popArticles a, .most-pop-articles .popArticles a:hover span, .category-list .category .article-count, .category-list .category:hover .article-count, .contentWrapper a {
    font-weight: 500;
    letter-spacing: .25px;
    color: #3db890;
    margin-top: 15px;
    text-transform: capitalize;
    /* text-decoration: underline; */
    font-family: "Open Sans",
    sans-serif;
    font-weight: 500;
}
/* Home Page Search Button */

#searchBar button {
    color: #fff;
    text-shadow: 0 0px 0px rgba(255,255,255,.0);
    background: #03d3d5;
    border-radius: 0 5px 5px 0;
    border: 1px solid #b98389;
    font-size: 18px;
    padding: 0 1.5em;
    height: 50px;
    position: absolute;
}
#searchBar button:hover {
    background: #2dd3fd;
    text-shadow: 0 0px 0px rgba(255,255,255,.0);
    border: 1px solid #2ad7d7;
}
input, button, select, textarea {
    font-family: "Open Sans",
    sans-serif,
    "Helvetica Neue",
    Helvetica,
    Arial,
    sans-serif;
}


/* Category Images */

/*
  Here is where you determine the images to be used just above each section of your Help Scout main page.
  You have to create one #category-xxx CSS selector for each separate category in your Help Scout collection.
  To get your category xxx number, simply visit the relevant category in your Help Scout docs and
  check the URL bar for the 3 digit category number.

  Then you have to specify an image located on a shared server somewhere on the internet that can
  serve up the images so Help Scout can display them.
*/


/* Getting Started (use your actual category names here in the comment to make it easier */
#category-123 {
    background-image: url(https://storage.myhostingprovider.com/public/images/123-getting-started.png);
}

/* Setup Users */
#category-456 {
  background-image: url(https://storage.myhostingprovider.com/public/images/456-setup-users.png);
}

/* Uploading Files */
#category-223 {
  background-image: url(https://storage.myhostingprovider.com/public/images/223-uploading-files.png);
}

/* etc... keep going and add one CSS ID selector for each category. */



/* Side Bar Styles */

#sidebar .nav-list a {
    display: inline-block;
    color: #4333d6;
    font-size: 14px;
    padding: 6px 15px 6px 0;
    line-height: 20px;
    margin-left: 0;
    font-family: "Open Sans",
    sans-serif;
    font-weight: 300;
}
#sidebar .nav-list .active a, #sidebar .nav-list .active a:hover, #sidebar .nav-list .active a:focus {
    font-weight: 500;
    color: #06b9dd;
    background: 0 0;
    text-shadow: none;
    text-decoration: underline;
}
#sidebar h3 {
    text-transform: uppercase;
    font-size: 16px;
    color: #7e8287;
    font-weight: 400;
    margin-bottom: 4px;
    font-family: "Open Sans",
    sans-serif;
    letter-spacing: 2px;
}
/* Article Styles */

#main-content {
    background: none;
    float: right;
    margin-bottom: 2em;
    padding: 32px 0 0 28px;
}

#fullArticle img {
    display: block;
    margin: 1em 0 2em;
    padding: 4px;
    border-radius: 4px;
    box-sizing: border-box;
}

#fullArticle .title, .contentWrapper h1 {
    margin: 0 30px .5em 0;
    font-family: "Open Sans",
    sans-serif;
    color: #db2955;
    font-weight: 700;
}
#fullArticle .printArticle {
    position: absolute;
    right: 46px;
    top: 40px;
}
#fullArticle, #fullArticle p, #fullArticle ul, #fullArticle ol, #fullArticle li, #fullArticle div, #fullArticle blockquote, #fullArticle dd, #fullArticle table {
    color: #7e8287;
    font-family: "Open Sans",
    sans-serif;
    font-size: 14px;
    font-weight: 300;
    letter-spacing: .01em;
}
#categoryHead .sort select {
    width: 150px;
    height: 24px;
    margin: 0;
    line-height: 24px;
    font-size: 13px;
    color: #7e8287;
    font-family: "Open Sans",
    sans-serif;
    font-weight: 300;
}
/* Style this one if you want bolded article text to have a different color */

#fullArticle strong {
    color: #7e8287;
}
#fullArticle h2 {
    font-size: 24px;
    font-family: "Open Sans",
    sans-serif;
    font-weight: 400;
    color: #db2955;
}
#fullArticle h3 {
    font-size: 20px;
    font-family: "Open Sans",
    sans-serif;
    font-weight: 700;
    color: #db2955;
}
#fullArticle h4 {
    font-size: 16px;
    font-family: "Open Sans",
    sans-serif;
    font-weight: 300;
    color: #7e8287;
    font-style: italic;
}
.contentWrapper p {
    margin-top: -4px;
    word-wrap: break-word;
    font-family: "Open Sans",
    sans-serif;
    color: #7e8287;
    font-weight: 300;
    font-size: 16px;
    letter-spacing: .01em;
}

/* Article Footers */

.articleFoot p, .articleFoot time {
    color: #7e8287;
    display: inline-block;
    font-family: "Open Sans",
    sans-serif;
    font-weight: 300;
    font-style: italic;
}

/* Page Footers */

footer p a {
    color: #7e8287;
    font-family: "Open Sans",
    sans-serif;
    font-weight: 300;
}


/* Contact Modal */

#contactModal h2, .abuse h2 {
    background: #fff;
    margin: 0;
    padding: 11px 0 10px 18px;
    font-size: 22px;
    border-bottom: 1px solid #ccc;
    border-top-left-radius: 4px;
    border-top-right-radius: 4px;
    font-family: "Open Sans",
    sans-serif;
    color: #58A4B0;
    font-weight: 300;
}
#contactModal .control-label {
    width: 110px;
    font-family: "Open Sans",
    sans-serif;
    font-size: 14px;
    font-weight: 300;
    color: #7e8287;
}

.btn {
    color: #fff;
    text-shadow: 0 0px 0px rgba(255,255,255,.0);
    background: #03d3d5;
    border-radius: 5px;
    border: 1px solid #b98389;
    font-size: 14px;
    padding: .5em;
    /* height: 50px; */
}

.btn:hover, .btn:focus, .btn:active, .btn.active, .btn.disabled, .btn[disabled] {
    color: #fff;
    text-shadow: 0 0px 0px rgba(255,255,255,.0);
    background: #8fa7c2;
  border: 1px solid #9da39a;
}

#search-query .btn:hover {
    color: #fff;
    text-shadow: 0 0px 0px rgba(255,255,255,.0);
    background: #8fa7c2;
    border-radius: 5px;
    border: 1px solid #9da39a;
    font-size: 18px;
    padding: 0 1.5em;
    height: 50px;
}

.category-list {
    text-align: center;
}

/* Fix for making homepage category gallery go smoothly from 3 to 2 to 1 column */

@media screen and (max-width: 1105px) {
    section.category-list .category {
        width: 48.2%;
    }
}
@media screen and (max-width: 760px) {
    section.category-list .category {
        box-sizing: border-box;
        margin: 0 0 20px;
        padding: 120px 20px 15px;
        width: 100%;
    }
}
/* RESPONSIVE */

@media (max-width: 768px) {
    .navbar .btn-navbar {
        margin-top: 16px;
        right: -10px;
    }
    .related {
        padding: 30px 25px 25px;
    }
    .related ul {
        margin-left: 0;
    }
    .related h3 {
        padding-left: 0;
    }
    .related ul>li a {
        margin-left: 0;
    }
}
@media (max-width: 480px) {
    #searchBar button {
        color: transparent;
        text-shadow: 0;
        background: transparent;
        border-radius: 0 5px 5px 0;
        border: 0;
        font-size: 18px;
        padding: 0 1.5em;
        height: 50px;
        position: absolute;
    }
    #searchBar button .icon-search {
        display: block;
        text-shadow: none;
        margin-top: 15px;
    }
    #searchBar button:hover .icon-search:hover {
        display: block;
        text-shadow: none;
        margin-top: 15px;
        background-color: transparent;
    }
}
@media (max-width: 480px) {
    #fullArticle .title, .contentWrapper h1 {
        font-size: 24px;
    }
    #fullArticle h2 {
        font-size: 20px;
    }
}


/* Collection Titles  */
.collection-category h2 a {
    color: #54494b;
}

.collection-category h2 {
    font-weight: 400;
    margin: 0 0 20px;
    padding-left: .65%;
    font-family: "Open Sans",
    sans-serif;
    text-align: center;
}




Tackling another design issue…

Probably the most popular module on my HR startup has to be the Recruitment module, which has taken over from the absence tracking module in the earlier days. We currently have over 500 job listings posted on HR Partner, with well over 35,000 candidates being tracked.

All in all, users love using the Recruitment area, however we have had some feedback that it is currently a little clunky to view individual applications. You have to constantly switch between the job listing view screen, which shows all applicants in the various stages of the hiring pipeline, and the main application view screen, and back. This consumes valuable clicks and precious seconds of time - especially when you have hundreds of applicants for a job.

We needed a way to allow our users to quickly step between job applications without the constant screen switching. We also didn’t want to clutter up the screen real estate too much. This was a tough problem to solve. But we had some good UX people on the team, and I wanted all of us to brainstorm and learn how we could achieve this end goal.

Now, currently on the employee view screen in HR Partner, we have this little pop up widget that allows you to skip forwards or backwards to the next 1 or 2 employees quickly without having to go back to the employee list.

Employee Switch Widget.png

Our immediate decision was to perhaps build something very similar in the Applicants view screen as well - to allow the user to jump ahead or back one or two applicants at a time. But then we though that it could be impractical, as applicants weren’t really sorted in any order, and there could be too many times when the user needed to jump from the top to the bottom of the list.

So back to the drawing board we went. We needed something that could be obvious and ‘there’ when needed, and out of sight when not needed.

Solution: We decided to build a sidebar popout widget that the user could call up when needed.

Applicant Sidebar Icon.png

That’s it. The end result was one tiny little icon that is SO easy to miss (which is why he have that popup appearing when our users first go to this screen). An infinitesimal change, but one that can have a big impact when clicked.

You see, clicking the icon slides out a narrow (around 200 pixel wide) sidebar window which shows all applicants who are in the current stage of the job pipeline as well.

Application View Sidebar 1.png

Clicking on any candidate name will open up the same Application details window, where you can see their answers to the application form, read their resumes, check their interviews and email history and fill in the scorecards and leave comments. AND - it leaves the sidebar open while doing so, so you quickly jump from candidate to candidate without interruption.

Application+View+Sidebar+2.jpg

While working on this widget, and based on our customer feedback and even our own experience with using our Recruitment module to hire internal team members, I remember that I was often circulating across 4 or 5 applicants, especially towards the tail end of the recruiting process, when we were trying to narrow down our selection to more suitable people we wanted to shortlist. I recall that as being quite tedious when I had to jump back to the list of 100 odd candidates and try and remember who our top picks were.

So we decided to include a ‘Recent’ tab to the sidebar, which shows you the last 10 candidates that were looked at. Now we can use this tab to quickly cycle between those last few candidates when discussing with the team.

Here is the new sidebar widget working how we expect it to. Doesn’t this look easier than the earlier video with all that back and forth??

Lastly, another common complaint about the Application view screen was the fact that in 90% of instances, the user would want to change the ‘Stage’ that the candidate was at while reading that screen. For example, they might want to move the candidate from the ‘Applied’ stage to the ‘Interview’ stage based on their suitability.

They could certainly do that now, however, it required changing the pull down menu, then scrolling down to the bottom of the application form and clicking ‘Save’. People were often forgetting to click ‘Save’ when they got distracted by the myriad of other information on that screen.

So we decided to introduce an unobtrusive ‘auto save’ whenever that drop down was changed. It ran instantly, in the background, and just popped up a discreet little ‘Saved!’ notification for a few seconds before disappearing again.

Application Stage Auto Save 1.png

Once again, we haven’t changed anything on screen, as our users were completely used to navigating around. We simple introduced quiet background behaviour to do the heavy lifting for them.

I guess that is one of the learning points for me, especially. Good design doesn’t mean throwing out the baby with the bathwater or making drastic changes. It is about minimising the ‘surprise factor’ for the user, but still letting them choose how they want to work and get out of their way when we have to.

It is early days yet, but I think we have gotten close with the excellent work the team have done on this.

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!

The first screen I look at every morning

Part of my morning ritual when I wake up and sit down at my iMac nowadays is to open up this particular screen on our SaaS dashboard:

HR Partner Real Time Map.png


HR Partner Real Time Map USA.png

This shows me all the users who are logged on to my HR SaaS application over the past few hours. This screen always gives me pause to think.

I remember when my startup was just in its infancy, and we only had a few dozen users around the world. When I look at this stack of pins in this real time map, I am reminded that this is no longer a side business or just a hobby business any more. We are now providing a real time critical service for people in all corners of the world (at time of writing, 1685 customers in 73 different countries).

It is a sobering thought, and I keep that in the back of my mind when I make any changes to our system now, to be sure that I don’t do anything that will result in downtime or errors.

This is also motivation for me to one day visit a lot of these countries and get to talk to our users face to face!

(BTW, this real time map widget was built using RethinkDB, Segment, MapBox and VueJS - I may do a separate blog post on the technical aspect of building this, which only took a couple of hours).