Apps that only listen can be useful, but there's so much more utility to explore by transforming a monologue into a conversation. Give your app the gift of dialogue by setting it up to send Slack messages.
This guide will help you learn a basic way to accomplish this and show you the paths you can take to make things complex and interactive.
Sending messagesIf you don't have a Slack app yet, here's a 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.
Once you've done that, come back here and keep reading.
Requesting the necessary permissionsEach Slack app starts off without permission to do very much at all. You will have to grant your app the correct scopes required in order for you to publish messages. There are lots of scopes available, and you can read our OAuth guide for more information on why they're needed, and what they do.
For the purposes of this guide, however, we'll skip over that and just tell you the permissions we need right now.
The first 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 second is chat:write
. This one grants permission for your app to send messages as itself (apps can send messages as users or bot users, but we'll cover that later).
To request permissions:
channels:read
and chat:write
from the drop-down menu.If you had already installed your app to its original workspace before, you might still see the permissions screen if the scopes you just added weren't previously granted to your app.
Authorize your app, and you should see a success message. On the OAuth & Permissions page you're brought back to, you should now see an OAuth access token available. Grab this token and store it for later, as we'll use that token to make some Web API calls.
Picking the right conversationNow we need to find somewhere within your workspace to send a message. This could be any Slack conversation, but we'll use a public channel.
We'll remind you again, it's not a good idea to attempt the instructions in this guide with a real, living workspace. If you really have to, at least create a new, empty public channel within the workspace for testing purposes.
In order to find a valid Slack conversation ID, we'll use the conversations.list
method. This API method will return a list of all public channels in the workspace your app is installed to. You'll need the channels:read
scope 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:
GET https://slack.com/api/conversations.list
Authorization: Bearer xoxb-your-token
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 {
static void findConversation(String name) {
var client = Slack.getInstance().methods();
var logger = LoggerFactory.getLogger("my-awesome-slack-app");
try {
var result = client.conversationsList(r -> r
.token(System.getenv("SLACK_BOT_TOKEN"))
);
for (Conversation channel : result.getChannels()) {
if (channel.getName().equals(name)) {
var conversationId = channel.getId();
logger.info("Found conversation ID: {}", conversationId);
break;
}
}
} catch (IOException | SlackApiException e) {
logger.error("error: {}", e.getMessage(), e);
}
}
public static void main(String[] args) throws Exception {
findConversation("tester-channel");
}
}
const { WebClient, LogLevel } = require("@slack/web-api");
const client = new WebClient("xoxb-your-token", {
logLevel: LogLevel.DEBUG
});
async function findConversation(name) {
try {
const result = await app.client.conversations.list({
token: "xoxb-your-token"
});
for (const channel of result.channels) {
if (channel.name === name) {
conversationId = channel.id;
console.log("Found conversation ID: " + conversationId);
break;
}
}
}
catch (error) {
console.error(error);
}
}
findConversation("tester-channel");
import logging
import os
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
client = WebClient(token=os.environ.get("SLACK_BOT_TOKEN"))
logger = logging.getLogger(__name__)
channel_name = "needle"
conversation_id = None
try:
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(f"Found conversation ID: {conversation_id}")
break
except SlackApiError as e:
print(f"Error: {e}")
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.
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.
Now that we've picked a destination, we need to decide what our message will look like.
Composing your messageDesigning a message is a complicated topic, so we've broken it out into its own section that you can read at your leisure.
For this guide, we'll just publish a plain-text message. Here's the message payload we're going to send:
{
"channel": "YOUR_CHANNEL_ID",
"text": "Hello, world"
}
That seems appropriate, right? Let's get down to the actual business of sending this message.
Publishing your messageWe're nearly there; we just need to make one more API call, this time to chat.postMessage
. Again, substitute in the values of the token and conversation ID that you noted earlier:
POST https://slack.com/api/chat.postMessage
Content-type: application/json
Authorization: Bearer xoxb-your-token
{
"channel": "YOUR_CHANNEL_ID",
"text": "Hello world :tada:"
}
import com.slack.api.Slack;
import com.slack.api.methods.SlackApiException;
import org.slf4j.LoggerFactory;
import java.io.IOException;
public class PublishingMessage {
static void publishMessage(String id, String text) {
var client = Slack.getInstance().methods();
var logger = LoggerFactory.getLogger("my-awesome-slack-app");
try {
var result = client.chatPostMessage(r -> r
.token("xoxb-your-token")
.channel(id)
.text(text)
);
logger.info("result {}", result);
} catch (IOException | SlackApiException e) {
logger.error("error: {}", e.getMessage(), e);
}
}
public static void main(String[] args) throws Exception {
publishMessage("C12345", "Hello world :tada:");
}
}
const { WebClient, LogLevel } = require("@slack/web-api");
const client = new WebClient("xoxb-your-token", {
logLevel: LogLevel.DEBUG
});
async function publishMessage(id, text) {
try {
const result = await app.client.chat.postMessage({
token: "xoxb-your-token",
channel: id,
text: text
});
console.log(result);
}
catch (error) {
console.error(error);
}
}
publishMessage("C12345", "Hello world :tada:");
import logging
import os
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
client = WebClient(token=os.environ.get("SLACK_BOT_TOKEN"))
logger = logging.getLogger(__name__)
channel_id = "C01JASD6802"
try:
result = client.chat_postMessage(
channel=channel_id,
text="Hello world!"
)
print(result)
except SlackApiError as e:
print(f"Error: {e}")
This time we're using a POST request, so your token from before has to be included in the header of the request as a bearer token.
Submit the request and your message should be delivered instantly. You'll get back a response payload containing an ok
confirmation value of true
, and other data such as the channel
the message was posted to. One very important piece of information in this response is the ts
value, which is essentially the ID of the message, and which you'll need if you want to reply to this message.
Locate the Slack conversation the message was sent to and it should be waiting for you:
OsloWoof! (Hello world!)
Amazing work! You've now implemented one of the core pieces of functionality for any Slack app. Keep reading to see how you can add a little more complexity.
Replying to your messageIn some cases, you might find it more useful for your app to reply to another message, creating a thread. For example, if your app is sending a message in response to being mentioned, then it makes sense to add a threaded reply to the source of the mention.
Make sure the message you want to reply to isn't a reply itself, as shown in our retrieving messages guide.
For this guide, we'll assume the message is an unthreaded one. In fact, let's reply to the message you just published. You need three pieces of information to reply in a thread:
token
created earlier.channel
that the parent message lives in.ts
value of the parent message.You should still have the last two pieces of information from the response payload you received after publishing the parent message.
In more generic terms, you can also find the ts
value of messages by following our guide to retrieving individual messages.
Pulling all that data together, we can make another chat.postMessage
API call to publish a reply:
POST https://slack.com/api/chat.postMessage
Content-type: application/json
Authorization: Bearer xoxb-your-token
{
"channel": "YOUR_CHANNEL_ID",
"thread_ts": "PARENT_MESSAGE_TS",
"text": "Hello again!"
}
import com.slack.api.Slack;
import com.slack.api.methods.SlackApiException;
import org.slf4j.LoggerFactory;
import java.io.IOException;
public class ReplyingMessage {
static void replyMessage(String id, String ts) {
var client = Slack.getInstance().methods();
var logger = LoggerFactory.getLogger("my-awesome-slack-app");
try {
var result = client.chatPostMessage(r -> r
.token("xoxb-your-token")
.channel(id)
.threadTs(ts)
.text("Hello again :wave:")
);
logger.info("result {}", result);
} catch (IOException | SlackApiException e) {
logger.error("error: {}", e.getMessage(), e);
}
}
public static void main(String[] args) throws Exception {
replyMessage("C12345", "15712345.001500");
}
}
const { WebClient, LogLevel } = require("@slack/web-api");
const client = new WebClient("xoxb-your-token", {
logLevel: LogLevel.DEBUG
});
async function replyMessage(id, ts) {
try {
const result = await app.client.chat.postMessage({
token: "xoxb-your-token",
channel: id,
thread_ts: ts,
text: "Hello again :wave:"
});
console.log(result);
}
catch (error) {
console.error(error);
}
}
replyMessage("C12345", "15712345.001500");
import logging
import os
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
client = WebClient(token=os.environ.get("SLACK_BOT_TOKEN"))
logger = logging.getLogger(__name__)
message_ts = "1610144875.000600"
channel_id = "C01JASD6802"
try:
result = client.chat_postMessage(
channel=channel_id,
thread_ts=message_ts,
text="Hello again :wave:"
)
print(result)
except SlackApiError as e:
print(f"Error: {e}")
You'll see another API response payload containing information about the newly published threaded reply. Return to the same conversation in Slack, and you'll see your original message now has a reply:
OsloWoof! (Hello world!)
OsloWoof Woof! (Hello world, again!)
When publishing threaded reply messages, you can also supply a reply_broadcast
boolean parameter, as listed in the relevant API docs. This parameter, if set to true
, will 'broadcast' a reference to the threaded reply to the parent conversation. Read more about the Slack user-facing equivalent of this feature here.
Apps can publish messages that appear to have been created by a user in the conversation. The message will be attributed to the user and show their profile photo beside it.
This is a powerful ability and must only be used when the user themselves gives permission to do so. For this reason, this ability is only available when an app has requested and been granted an additional scope — chat:write.customize
.
Your app should only use this feature in response to an inciting user action. It should never be unexpected or surprising to a user that a message was posted on their behalf, and it should be heavily signposted in advance.
To modify the appearance of the app, make calls to chat.postMessage
while providing any of the following parameters:
username
to specify the username for the published message.icon_url
to specify a URL to an image to use as the profile photo alongside the message.icon_emoji
to specify an emoji (using colon shortcodes, eg. :white_check_mark:
) to use as the profile photo alongside the message.If the channel
parameter is set to a User ID (beginning with U
), the message will appear in that user's direct message channel with Slackbot. To post a message to that user's direct message channel with the app, use the DM ID (beginning with D
) instead.
Scheduling a message is just a bit of fancy footwork on top of sending a message directly. 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!
PermissionsNow 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 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": "Woooooooof! (Dinner time!)"
}
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 the chat.scheduleMessage
method instead of the chat.postMessage
method:
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,
}
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);
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();
var result = ctx.client().chatScheduleMessage(r -> r
.token(ctx.getBotToken())
.channel(payload.getChannelId())
.text(payload.getText())
.postAt((int) tomorrow.toInstant().getEpochSecond())
);
logger.info("result: {}", result);
} catch (IOException | SlackApiException e) {
logger.error("error: {}", e.getMessage(), e);
}
return ctx.ack();
});
var server = new SlackAppServer(app);
server.start();
}
}
const { WebClient, LogLevel } = require("@slack/web-api");
const client = new WebClient("xoxb-your-token", {
logLevel: LogLevel.DEBUG
});
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(9, 0, 0);
const channelId = "C12345";
try {
const result = await client.chat.scheduleMessage({
channel: channelId,
text: "Looking towards the future",
post_at: tomorrow.getTime() / 1000
});
console.log(result);
}
catch (error) {
console.error(error);
}
import datetime
import logging
import os
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
client = WebClient(token=os.environ.get("SLACK_BOT_TOKEN"))
logger = logging.getLogger(__name__)
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_id = "C12345"
try:
result = client.chat_scheduleMessage(
channel=channel_id,
text="Looking towards the future",
post_at=schedule_timestamp
)
logger.info(result)
except SlackApiError as e:
logger.error("Error scheduling message: {}".format(e))
Then sit back and relax. Like magic, the message appears at the moment specified in the post_at
Unix timestamp.
Woooooooof! (Dinner time!)
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.
"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
method.
Call the chat.scheduledMessages.list
method with optional channel
, latest
, and oldest
parameters to specify which channel and time range you're interested in:
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,
}
import com.slack.api.Slack;
import com.slack.api.methods.SlackApiException;
import org.slf4j.LoggerFactory;
import java.io.IOException;
public class ChatScheduledMessagesList {
static void listScheduledMessages(String latest, String oldest) {
var client = Slack.getInstance().methods();
var logger = LoggerFactory.getLogger("my-awesome-slack-app");
try {
var result = client.chatScheduledMessagesList(r -> r
.token(System.getenv("SLACK_BOT_TOKEN"))
.latest(latest)
.oldest(oldest)
);
for (var message : result.getScheduledMessages()) {
logger.info("message: {}", message);
}
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");
}
}
const { WebClient, LogLevel } = require("@slack/web-api");
const client = new WebClient("xoxb-your-token", {
logLevel: LogLevel.DEBUG
});
async function listScheduledMessages(latest, oldest) {
try {
const result = await client.chat.scheduledMessages.list({
latest: latest,
oldest: oldest
});
for (const message of result.scheduled_messages) {
console.log(message);
}
}
catch (error) {
console.error(error);
}
}
listScheduledMessages("1551991429", "2661991427");
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__)
def list_scheduled_messages(latest, oldest):
try:
result = client.chat_scheduledMessages_list(
latest=latest,
oldest=oldest
)
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")
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:
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"
}
import com.slack.api.Slack;
import com.slack.api.methods.SlackApiException;
import org.slf4j.LoggerFactory;
import java.io.IOException;
public class ChatDeleteScheduledMessage {
static void deleteScheduledMessage(String channel, String id) {
var client = Slack.getInstance().methods();
var logger = LoggerFactory.getLogger("my-awesome-slack-app");
try {
var result = client.chatDeleteScheduledMessage(r -> r
.token(System.getenv("SLACK_BOT_TOKEN"))
.channel(channel)
.scheduledMessageId(id)
);
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");
}
}
const { WebClient, LogLevel } = require("@slack/web-api");
const client = new WebClient("xoxb-your-token", {
logLevel: LogLevel.DEBUG
});
const messageId = "U12345";
const channelId = "C12345";
try {
const result = await client.chat.deleteScheduledMessage({
channel: channelId,
scheduled_message_id: messageId
});
console.log(result);
}
catch (error) {
console.error(error);
}
import logging
import os
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
client = WebClient(token=os.environ.get("SLACK_BOT_TOKEN"))
logger = logging.getLogger(__name__)
message_id = "U12345"
channel_id = "C12345"
try:
result = client.chat_deleteScheduledMessage(
channel=channel_id,
scheduled_message_id=message_id
)
logger.info(result)
except SlackApiError as e:
logger.error(f"Error deleting scheduled message: {e}")
You'll receive the typical success response once your scheduled message has been deleted.
Update a scheduled messageTo 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!
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4