Super Chemex Bot

At Contentful, we drink a lot of coffee. And while there are many ways to brew coffee, Chemex is one of our favorites. It’s fun, simple, and you can make enough for the whole team.

 Chemex container with pourer

Back when everyone sat in one small office, we had an actual doorbell in the kitchen. Whenever someone made a fresh batch of Chemex, they’d ring the bell, and regardless of where you were in the office, you could hear it. But over the past two years, the company has grown, and that solution didn’t scale.

Our Berlin office spans multiple floors and adjacent buildings, with each floor featuring its own kitchen and a dedicated Slack channel for communication. We wanted a button in every kitchen to post a notification to that area’s Slack channel when there's coffee available.

We got our hands on some AWS IoT buttons—the programmable version of Amazon Dash buttons—and decided to hack a solution. The basic idea was to have the button trigger an AWS Lambda function that would then emit a message to the right channel using the Slack API. We stored our configurations, such as mappings between buttons and Slack channels, along with the content of the messages in Contentful. This allows anyone to change these around without pushing code each time.

The whole data flow looks as follows: when someone presses one of our Chemex buttons in the office, a message gets passed to AWS IoT. Based on the serial number, AWS IoT triggers our Lambda function. The lambda function fetches the configuration from Contentful and posts a message to Slack.

The first step was to register each Dash button with the AWS IoT platform. This process involved connecting a computer to the button’s wifi network and following the wizard to generate a certificate and private key. Then we copied those onto the button.

Next, we switched over to the Lambda dashboard in AWS and created a new function. We selected Node.js as the runtime so we could use the Slack and Contentful JavaScript SDKs. Under Triggers, we added each configured button as a trigger to this function. Now our Lambda function will receive the button press event as an argument. CloudWatch shows us that when the button is pressed, our function is called. Awesome!

After that, we set up the data in Contentful. We created two content types—Configuration and Message. Each configuration entry contains the button’s serial number and its respective Slack channel's name. Each message contains the text—and our bot will pick one at random to post on the Slack channel. The entries look like so:

There's a field for the "Device Serial Number" containing an alphanumeric string and a field for the Slack channel, in this case "cofftentful".

We created our Slack application, gave it a name, description, and permissions. Then we copied the generated client ID and secret into Lambda environment variables.

We called our little helper "Super Chemex Bot" and it gave it a nice, steaming Chemex container as its avatar.

The final step was to finish writing the Lambda function. It goes something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
exports.handler = (event) => {
  // Fetch data from Contentful containing all configs and messages
  contentfulClient.getEntries()
  .then(prepareContentfulData)
  .then(({ configs, messages }) => {
    const { channel } = configs[event.serialNumber]

    // Select a message at random from the list
    const { message } = messages[Math.floor(Math.random() * messages.length)]

    // Post it to slack
    slackWebClient.chat.postMessage(channel, message)
  })
}

We pushed the code to Lambda with the AWS CLI tool, and glued the first button to the table in the kitchen of the engineering floor and went home. The next morning we announced it on Slack.

Our annoucement message read "Everyone, please meet Super Chemex Bot - our friendly bot-overlord who will now tell you when there's fresh chemex in our kitchen. Well, only if you actually press the button."

Allowing everyone to come up with new messages sparked some creativity.

Some examples of the messages Super Chemex bot posts are "rocket fuel is ready", "grab a no-fat-caramel-soy-frappecino in the kitchen..., lol, jk ;)" and "Coffee - the most important meal of the day!".

Since we deployed this to the engineering floor in April, our team pressed that one button 275 times. The average Chemex is around 900mL. That’s almost 250 liters of coffee in 4.5 months on just one floor. Not bad!

If you want your own Chemex Button, you can check out the code on GitHub. Or if you’re in Berlin, swing by our office and have a coffee with us.

Blog posts in your inbox

Subscribe to receive most important updates. We send emails once a month.