Private message from TaskManager App

In this tutorial, we will build a Slack application to track team tasks in Airtable. Our Slack app will ask team members what they’re working on and record their responses in Airtable.

We’ll program our app to run once a week on Monday, but you’ll learn to configure it to run daily, hourly and minutely if you’d like. You’ll also have the option to customize the message that your Slack app sends.

What We’ll Learn:

  • How to make Slack API requests to retrieve members’ information via a slash command and store information in Airtable.
  • How to schedule code to run weekly, daily, minutely, and hourly with Autocode.
  • How to use Slack and Airtable APIs on Standard Library to build custom applications.

After this tutorial, you will have all the tools you need to build a Slack app with Airtable as a backend datastore. Build a custom tool that fits your teams’ needs and manage all teamwork within Slack!

What You’ll Need Beforehand

  • 1x Slack workspace
  • 1x Airtable free account Airtable is a combination of a spreadsheet & database.
  • 1x Standard Library free account — A platform for building and deploying APIs, linking software tools, powering Slack apps, automating tasks, and more.

How the Slack App Works

After installation, you will run a /cmd getmembers from your Slack workspace. This command will trigger a webhook hosted on Standard Library to populate an Airtable base with all the active members in the workspace. You can then select the team members you’d like to have your app message to track tasks.

/cmd getmembers retrieves active users

Populated Airtable via Slack using /cmd getmembers

The Slack app will search through the Dates table to find the current date where the wasSent field is unchecked and thestatus field is pending.

If these conditions are met, then your Slack app will send a private message to each of the users in the Members table. Once all messages are delivered it’ll check off the wasSent field.

Private message to all listed members in Airtable

wasSent field is automatically checked off

A user can then reply with the tasks their working on by invoking the command /cmd record <text> for example:

/cmd record Content: Build a Custom Slack + Airtable Task Management Bot

members can reply with /cmd record

The Replies table will automatically populate with the respondent’s reply and link to the user’s real_name on Members table and Date.

Member replies are stored in Airtable

Prepare your Airtable Base

Once you log in to Airtable, copy this base by clicking the link and select the workspace you’d like to add the base to:

https://airtable.com/addBaseFromShare/shrDjE80mXmDexLfM?utm_source=airtable_shared_application

Install Slack App Using Autocode

Now copy my Slack app template using this link:

https://autocode.stdlib.com/github/JanethL/TaskManagerBot/?filename=README.md

Sign in or create a free account. If you have a Standard Library account, click Already Registered and sign in using your Standard Library credentials.

Give your project a unique name and select Start API Project from Github:

Fork Code from Github

Autocode automatically sets up a project scaffold to save your project as an API endpoint.

To deploy your API to the cloud, navigate through the functions/events/scheduler folders on the left and select weekly.js file.

Enter weekly.js

Click Accounts Required

Select the red Account Required button, which will prompt you to link Slack and Airtable accounts.

Link Resources

Let’s start by linking a Slack Resource. Select Link New Resource to link a new Slack app.

Install Standard Library App

You should see an OAuth popup. Select the workspace you’d like to install your Slack app in, and click Allow.

Click Allow

Give your Slack app a name and image if you’d like.

Select Finish

Select Finish. Next select Link Resource to connect to your Airtable account by following the instructions on the modal to retrieve your Airtable API Key and select finish.

Link Airtable

Find and select your base and click Finish.

Pick an Airtable base

The green checkmarks confirm that you’ve linked your accounts properly. Select Finished Linking.

Select Finished Linking

You’re ready to deploy your Slack App! Select Deploy in the bottom-left of the file manager.

Deploy App

Continue

Your Slack App is now available for use in the Slack workspace you authorized it.

The final step is to populate the Members Airtable with active members in your workspace by running /cmd getmembers from any channel in Slack. You can then select the team members you’d like to have your app message.

🙌🏼 Your Slack task manager is all set!

It’ll query your Airtable for user_id to send users a private message in the #general channel every Monday at 8:00 am PST.

Bonus: Dive into the Code 👩🏻‍💻

Slack Slash Command: /cmd getmembers

When a user type’s /cmd getmembers, a webhook on Standard Library is triggered. Navigate through the /functions/slack/command folders and select /getmembers.js file.

getmembers.js file

const lib = require('lib')({  token: process.env.STDLIB_SECRET_TOKEN});

/** * An HTTP endpoint that acts as a webhook for Slack command event * @param {object} event * @returns {object} result Your return value */module.exports = async (event) => {

 // Store API Responses  const result = {    slack: {}  };

 console.log(`Running [Slack → List all users]...`);  result.slack.returnValue = await lib.slack.users['@0.3.32'].list({    include_locale: true,    limit: 100  });

 const activeMembers = result.slack.returnValue.members.filter(members => members.is_bot == false);

 console.log(`Running [Airtable → Insert Rows into a Base]...`);  for (var i = 0; i < activeMembers.length; i++) {    await lib.airtable.query['@0.4.5'].insert({      table: `Members`,      fieldsets: [{        'real_name': `${activeMembers[i].profile.real_name}`,        'user_id': `${activeMembers[i].id}`      }]    });  }

 return result;

};

