How Lattice built an employee feedback app in Slack

A behind the scenes look at how Lattice used the Slack API

Byron Yang
he/him
Software Engineer

Feedback is powerful. Constructive feedback helps employees understand how they can get better, while positive praise motivates employees and makes them feel good about the work they’ve accomplished.

Lattice helps companies build a culture of feedback by bringing feedback workflows into places where employees are already working like Gmail and Slack.

The following post explains how Lattice’s design and engineering team approached the challenge of building an app for Slack and integrating our existing web application with the Slack API.

Understanding the problem space

Slack is a great avenue for real-time conversations so it’s a logical place to give real-time feedback. Building an app within Slack means we need to ensure our product capabilities can align with Slack’s constraints. Our feedback product web experience is fairly comprehensive and will continue to expand. So we asked ourselves: how might we account for the following workflows and notifications to build a foundation for us to iterate from?

  • Give and receive private feedback.
  • Give and receive public praise.
  • Request feedback about myself or a direct report.
  • Receive a notification when I’ve been given feedback.

The first step was to understand how users interface with Slack apps. We broke down the key methods available to us and wrote out the pros and cons to each direction.

Slash commands: Single keyword workflow activation

Example: /praise.

Great for quick workflows of the power user but requires more recall than retention.

This is an example of image caption

Pros

  • Quick workflows for power users.

Cons

  • Less technical employees will have trouble interacting with the app.
  • Slash commands can overlap with other apps and the most recently installed command will take priority.

Compound slash commands: Multi word syntax for taking a specific action

Example: /lattice praise @jared You’re doing a great job!

Allows for many actions off of a single slash command family, however the syntax can become long and prone to user errors.

This is an example of image caption

Pros

  • Single unique slash command
  • Powerful once you know the command syntax

Cons

  • Recall based interaction patterns have trouble being widely adopted especially by less technical employees.

App channel chat bot

Private conversation based on keyword commands. App channel allows for more back and forth in a private setting than a slash command accessed in any channel.

This is an example of image caption

Pros

  • Users are more comfortable writing sensitive feedback in a private channel than a slash command accessed in any channel.
  • Bot can help users find the command they are looking for by providing a clickable list of actions.

Cons

  • Not as readily available as a slash command.
  • Can’t fulfill complex workflows in a single command.

To meet the requirements of our current feedback app functionality now and in the future we decided to go for the app channel chat bot direction. Lattice is built for all employees of a given organization which means we’re building for a wide range of technical skills.

Our next step was to mock up some workflows to get aligned with engineers and test with real users. We used Figma to create some clickable prototypes.

This is an example of image caption

After testing our prototypes, exploring the capabilities of the Slack API, and aligning with the engineering team, we began coding the app.

From wireframes to code

JavaScript is our language of choice at Lattice and powers our entire stack. Our web application is written in React, our mobile app is React Native, and our backend services and Lambda functions run on Node.js. We find this helps us share tooling across our entire stack and ship new features quickly.

We have one central monolith which houses our business logic and writes data to our PostgreSQL database. This service exposes a GraphQL API which our frontend applications and other services use to query data and commit mutations.

We contemplated whether we should keep all our Slack integration code inside this monolith (where our business logic for giving feedback lives) or build a dedicated service for it. From our designs and research about Slack’s API, we knew we would need to continuously listen and respond to events from Slack and would need to support sending bursts of notifications to Slack.

We decided to create a new service just for communicating with Slack, so we could keep related functionality all in one place, auto-scale the service independently from the monolith, and decouple deploying changes to our feedback business logic and changes to Slack app workflows. It was a natural choice for us to write this new service in JavaScript so that the development experience would be familiar to the engineers on our team and we could re-use existing code for setting up a new service.

Our engineering insights

The asynchronous nature of Node.js was particularly helpful for this type of event-driven service. When a Slack user interacts with our bot, Slack sends us an event, we acknowledge to Slack we received their request, and then we continue to process the event — all in the same function. In other languages or runtimes, you might need to send the event payload to a queue and process it somewhere else in your code.

Our event handler for most of our workflows ended up looking similar to this:

And by using the same GraphQL API that our web and mobile apps use, we were able to create similar experiences to what’s in our product inside Slack without having to write any custom endpoints for our new service. We treated our new service just like another client of our API.

React developers who use GraphQL may be familiar with using a client library like Relay or Apollo to make requests and cache data. We weren’t able to use something like that here since our code lives entirely on the server. But we found success in building our own lightweight client similar to graphql-request to reduce boilerplate in making calls to our API.

In GraphQL, there is a convention of having a “Viewer” type represent the person who is logged into your application and performing actions. We found value in translating this concept when working with the Slack bot user channel. When we receive a message.im event, we take the Slack ‘user_id’ in the event payload, lookup that user’s information in Lattice, and pass that information along to the rest of our event handler. This allows us to not only know which user in Slack is interacting with our bot but also lets us enable or disable features and change message copy depending on that person’s role or permissions inside Lattice (eg. if they are a manager or an individual contributor).

To create this bridge between a Slack user and a Lattice user, we see if we have a Lattice user with the same email as the Slack user interacting with our bot. We use users.info to find a Slack user’s email and users.lookupByEmail to find a Lattice user’s Slack user. Since we need to bridge these accounts every time a Slack user interacts with out bot, we found success in caching this relationship between a Slack user’s id and a Lattice user’s id, so our bot user can be able to respond quickly.

We touched almost all of the features available in Slack’s API in this project, from slash commands to interactive messages to dialogs. It was a fun engineering challenge, and with a working Slack app, it was time to share our work with real users to get feedback.

Getting user validation

We tested our early versions of the app with folks of varying technical backgrounds and understanding of our current feedback functionality in Lattice. Here’s what we learned:

1) Notifications coming in are hard to distinguish quickly.

We built out a color scheme for a user to better discern the nature of the message. We don’t expect users to memorize this but noticed that the consistent pattern appeared to better organize the information from the actions.

This is an example of image caption

2) One way conversations are creepy.

Nobody likes sending text messages without delivery confirmation. Repeating the feedback you’ve written back to you gives you confidence that the message was received.

This is an example of image caption

3) Some users will mistype commands or forget them altogether. We added a catch-all message to get a user back on track.

We refined the app based on user feedback and shared with our Lattice customers. But this isn’t the final iteration. We have so much more planned and as always, we plan to build it alongside our amazing customers.

This is an example of image caption

Shipping and Sharing

The end result of this project was the first performance management solution that’s built within Slack. We launched the app with a blog post, email to customers, a booth at Slack’s Frontiers conference, and, of course, an emoji GIF.

This is an example of image caption

With the Lattice app, employees can give each other feedback anywhere in Slack.

The feedback gets stored in Lattice for future conversations, and if Public Praise, posted to a public channel. We’re also taking advantage of the Slack API to reduce email fatigue, and with a new home to send notifications, we are expanding our Slack integration to our other products like performance reviews. Notifications get pushed directly into Slack, so more employees can complete their reviews by being notified where they work.

As Lattice continues to expand our product suite, we’ll keep building on the Slack API to make performance management easier for all employees.

To learn more about Lattice, click here and schedule some time to talk with a product specialist.

Thanks to Bill Dybas, Elliot Dahl, and Ryan Hinshaw for writing this article and building the Lattice app.

more articles
About Lattice

Our mission is to make work meaningful

Lattice works with People teams across the globe to turn employees into high performers, managers into leaders, and companies into the best places to work.

Join us