Introducing Boards.

Our Slack integration for keeping our clients inspired.

We are constantly exposed to inspirational work, a lot of which aligns with our client's needs. But we have realised that we take this for granted, and our clients don’t have the same level of exposure. What if we could share a curated selection of the best design work with our clients so they too can benefit from the same exposure?

The idea

We wanted a method to keep our clients updated with the best ideas, trends and design work. But we didn't want to make it a chore to update so when time is tight and deadlines are looming it doesn't get forgotten about. Like a lot of design studios, we live in Slack and regularly share pieces of work that have caught our eye with the rest of the team. So in a bid to minimise any additional work, we decided the method of sharing stuff had to be inside Slack.

How it works

  1. Use the shortcut menu and select Add to board

  2. Populate the Modal with all the relevant content selecting which boards you want to add it to

  3. Hit the Submit button

  4. Wait to be updated in Slack

How we built it

To make the build as efficient as possible we decided to stick to as many of our usual tools as possible, so we decided to combine Slack with a Nextjs app, DatoCMS to store the boards and hosted it on Netlify.

After a bit of back and forth with the Slack documentation, some permissions changes and a failed attempt to get ChatGPT to write it for us, we created a Next.js function that handles the Slack event where board_add is the name we have to the shortcut action.

import { NextResponse } from 'next/server';
import extractUrls from 'extract-urls';

export async function POST(request: Request, response:Response) {
  const data = await request.formData();
  const payload = data.get("payload") as string;

    const json = JSON.parse(payload);

    if(json.type === 'message_action' && json.callback_id === 'board_add'){
      //do something here

  return NextResponse.json({
    "response_action": "clear"
  }, {status: 200});

We realised the app can't access images added to a channel where the app hasn't been added, so we wrote a little check for that:

export const isUserInChannel = async (channelId: string, username: string):Promise<boolean> => {
  const user = await getUserByName(username);

  if(!user) return false;

  const members = await getChannelMembers(channelId);

  return members.includes(user.id);

export const getAllUsers = async () => {
  const response = await axios({
    method: 'GET',
    headers: {
      'Authorization': `Bearer ${process.env.SLACK_TOKEN}`

  return response.data.members;

export const getUserByName = async (name: string) => {
  const users = await getAllUsers();
  return users.find((user: { name: string; }) => user.name === name);

export const getChannelMembers = async (channelId: string) => {
  const response = await axios({
    method: 'GET',
    headers: {
      'Authorization': `Bearer ${process.env.SLACK_TOKEN}`
    params: {
      channel: channelId

  return response.data.members || [];

Slack's 3-second rule, where you have to respond within 3 seconds posed a bit of an issue. Downloading an image from Slack and uploading it to DatoCMS was definitely not going to happen in 3-seconds. So to solve this we made use of Netlify's background functions to write a function that could run in the background and keep the user updated on its progress in Slack using response_url.



Fraser Hobbs

About OHMY

OHMY is a design and technology studio that builds brands and digital products. We help established businesses and startups to give meaningful answers to their most challenging questions.