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.
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!
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.
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
:
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,
}
Then sit back and relax. Like magic, the message appears at the moment specified in the post_at
Unix timestamp.
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.
"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:
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");
}
}
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");
Code to initialize Bolt appimport 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")
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.
With the scheduled_message_id
that you need in hand, it's time to banish that breakfast reminder. Use the chat.deleteScheduledMessage
endpoint:
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");
}
}
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); }
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__)
# 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}")
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.
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!