The first lines of code imports an NPM package called “lib” to allow us to communicate with Slack and Airtable APIs on top of Standard Library:

const lib = require(‘lib’)({token: process.env.STDLIB_SECRET_TOKEN});

Lines 5–9 is a comment that serves as documentation and allows Standard Library to type check calls to our functions. If a request does not supply a parameter with a correct (or expected type) it will return an error. This specific API endpoint is expecting information about the event inside an {object} and will return response data inside an {object}.

Line 10 is a function (module.exports) that will export our entire code found in lines 8–34. Once we deploy our code, this function will be wrapped into an HTTP endpoint (API endpoint) and it’ll automatically register with Slack. Every time the slash command \cmd getmembers is invoked, Slack will send that event’s payload of information for our API endpoint to consume.

Line 13 const result = {slack: {}} declares a result variable to store response data in an object from the following request to Slack API.

Lines 18–21 make a request to lib.slack.users[‘@0.3.32’]. to retrieve a list of users’ information and stores the response data inside our result variable as result.slack.returnValue. You can view the result object by selecting Run Code. When you select Run Code Autocode will simulate a slash command event on Slack, and the response will be visible in the logs right below the Run Code button. API Responses are highlighted in green.

data is stored inside result.slack.returnValue

Line 23 defines a variable activeMembers and filter through the list to retrieve only active members from result.slack.returnValue.members. We will select users’ real_name, and user_id from this response and pass this data into our subsequent Airtable API request.

Lines 26–34 use a for loop to iterate through activeMembers. The for loop grabs all users’ real_name, and user_id and maps those to the Airtable fields: real_name, user_id via a post request to lib.airtable.query[‘@0.4.5’].

Scheduled Slack Messages

To open up the file running the weekly messages from your Slack app, navigate through the /functions/events/scheduler folders, and select /weekly.js file on your Autocode project.

const lib = require('lib')({  token: process.env.STDLIB_SECRET_TOKEN});

/** * An HTTP endpoint that acts as a webhook for Scheduler daily event * @returns {object} result Your return value */

