Beginner

Scheduling messages

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!

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

Permissions

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

A quick recap: you can use either an app with a bot or user token. If you choose to use a bot, the bot scope is all you need. As a friendly reminder, the legacy bot token isn't supported.

With a user token, the first scope your app requires is channels:read. That scope lets your app retrieve a list of all the public channels in a workspace so that you can pick one to publish a message to. If you already know the ID of the channel you wish to send messages to, you can skip out on requesting channels:read.

The next scope you'll need is chat:write:bot or chat:write:user. That scope grants permission for your app to send messages.

Schedule a message

Our guide to directly sending messages talked you through a simple 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 insantiates 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 insantiates 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

Helpful hint: messages can only be scheduled up to 120 days in advance, and can't be scheduled for the past.

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 the messages you've scheduled

"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 insantiates 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 message you've scheduled

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

Updating

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

Reminders, notifications, even calendars—all now fall within easy 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!

Warnings and known issues

  • If your app tries to schedule a message in the past or more than 120 days in the future, we'll return an invalid_time error.
  • To update a pending scheduled message, you'll need to delete your old message and then schedule a new one.

Was this page helpful?