This tutorial will walk you through the creation of a Slack app that can regularly post messages to a particular Slack channel.
Features you’ll use
Quickly create an app with the correct configuration of scopes and features for this tutorial by clicking below.
Every Slack app journey begins with a few common steps. You'll need to create an app, give it some permission scopes, and then install it to a suitable development workspace.
We'll show you how to use the app settings pages and give you a brief tour of all the features you can setup for your app.
You won't get far without a Slack app to accompany you on this journey. Start here by creating an app.
Scopes give your app permission to do things (for example, post messages) in Slack. They're an essential component of any functional Slack app.
Each scope that you add to your app will be presented in a permission request dialog during app installation. When you're building an app in a development workspace that you control, you'll be able to experience both sides of this system.
Each app can use lots of different scopes, with the exact list depending on the type of access token the app will use. We favour using bot tokens for most apps, so we'll explain how to request scopes for that token type.
To choose the scopes to add to your app:
Once added, the scope list will be saved automatically. Your app will now request these scopes during installation.
If you add scopes to your app settings after installation, reinstall your app to request and grant permission for the added scopes.
To read more about scopes and permissions, check out our app authentication docs.
Your app can request any scope you want—but final say always resides with the user installing your app. A user can choose to refuse any and all installs that seem to request permissions beyond what they're comfortable granting.
When you're installing your app to your own development workspace, you can experience this process for yourself.
To install your app to your development workspace:
You'll be redirected to the OAuth permission request dialog:
As a user, you're choosing to trust the app. Is it trustworthy? Well, you're building it—hopefully, it's not too bad. After installation, you'll be redirected back to your app settings. You'll see the newly generated access token on the Install App page you're redirected to.
Access tokens are imbued with power. Your app is able to use this new access token to do practically everything that a Slack app can do.
Remember to keep your access token secret and safe, to avoid violating the security of your app, and to maintain the trust of your app's users.
At a minimum, avoid checking your access token into public version control. Access it via an environment variable. We've also got plenty more best practices for app security.
Bolt is a framework that lets you build Slack apps in a flash—available in JavaScript, Python, and Java.
Bolt handles much of the foundational setup so you can focus on your app's functionality. Out of the box, Bolt includes:
Bolt also has built-in type support, so you can get more work done right from your code editor.
Follow our guides on getting started with Bolt for JavaScript, Bolt for Python, or Bolt for Java. Or dig deeper by exploring our resources and sample code.
Your Slack app can publish text messages to public channels or private conversations just as users can.
Apps, however, can also include special visual components in their messages. These messages are designed using a framework we call Block Kit.
Composing a message involves choosing blocks from Block Kit, and laying them out to present important data. In doing so, you’ll create an array of Block Kit objects that will be used when publishing a message.
After you've finished with this step, you will be able to generate your own message payload.
All of the Slack APIs that publish messages use a common structure, called a message payload.
This payload is a JSON object that is used to define the content of the message and metadata about the message. The metadata can include required information such as the conversation the message should be published to, and optional parameters which determine the visual composition of the message.
Here's a very basic app-published message payload:
{
"channel": "CONVERSATION_ID",
"text": "Hello, world.",
}
You can go further to customize published messages by using special text formatting, or by composing a blocks
parameter to define a rich, and potentially interactive, message layout.
View our message payload reference for a list of potential payload parameters.
When an app publishes a message, the app needs to know which Slack conversation the message should be added to.
In order to find a valid Slack conversation ID, we'll use the conversations.list
API method. This API will return a list of all public channels in the workspace your app is installed to. You'll need the channels:read
permission granted to your app.
Within that list, we'll be able to find a specific id
of the conversation that we want to access. Here's an example API call:
import com.slack.api.Slack; import com.slack.api.methods.SlackApiException; import com.slack.api.model.Conversation; import org.slf4j.LoggerFactory; import java.io.IOException; public class FindingConversation { /** * Find conversation ID using the conversations.list method */ static void findConversation(String name) { // you can get this instance via ctx.client() in a Bolt app var client = Slack.getInstance().methods(); var logger = LoggerFactory.getLogger("my-awesome-slack-app"); try { // Call the conversations.list method using the built-in WebClient var result = client.conversationsList(r -> r // The token you used to initialize your app .token(System.getenv("SLACK_BOT_TOKEN")) ); for (Conversation channel : result.getChannels()) { if (channel.getName().equals(name)) { var conversationId = channel.getId(); // Print result logger.info("Found conversation ID: {}", conversationId); // Break from for loop break; } } } catch (IOException | SlackApiException e) { logger.error("error: {}", e.getMessage(), e); } } public static void main(String[] args) throws Exception { // Find conversation with a specified channel `name` findConversation("tester-channel"); } }
Code to initialize Bolt app// Require the Node Slack SDK package (github.com/slackapi/node-slack-sdk) const { WebClient, LogLevel } = require("@slack/web-api"); // WebClient instantiates a client that can call API methods // When using Bolt, you can use either `app.client` or the `client` passed to listeners. const client = new WebClient("xoxb-your-token", { // LogLevel can be imported and used to make debugging simpler logLevel: LogLevel.DEBUG });
// Find conversation ID using the conversations.list method async function findConversation(name) { try { // Call the conversations.list method using the built-in WebClient const result = await app.client.conversations.list({ // The token you used to initialize your app token: "xoxb-your-token" }); for (const channel of result.channels) { if (channel.name === name) { conversationId = channel.id; // Print result console.log("Found conversation ID: " + conversationId); // Break from for loop break; } } } catch (error) { console.error(error); } } // Find conversation with a specified channel `name` findConversation("tester-channel");
Code to initialize Bolt appimport logging import os # Import WebClient from Python SDK (github.com/slackapi/python-slack-sdk) from slack_sdk import WebClient from slack_sdk.errors import SlackApiError # WebClient instantiates a client that can call API methods # When using Bolt, you can use either `app.client` or the `client` passed to listeners. client = WebClient(token=os.environ.get("SLACK_BOT_TOKEN")) logger = logging.getLogger(__name__)
channel_name = "needle" conversation_id = None try: # Call the conversations.list method using the WebClient for result in client.conversations_list(): if conversation_id is not None: break for channel in result["channels"]: if channel["name"] == channel_name: conversation_id = channel["id"] #Print result print(f"Found conversation ID: {conversation_id}") break except SlackApiError as e: print(f"Error: {e}")
GET https://slack.com/api/conversations.list Authorization: Bearer xoxb-your-token
You'll get back a JSON object, with a channels
array containing all the public channels that your app can see. You can find your channel by looking for the name
in each object.
When you've found the matching channel, make note of the id
value, as you'll need it for certain API calls.
If your app implements shortcuts, slash commands, or uses the Events API, your app will see conversation id
s in request payloads sent by those features.
In those cases, your app can dynamically respond using the payload data to identify the relevant conversation, rather than needing to use the conversations.list
method described above.
Text within message payloads can be formatted using a special markup syntax called mrkdwn
. Consult this reference guide to learn everything you can do with mrkdwn
.
Slack provides a range of visual components, called Block Kit, that can be used to lay out info in messages.
Use Block Kit Builder to visually prototype app-publishable messages.
The Web API is the core of every Slack app's functionality. Here, we'll use it to access the API for scheduling messages.
Before we do that, familiarize yourself with the process for actually using the API. We recommend using Bolt to save yourself a lot of time and effort.
After finishing this step, you should be familiar with the basics of using the Web API in an app.
All flavors of Bolt use a similar client interface, with API parameters passed as arguments.
Here's a basic Bolt example that calls chat.postMessage
:
import com.slack.api.bolt.App; import com.slack.api.bolt.AppConfig; import com.slack.api.bolt.jetty.SlackAppServer; import com.slack.api.methods.SlackApiException; import java.io.IOException; public class ChatPostMessage { public static void main(String[] args) throws Exception { var config = new AppConfig(); config.setSingleTeamBotToken(System.getenv("SLACK_BOT_TOKEN")); config.setSigningSecret(System.getenv("SLACK_SIGNING_SECRET")); var app = new App(config); // `new App()` does the same app.message("hello", (req, ctx) -> { var logger = ctx.logger; try { var event = req.getEvent(); // Call the chat.postMessage method using the built-in WebClient var result = ctx.client().chatPostMessage(r -> r // The token you used to initialize your app is stored in the `context` object .token(ctx.getBotToken()) // Payload message should be posted in the channel where original message was heard .channel(event.getChannel()) .text("world") ); logger.info("result: {}", result); } catch (IOException | SlackApiException e) { logger.error("error: {}", e.getMessage(), e); } return ctx.ack(); }); var server = new SlackAppServer(app); server.start(); } }
Code to initialize Bolt app// Require the Node Slack SDK package (github.com/slackapi/node-slack-sdk) const { WebClient, LogLevel } = require("@slack/web-api"); // WebClient instantiates a client that can call API methods // When using Bolt, you can use either `app.client` or the `client` passed to listeners. const client = new WebClient("xoxb-your-token", { // LogLevel can be imported and used to make debugging simpler logLevel: LogLevel.DEBUG });
// ID of the channel you want to send the message to const channelId = "C12345"; try { // Call the chat.postMessage method using the WebClient const result = await client.chat.postMessage({ channel: channelId, text: "Hello world" }); console.log(result); } catch (error) { console.error(error); }
Code to initialize Bolt appimport logging import os # Import WebClient from Python SDK (github.com/slackapi/python-slack-sdk) from slack_sdk import WebClient from slack_sdk.errors import SlackApiError # WebClient instantiates a client that can call API methods # When using Bolt, you can use either `app.client` or the `client` passed to listeners. client = WebClient(token=os.environ.get("SLACK_BOT_TOKEN")) logger = logging.getLogger(__name__)
# ID of the channel you want to send the message to channel_id = "C12345" try: # Call the chat.postMessage method using the WebClient result = client.chat_postMessage( channel=channel_id, text="Hello world" ) logger.info(result) except SlackApiError as e: logger.error(f"Error posting message: {e}")
If you're curious, you can read more about how Bolt can be used to call the Web API—just consult the relevant Bolt for JavaScript, Bolt for Python, or Bolt for Java docs.
Not using Bolt? Read how to manually build an app that can interact with the Web API.
Putting everything you've learned above together, you're ready to make the API calls that will schedule a message for publishing.
You could choose to handle the scheduling of publishing entirely within your own app, but we recommend you use our APIs to handle that for you.
If you're using Bolt, call chat.scheduleMessage
by following the Bolt example below:
import com.slack.api.bolt.App; import com.slack.api.bolt.AppConfig; import com.slack.api.bolt.jetty.SlackAppServer; import com.slack.api.methods.SlackApiException; import java.io.IOException; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; public class ChatScheduleMessage { public static void main(String[] args) throws Exception { var config = new AppConfig(); config.setSingleTeamBotToken(System.getenv("SLACK_BOT_TOKEN")); config.setSigningSecret(System.getenv("SLACK_SIGNING_SECRET")); var app = new App(config); // `new App()` does the same app.command("/schedule", (req, ctx) -> { var logger = ctx.logger; var tomorrow = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).plusDays(1).withHour(9); try { var payload = req.getPayload(); // Call the chat.scheduleMessage method using the built-in WebClient var result = ctx.client().chatScheduleMessage(r -> r // The token you used to initialize your app .token(ctx.getBotToken()) .channel(payload.getChannelId()) .text(payload.getText()) // Time to post message, in Unix Epoch timestamp format .postAt((int) tomorrow.toInstant().getEpochSecond()) ); // Print result logger.info("result: {}", result); } catch (IOException | SlackApiException e) { logger.error("error: {}", e.getMessage(), e); } // Acknowledge incoming command event return ctx.ack(); }); var server = new SlackAppServer(app); server.start(); } }
Code to initialize Bolt app// Require the Node Slack SDK package (github.com/slackapi/node-slack-sdk) const { WebClient, LogLevel } = require("@slack/web-api"); // WebClient instantiates a client that can call API methods // When using Bolt, you can use either `app.client` or the `client` passed to listeners. const client = new WebClient("xoxb-your-token", { // LogLevel can be imported and used to make debugging simpler logLevel: LogLevel.DEBUG });
// Unix timestamp for tomorrow morning at 9AM const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setHours(9, 0, 0); // Channel you want to post the message to const channelId = "C12345"; try { // Call the chat.scheduleMessage method using the WebClient const result = await client.chat.scheduleMessage({ channel: channelId, text: "Looking towards the future", // Time to post message, in Unix Epoch timestamp format post_at: tomorrow.getTime() / 1000 }); console.log(result); } catch (error) { console.error(error); }
Code to initialize Bolt appimport datetime import logging import os # Import WebClient from Python SDK (github.com/slackapi/python-slack-sdk) from slack_sdk import WebClient from slack_sdk.errors import SlackApiError # WebClient instantiates a client that can call API methods # When using Bolt, you can use either `app.client` or the `client` passed to listeners. client = WebClient(token=os.environ.get("SLACK_BOT_TOKEN")) logger = logging.getLogger(__name__)
# Create a timestamp for tomorrow at 9AM tomorrow = datetime.date.today() + datetime.timedelta(days=1) scheduled_time = datetime.time(hour=9, minute=30) schedule_timestamp = datetime.datetime.combine(tomorrow, scheduled_time).strftime('%s') # Channel you want to post message to channel_id = "C12345" try: # Call the chat.scheduleMessage method using the WebClient result = client.chat_scheduleMessage( channel=channel_id, text="Looking towards the future", post_at=schedule_timestamp ) # Log the result logger.info(result) except SlackApiError as e: logger.error("Error scheduling message: {}".format(e))
POST https://slack.com/api/chat.scheduleMessage Content-type: application/json Authorization: Bearer xoxb-your-token { "channel": "YOUR_CHANNEL_ID", "text": "Hey, team. Don't forget about breakfast catered by John Hughes Bistro today.", "post_at": 1551891428, }
If you're not using Bolt, you'll need to manually create an API call to chat.scheduleMessage
. Here's an example:
import com.slack.api.bolt.App; import com.slack.api.bolt.AppConfig; import com.slack.api.bolt.jetty.SlackAppServer; import com.slack.api.methods.SlackApiException; import java.io.IOException; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; public class ChatScheduleMessage { public static void main(String[] args) throws Exception { var config = new AppConfig(); config.setSingleTeamBotToken(System.getenv("SLACK_BOT_TOKEN")); config.setSigningSecret(System.getenv("SLACK_SIGNING_SECRET")); var app = new App(config); // `new App()` does the same app.command("/schedule", (req, ctx) -> { var logger = ctx.logger; var tomorrow = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).plusDays(1).withHour(9); try { var payload = req.getPayload(); // Call the chat.scheduleMessage method using the built-in WebClient var result = ctx.client().chatScheduleMessage(r -> r // The token you used to initialize your app .token(ctx.getBotToken()) .channel(payload.getChannelId()) .text(payload.getText()) // Time to post message, in Unix Epoch timestamp format .postAt((int) tomorrow.toInstant().getEpochSecond()) ); // Print result logger.info("result: {}", result); } catch (IOException | SlackApiException e) { logger.error("error: {}", e.getMessage(), e); } // Acknowledge incoming command event return ctx.ack(); }); var server = new SlackAppServer(app); server.start(); } }
Code to initialize Bolt app// Require the Node Slack SDK package (github.com/slackapi/node-slack-sdk) const { WebClient, LogLevel } = require("@slack/web-api"); // WebClient instantiates a client that can call API methods // When using Bolt, you can use either `app.client` or the `client` passed to listeners. const client = new WebClient("xoxb-your-token", { // LogLevel can be imported and used to make debugging simpler logLevel: LogLevel.DEBUG });
// Unix timestamp for tomorrow morning at 9AM const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setHours(9, 0, 0); // Channel you want to post the message to const channelId = "C12345"; try { // Call the chat.scheduleMessage method using the WebClient const result = await client.chat.scheduleMessage({ channel: channelId, text: "Looking towards the future", // Time to post message, in Unix Epoch timestamp format post_at: tomorrow.getTime() / 1000 }); console.log(result); } catch (error) { console.error(error); }
Code to initialize Bolt appimport datetime import logging import os # Import WebClient from Python SDK (github.com/slackapi/python-slack-sdk) from slack_sdk import WebClient from slack_sdk.errors import SlackApiError # WebClient instantiates a client that can call API methods # When using Bolt, you can use either `app.client` or the `client` passed to listeners. client = WebClient(token=os.environ.get("SLACK_BOT_TOKEN")) logger = logging.getLogger(__name__)
# Create a timestamp for tomorrow at 9AM tomorrow = datetime.date.today() + datetime.timedelta(days=1) scheduled_time = datetime.time(hour=9, minute=30) schedule_timestamp = datetime.datetime.combine(tomorrow, scheduled_time).strftime('%s') # Channel you want to post message to channel_id = "C12345" try: # Call the chat.scheduleMessage method using the WebClient result = client.chat_scheduleMessage( channel=channel_id, text="Looking towards the future", post_at=schedule_timestamp ) # Log the result logger.info(result) except SlackApiError as e: logger.error("Error scheduling message: {}".format(e))
POST https://slack.com/api/chat.scheduleMessage Content-type: application/json Authorization: Bearer xoxb-your-token { "channel": "YOUR_CHANNEL_ID", "text": "Hey, team. Don't forget about breakfast catered by John Hughes Bistro today.", "post_at": 1551891428, }
Read our guide to the chat.scheduledMessage
API if you want to learn more about the feature.