Slack won. It took over the world by storm (especially if we are talking about IT), without prisoners and with no mercy (e.g., Atlassian admitted that they do not have a chance by selling the IP and shutting down HipChat and Stride). You may or may not be a fan, you might dislike the tool itself (anyone missing the days when we all used IRC at work?), but it is definitely going to stay with us for a long time.

The commonness of that solution is dominant - even smaller groups (e.g., local meetups) are using that. Amount of connected workspaces varies per person, and for me, it hit the level of 13 connected spaces, where I am actively (daily) using 4 of them. Even though UX and UI is one of the often raised arguments in favor, there is no simple way to become offline on all those spaces at once. You need to click through all of those settings and set up your statuses.

When going on holidays, you should set those, especially putting yourself into the away mode or do not disturb to mute all notification. It is like a good practice of setting up an email autoresponder.

Recently, I thought that actually, I could utilize one of my gadgets to create an Offline Button for Slack. I pick AWS IoT Button:

AWS IoT Button Unboxing

Do not worry. I still have a full drawer. 😂 The only difference is that I can close it gently without cramming stuff up and force pushing.

Also, here you can find my slides about the following topic from the AWS Polish user group meet-up:

Problem Definition

So let’s gather our requirements:

As a Wojtek
I want to click a button
So that I will be offline on the most important Slack workspaces

Sounds easy? Let’s do it, with additional assumptions - as it is a side project anyway, I want to learn about new stuff that AWS announced recently:

  • Custom AWS Lambda Runtimes - for me, the language of choice will be Elixir.
  • Amplify Framework - which looks like a promising and efficient (in terms of execution) way when it comes to building clients that are leveraging AWS ecosystem.
  • I want to scratch the surface of the beast named AWS IoT.

Outside of that, I will use a trusted Serverless Framework as a workhorse for the AWS Lambda implementation, especially that they introduced support for custom runtimes as well.

Why serverless?

Let’s play a little and think like a builder of an actual product, why should I consider serverless for that particular implementation. I have to be pragmatic, as my resources are scarce. An obvious statement would be that I favor cost-effectiveness because serverless is cheap, scalable and so on. For that purpose it would be a good, but not the best answer. There is something which matters even more than money - it’s time. For me time to market is much more important, and serverless solutions on top of the provider’s ecosystem are blessing in that regard.

The whole project took almost 27 hours to build - including hops and obstacles that I listed below - which is good, but not impressive. It could be done much faster if the experience was smooth (my own choice to introduce some additional hurdles and test experimental stuff, as it is a side project). There is one caveat: it can be much slower if I had to learn everything from scratch. It will not be an order of magnitude, but 3-4 times slower - so it is crucial to know what are you doing and focus only on the execution.

Focus On Product And Move Tech Out Of Sight

Amazon Web Services is our core expertise. Partner with our experienced and pragmatic builders that help you innovate and build a competitive advantage to move quicker and be cost-effective with the use of AWS cloud platform.

Schedule a call with our expert

Building in such way reminds me playing with LEGO, so let’s review what we need:

Button for Slack - Serverless LEGO

Architecture and Bootstrap

As promised above, I have used for this project both Amplify and Serverless frameworks. I marked the area of influence, which library is responsible for what is surrounded on the diagram with the dashed line.

Button for Slack project Architecture

In my case, Amplify manages the user interface, which includes hosting (via S3 websites, but framework supports CloudFront with SSL as well out of the box) and authentication (which leverages Amazon Cognito, configured based on the details - no brainer it just works). I do not exaggerate if I tell you that after following series of commands I had a working and deployed front-end application with authentication back-end, ready to create additional components:

The result looks like the following:

Button for Slack - Result of Amplify Framework bootstrapping

Knowing that it is deployed and available live on production, and fully supports account creation, password resets, email verification, MFA out of the box, the result looks impressive. Especially in the context of how much work we’ve put here.

Then, after putting some effort, I have managed to come up with such “minimalistic UI”:

Button for Slack - Minimalistic UI

Yes, an editor down on the screenshot, instead of a proper form is a result of my laziness. As I planned only to validate this idea and polish it later, I think it is a good approximation. 😂

AWS IoT Button

Let’s meet our small, but the great star. AWS IoT Button is a battery-powered device, with WiFi and colorful RGB LED. It sends events via MQTT and fully integrates with AWS IoT Core service.

Did I say battery? Yes, there is one, which lasts approximately 2000 clicks, then our button fades out, and there is no way to recharge it, nor replace the battery.

How the binding process looks like then? You have to install the mobile application called AWS Iot Button Dev and go through the binding process with a wizard, which includes logging into your AWS account. Then you can change the WiFi via the same application or as it is specified in the FAQ via the manual process. During the initial call, the button will fetch and provision AWS IoT Core certificates and connect to the hub.

What if we would like to build a product from that? How can we connect the button to the AWS account programmatically? Is there any claiming mechanism? Unfortunately, this version would need to be hardwired to your AWS account before you sold that to the customers, or users would have to manually go through the binding process (or instead you need to implement a custom process on your own). If I remember correctly, AWS said explicitly that this is a prototype, and later they’ve introduced AWS IoT 1-Click and AWS IoT Enterprise Button which solve that problem - although, those are two different and distinct services and buttons are not compatible.

Anyway, after establishing successful connection, you will be able to see a following payload sent as a event:

