Scheduling messages

Scheduling a message is just a bit of fancy footwork on top of sending a message directly. First, take a gander at our guide to sending messages. After you've done that, come back here and keep reading.

Getting started with scheduled messages

One thing you'll need before starting is a Slack app. If you don't have one yet, here's a very quick guide to help you create one. Make sure you create the app in a workspace that won't mind you posting lots of test messages!

Permissions

Now for some particularly pleasant permissions news: your app's permissions are actually the ones you've already acquired to post messages!

Let’s quickly recap. Your app uses a bot token to perform actions as itself. You grant your app permission to perform specific actions by giving its bot token the corresponding scopes.

For your app to send scheduled messages, it only needs one scope: chat:write.

If you don't already know the ID of the channel you wish to send messages to, you may also want to give your app another scope: channels:read. This scope lets your app retrieve a list of all the public channels in a workspace so you can pick one to publish a message to.

Schedule a message

Our guide to directly sending messages talked you through a call to chat.postMessage. Let's reinvent our app to send a reminder instead: say, about a weekly team breakfast.

{
  "channel": "YOUR_CHANNEL_ID",
  "text": "Hey, team. Don't forget about breakfast catered by John Hughes Bistro today."
}

If you want to do things the hard way, your app could implement state storage and job scheduling to send this message at the right time each week, using a database and batch task runner.

If you prefer an easier approach, use a scheduled message instead. Add a post_at parameter to your JSON request, and pass your JSON to chat.scheduleMessage instead of chat.postMessage:

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, }

Then sit back and relax. Like magic, the message appears at the moment specified in the post_at Unix timestamp.

Message in Slack conversation that shows a breakfast reminder from the Breakfast Club app

Messages can only be scheduled up to 120 days in advance.

The HTTP response from chat.scheduleMessage includes a scheduled_message_id, which can be used to delete the scheduled message before it is sent. Read on to find out how.

List your scheduled messages

"Fire and forget" reminders are all well and good, but the best-laid breakfast plans sometimes fall through. Let's say a holiday closes the office during one of your team's scheduled breakfast clubs. Better cancel that reminder message!

Your app can list all the messages that it currently has scheduled with the chat.scheduledMessages.list endpoint.

Call chat.scheduledMessages.list with optional channel, latest, and oldest parameters to specify which channel and time range you're interested in:

Listing scheduled messages
Java
JavaScript
Python
HTTP
Java
import com.slack.api.Slack; import com.slack.api.methods.SlackApiException; import org.slf4j.LoggerFactory; import java.io.IOException; public class ChatScheduledMessagesList { /** * Lists scheduled messages using latest and oldest timestamps */ static void listScheduledMessages(String latest, String oldest) { // 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 chat.scheduledMessages.list method using the built-in WebClient var result = client.chatScheduledMessagesList(r -> r // The token you used to initialize your app .token(System.getenv("SLACK_BOT_TOKEN")) .latest(latest) .oldest(oldest) ); // Print scheduled messages for (var message : result.getScheduledMessages()) { logger.info("message: {}", message); } // Print result logger.info("result: {}", result); } catch (IOException | SlackApiException e) { logger.error("error: {}", e.getMessage(), e); } } public static void main(String[] args) throws Exception { listScheduledMessages("1551991429", "1551991427"); } }
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 });
// List scheduled messages using latest and oldest timestamps async function listScheduledMessages(latest, oldest) { try { // Call the chat.scheduledMessages.list method using the WebClient const result = await client.chat.scheduledMessages.list({ latest: latest, oldest: oldest }); // Print scheduled messages for (const message of result.scheduled_messages) { console.log(message); } } catch (error) { console.error(error); } } listScheduledMessages("1551991429", "2661991427");
Python
Code to initialize Bolt app
import os import logging from slack_sdk.errors import SlackApiError from slack_sdk.web import WebClient client = WebClient(token=os.environ["SLACK_BOT_TOKEN"]) logger = logging.getLogger(__name__)
# List scheduled messages using latest and oldest timestamps def list_scheduled_messages(latest, oldest): try: # Call the chat.scheduledMessages.list method using the WebClient result = client.chat_scheduledMessages_list( latest=latest, oldest=oldest ) # Print scheduled messages for message in result["scheduled_messages"]: logger.info(message) except SlackApiError as e: logger.error("Error creating conversation: {}".format(e)) list_scheduled_messages("1551991429", "2661991427")
HTTP
POST https://slack.com/api/chat.scheduledMessages.list Content-type: application/json Authorization: Bearer xoxb-your-token { "channel": "YOUR_CHANNEL_ID", "latest": 1551991429, "oldest": 1551991427, }

