Post messages on a schedule

Beginner

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

Scopes you'll request

Create a pre-configured app

Quickly create an app with the correct configuration of scopes and features for this tutorial by clicking below.

Step 1Before you begin

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.

  • Setting up a Slack app
    Watch our video showing how to create and set up a Slack app.

    We'll show you how to use the app config pages and give you a brief tour of all the features you can setup for your app.

  • Create an app

    You won't get far without a Slack app to accompany you on this journey. Start here by creating an app.

  • Requesting scopes for your 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:

    1. Head over to app config page for your app.
    2. Navigate to the OAuth & Permissions page.
    3. Scroll down to the Bot Token Scopes section.
    4. Click Add an OAuth Scope.
    5. Add the scopes listed above.

    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 config 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.

  • Install your app

    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:

    1. Head over to app config page for your app.
    2. Navigate to the Install App page.
    3. Click Install to Workspace.

    You'll be redirected to the OAuth permission request dialog:

    Oauth UI for users

    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 config. 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.

  • Use Bolt to build your app

    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:

    • A basic web server to run your app on
    • Authentication and installation handling for all the ins and outs of OAuth
    • Simplified interfaces for all Slack APIs and app features
    • Automatic token validation, retry, and rate-limiting logic

    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.

Step complete!

Step 2Compose your message

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.

When you're done

After you've finished with this step, you will be able to generate your own message payload.

  • Create a basic 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.

  • Find a place to publish your message

    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:

    Finding a conversation
    Java
    JavaScript
    Python
    HTTP
    Java
    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"); } }
    JavaScript
    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");
    Python
    Code to initialize Bolt app
    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__)
    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}")
    HTTP
    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 ids 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.

  • OptionalFormat text in your message

    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.

  • OptionalCreate a rich message layout

    Slack provides a range of visual components, called Block Kit, that can be used to lay out info in messages.

  • OptionalPrototype and preview message layouts

    Use Block Kit Builder to visually prototype app-publishable messages.

Step complete!

Step 3Learn how to use the Web API

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.

When you're done

After finishing this step, you should be familiar with the basics of using the Web API in an app.

  • Using the Slack Web API with Bolt

    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:

    Calling chat.postMessage
    Java
    JavaScript
    Python
    Java
    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(); } }
    JavaScript
    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); }
    Python
    Code to initialize Bolt app
    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__)
    # 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.

  • AlternativeUse the Web API manually

    Not using Bolt? Read how to manually build an app that can interact with the Web API.

Step complete!

Step 4Schedule a message to publish

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.

  • Schedule a message with Bolt

    If you're using Bolt, call chat.scheduleMessage by following the Bolt example below:

    Scheduling a message
    Java
    JavaScript
    Python
    HTTP
    Java
    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(); } }
    JavaScript
    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); }
    Python
    Code to initialize Bolt app
    import 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))
    HTTP
    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, }
  • AlternativeSchedule a message manually

    If you're not using Bolt, you'll need to manually create an API call to chat.scheduleMessage. Here's an example:

    Scheduling a message
    Java
    JavaScript
    Python
    HTTP
    Java
    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(); } }
    JavaScript
    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); }
    Python
    Code to initialize Bolt app
    import 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))
    HTTP
    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.

Step complete!

Was this page helpful?