Isn’t that beautiful? You have three fields and everything in them - device ID, information how long button will last via batteryVoltage, and type of the click, which precisely defined, and you do not have to implement debouncing on your side.

How does it look inside?

Remember when I talked about custom runtime? As Elixir is not officially supported by the AWS we need to use a community one or develop our own. There are two available - one, semi-official provided by the AWS partner AlertLogic accessible here and the example provided here. I have forked the latter one and adjusted for my requirements. Our remarks are mostly related to the way how ERTS is packaged and built, which increases the package size. By trimming the release and compiling Erlang without many unnecessary parts, we have managed to go down from 50 MB to 38 MB, which includes the application and dependencies.

You may ask: how does the custom runtime work internally? So that is an excellent question, but the answer would require much ground to cover. For this blog post we will treat it as a black box, precisely, in the same way, you would develop applications in Node.js, Python or any other officially supported programming language. We are preparing a follow-up blog post with more juicy details about how to implement a custom runtime, so if you are interested - leave us a comment down below.

I have used Serverless framework to deploy Amazon API Gateway and all six AWS Lambdas. They have the following responsibilities:

  • There are two lambdas for getting and updating user profile - by user profile I understand the status messages, including emojis, that will be set for active and away statuses and device ID of the associated AWS IoT Button. Both are protected with the use of Amazon Cognito, so only authenticated users can access that part of the API.
  • Another function is responsible for listing connected Slack workspaces for a given user.
  • Another two lambdas are used for connecting and disconnecting a new Slack workspace. Lambda responsible for connecting the new workspace is publicly available, as it will be called by Slack API during the standard OAuth2 dance.
  • Last, but not least - there is a function which handles events coming from the AWS IoT Buttons associated with the AWS account.

Are you interested in details, how Elixir worked out? It behaved pretty well. The experience was great after fighting off the initial issues. You see, every single time you choose something which is not officially supported, there must be hurdles along the way. In my case, the only and most problematic thing was related to the AWS SDK implementation in Elixir called ex-aws/ex_aws. They did not pick up provided credentials via environment variables automatically, but you have explicitly set all three variables in the configuration. It’s a minor issue, but I spent the significant amount of time, fighting unauthorized calls to the Dynamo DB because of that.

For other questions and doubts do not hesitate to ask in the comments, you can find the source code of the application here: patternmatch/button-for-slack.

Does it work?

Of course! My colleagues made fun of me the whole day that I am playing with buttons, changing statuses on Slack. It brings more fun than it looks at first sight. For the skeptics, I have prepared a following demo:


I have talked about some of the traps inside the blog post, but I have listed all of the problems I have fought here:

  1. Custom Elixir Runtimes for AWS Lambda are not that mature as we expect.
  • Package size easily approaches the limit (50 MB), without trimming you will not be able to deploy without additional work which includes layers.
  • You own it - which complicates a little bit the narration about smaller OPEX costs, as you have to maintain it, e.g., by applying security patches and so on.
  1. AWS SDK for Elixir - inconveniences and problems along the way that are related to immaturity.
  • In the long run, that code has to be generated to be successfully maintained, like in the aws-sdk-go example.
  1. Slack API - OAuth2 scopes.
  • There is a significant difference between Add To Slack vs. Sign in with Slack in terms of OAuth2 scopes, and I think Slack documentation does not do a great job there to explain it.
    • Simply put, if you want to execute actions on behalf of the user, you need to do two steps:
      • First, you install new application inside Slack workspace (with Add to Slack button), only with the permissions needed to execute those actions, leaving identity outside of scopes.
      • Then you use Sign in with Slack button which uses client_id provided for the application and only the scopes related to identity management - after the standard OAuth2 flow you will get the token, with which you can execute actions on user behalf.
  1. API Gateway CORS in Lambda-Proxy mode sucks when using Authenticators.
  • Oh boy, this one took me a tremendous amount of time. In general, there are two modes how you can integrate AWS Lambda with Amazon API Gateway, and I prefer the Lambda-Proxy (you can read about differences here). The problem is that in case of using custom authorizers, as I do via Amazon Cognito, you need to define DEFAULT_4XX answers for API Gateway on your own (you also need the DEFAULT_5XX, when your logic will bail out). You need to have that, in case of any authentication problems. However, that requirement is not documented anywhere, and I found that after hours of battles with CORS, cursing JavaScript and browsers. It turned out it was a problem with the API Gateway.
    • If you will think about it, it looks obvious - in case of invalid Authorization header (e.g., unauthenticated request) that event will not invoke attached Lambda at all. It means, that API Gateway needs to return proper HTTP headers as a default answer, to satisfy CORS.
    • You can find that solution here: DEFAULT_4XX answer inside serverless.yaml.
  1. AWS IoT Button is not AWS IoT 1-Click.
  • I have got almost a heart-attack because of that, as I tried to claim a device via AWS Console inside AWS IoT 1-Click service, receiving that my device is not supported. As stated above, those are different services, and AWS IoT 1-Click supports only AWS IoT Enterprise Buttons, which allows for a programmatic claims via API.


I hope you have enjoyed the ride as much as I did. For sure, I did not exhaust the topic, and we plan to prepare a follow-up blog post about how exactly custom AWS Lambda runtimes works under the hood. If you are interested in hearing more about that, give us a shout out in the comments. Also, in case of any questions or doubts do not hesitate to ask them below.