oldest signifies the Unix timestamp of the earliest range you're interested in. latest signifies, well, the latest. So oldest must be less than latest if both are specified.

The endpoint yields scheduled messages sent by your app, plus pagination information.

{
    "ok": true,
    "scheduled_messages": [
        {
            "id": "YOUR_SCHEDULED_MESSAGE_ID",
            "channel_id": "YOUR_CHANNEL_ID",
            "post_at": 1551991428,
            "date_created": 1551891734
        }
    ],
    "response_metadata": {
        "next_cursor": ""
    }
}

Now that you've got the id of the breakfast club reminder you want to delete, one more method call awaits, so read on.

Delete a scheduled message

With the scheduled_message_id that you need in hand, it's time to banish that breakfast reminder. Use the chat.deleteScheduledMessage endpoint:

Calling chat.deleteScheduledMessage
Java
JavaScript
Python
HTTP
Java
import com.slack.api.Slack; import com.slack.api.methods.SlackApiException; import org.slf4j.LoggerFactory; import java.io.IOException; public class ChatDeleteScheduledMessage { /** * Deletes scheduled message using channel ID and scheduled message ID */ static void deleteScheduledMessage(String channel, String id) { // 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 { var result = client.chatDeleteScheduledMessage(r -> r // The token you used to initialize your app .token(System.getenv("SLACK_BOT_TOKEN")) .channel(channel) .scheduledMessageId(id) ); // Print result logger.info("result: {}", result); } catch (IOException | SlackApiException e) { logger.error("error: {}", e.getMessage(), e); } } public static void main(String[] args) throws Exception { deleteScheduledMessage("C12345", "Q123ABC95"); } }
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 });
// The ts of the message you want to delete const messageId = "U12345"; // ID of channel that the scheduled message was sent to const channelId = "C12345"; try { // Call the chat.deleteScheduledMessage method using the WebClient const result = await client.chat.deleteScheduledMessage({ channel: channelId, scheduled_message_id: messageId }); 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__)
# The ts of the message you want to delete message_id = "U12345" # ID of channel that the scheduled message was sent to channel_id = "C12345" try: # Call the chat.deleteScheduledMessage method using the built-in WebClient result = client.chat_deleteScheduledMessage( channel=channel_id, scheduled_message_id=message_id ) # Log the result logger.info(result) except SlackApiError as e: logger.error(f"Error deleting scheduled message: {e}")
HTTP
POST https://slack.com/api/chat.deleteScheduledMessage Content-type: application/json Authorization: Bearer xoxb-your-token { "channel": "YOUR_CHANNEL_ID", "scheduled_message_id": "YOUR_SCHEDULED_MESSAGE_ID" }

You'll receive the typical success response once your scheduled message has been deleted.

Update a scheduled message

To update a pending scheduled message, delete the old message and then schedule a new one.

Reminders, notifications, even calendars—all now fall within grasp of your app. You don't have to store any state at all if you don't wish to. Instead, list and delete your scheduled messages on the fly. Combine scheduled messages with user interactivity to create a chat bot that's a capable, clever assistant. Enjoy the newfound simplicity of scheduled messages!

Recommended reading