module.exports = async () => {

 // Store API Responses  const result = {    airtable: {},    slack: {}  };

 console.log(`Running [Airtable → Retrieve Distinct Values by querying a Base]...`);  result.airtable.distinctQueryResult = await lib.airtable.query['@0.4.5'].distinct({    table: `Members`,    field: `user_id`,    limit: {      'count': 0,      'offset': 0    },  });

 const momentTimezone = require('moment-timezone'); // https://momentjs.com/timezone/docs/  let date = momentTimezone().tz('America/Los_Angeles'); //sets the timezone of the date object to 'America/Los_Angeles'  let formatted_date = date.format('YYYY-MM-DD');

 console.log(formatted_date);

 console.log(`Running [Airtable → Select Rows by querying a Base]...`);  result.airtable.selectQueryResult = await lib.airtable.query['@0.4.5'].select({    table: `Dates`,    where: [{      'Date__is': formatted_date,      'wasSent__is_null': true,      'Status__is': `pending`    }],    limit: {      'count': 0,      'offset': 0    }  });

 console.log(result.airtable.selectQueryResult);

 console.log(`Running [Slack → retrieve channel by name ]}`);  result.slack.channel = await lib.slack.channels['@0.6.6'].retrieve({    channel: `#general`  });

 console.log(result.slack.channel);

 console.log(`Running [Slack → Create a new Ephemeral Message from your Bot]...`);  for (var i = 0; i < result.airtable.distinctQueryResult.distinct.values.length; i++) {

   await lib.slack.messages['@0.5.11'].ephemeral.create({      channelId: `${result.slack.channel.id}`,      userId: `${result.airtable.distinctQueryResult.distinct.values[i]}`,      text: `What tasks are you working on for week of ${result.airtable.selectQueryResult.rows[0].fields.Date}? \n Please reply using \/cmd record:`,      as_user: false    });  }

 console.log(`Running airtable.query[@0.3.3].update()...`);  result.updateQueryResult = await lib.airtable.query['@0.4.5'].update({    table: `Dates`, // required    where: [{      Date: `${result.airtable.selectQueryResult.rows[0].fields.Date}`    }],    fields: {      wasSent: true    }  });

 return result;

};

The weekly.js code will run once a week on Monday at 8 am PST.

Lines 19–26 make a request to lib.airtable.query[‘@0.4.5’] API to retrieve user_id from the Members table and stores the results for this query in result as result.airtable.distinctQueryResult.

Lines 28–30 we’re using moment-timezone npm package to format the date YYYY-MM-DD so that when we query the Airtable Dates table, we can identify and match the row with the current date.

Lines 35–46 make another request to lib.airtable.query[‘@0.4.5’] to query the Dates table. It’s looking for rows where Date is equal to the current date in format YYYY-MM-DD with wasSent empty, and pending status. If the criteria is met it returns the row and stores it in result.airtable.selectQueryResult where we will access it to build the rest of our workflow. The Airtable API returns that information in a JSON object, which can be viewed from Autocode by logging the response: console.log(result.airtable.selectQueryResult);(Line 45). When you test run your code, you will view the logged response data right below the Run Code button highlighted in blue.

Lines 51–53 make a request to lib.slack.channels[‘@0.6.6’] to retrieve information for #general channel and stores the response data in result.slack.channel. We log the response using: console.log(result.slack.channel); (Line 52).

Slack channel information stored in result.slack.channel.

Lines 58–66 uses a for loop to iterate through the data stored in result.airtable.distinctQueryResult.distinct.values. The for loop grabs all user_id’s and sends a private message to all via lib.slack.messages[‘@0.5.11’].ephemeral.create. We identify the channel we want to post the private message in by setting the value for channelID to: ${result.slack.channel.id}.

Lines 69-77 update our Airtable once our Slack app has sent out the message to users by calling lib.airtable.query[‘@0.4.5’].update and setting the wasSent field to true.

  • Note when test running the code make sure all users in Airtable are in the channel where you’ll be sending them the private message.
  • To test your Slack app make sure the Dates table has one row with the current date and the wasSent field is unchecked.

Slack Slash Command: /cmd record

const lib = require('lib')({  token: process.env.STDLIB_SECRET_TOKEN});/** * An HTTP endpoint that acts as a webhook for Slack command event * @param {object} event * @returns {object} result Your return value */module.exports = async (event) => {  // Store API Responses  const result = {    slack: {},    airtable: {}  };

 console.log(`Running [Airtable → Select Rows by querying a Base]...`);  result.airtable.selectDate = await lib.airtable.query['@0.4.5'].select({    table: `Dates`,    where: [{      'wasSent__is': true,      'Status__is': `pending`    }]  });  console.log(`Running [Airtable → Select Rows by querying a Base]...`);  result.airtable.selectUser = await lib.airtable.query['@0.4.5'].select({    table: `Members`, // required    where: [{      'user_id': `${event.user_id}`    }],  });  let user = result.airtable.selectUser.rows[0].id;  let date = result.airtable.selectDate.rows[0].id;

 console.log(user);  console.log(result.airtable.selectDate);  console.log(result.airtable.selectUser);

 console.log(`Running [Airtable → Select Rows by querying a Base]...`);  result.airtable.QueryResult = await lib.airtable.query['@0.4.5'].select({    table: `Replies`, // required    where: [{      'Respondent__is': user,      'Date__is': date    }]  });  console.log(`Running [Airtable → Insert Rows into a Base]...`);  if (result.airtable.QueryResult.rows.length === 0) {    result.airtable.insertQueryResults = await lib.airtable.records['@0.2.1'].create({      table: `Replies`,      fields: {        'Reply': `${event.text}`,        'Respondent': [user],        'Date': [date]      }    });

 }

 return result;};

To open the file for /cmd record navigate through /functions/events/slack/command folders and select /record.js file.

This file behaves like the getmembers.js file. When a user submits /cmd record a webhook will be triggered, and the code inside this file will run.

First, an API call is made to query Airtable for rows in Dates table where wasSent is checked off and Status is pending. Then the Members table is queried to find the row where the user_id matches the user_id from the event. Next, the Replies table is queried to check if the user has submitted a response for that date. If the user hasn’t submitted an answer for that date, then our user’s reply is added in a new row and is linked to the appropriate field in Members and Dates table.

To test run the code in this file, you’ll need to edit the test parameters sent to this API. The code instructions inside this API are expecting a user id which is found in your Airtable. Select Edit Payload and add a user_id value found in your Airtable.

Edit Payload

Edit user_id

Select Save Payload and run your code.

Your tables will populate with the Reply from the test event.

Changing the Time Interval

To change the time interval, navigate through functions/events/scheduler folder and open the weekly.js file.

You will notice that the code is programmed to run at 8:00 am America — Los Angeles time. To change the time your Slack app sends messages, right-click on the weekly.js file and select Clone API Endpoint.

Use the API Wizard to select Scheduler as your event source and then select the frequency you’d like to have your Slack app message your team members. Select the time and timezone, and make sure to hit the Save Endpoint button so that you can edit the code highlighted in green.

  • Delete the extra const result = {}; statement on line 10.
  • Delete the extra return result; statement on line 81.
  • Select Save Endpoint.

Autocode automatically saves your new endpoint file as daily.js inside the /scheduler folder. Delete your first file if you don’t want your app to run weekly.

Make sure to deploy your app again for the changes to take effect by selecting Deploy in the bottom-left of the file manager.

Support

🙌🏼 And That’s all!

You now have all the tools you need to build a custom Slack app with Airtable as a backend!

If there’s anything in the article you notice could be improved let me know!

I would love for you to comment here, e-mail me at Janeth [at] stdlib [dot] com. Let me know if you’ve built anything exciting that you would like Standard Library team to feature or share — I’d love to help!

Tags

What's your story?  Tell us how you use no-code
Something wrong?