Sending messages

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.


Getting started with sending 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!

After you've done that, come back here and keep reading.

Requesting the necessary permissions

Each Slack app starts off without permission to do very much at all. You will have to grant your app the correct scopes that are 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).

Requesting these permissions is easy:

  1. Load up the settings for your app from the app management page.
  2. In the navigation menu, choose the OAuth & Permissions feature.
  3. Scroll down to the Scopes section, and pick channels:read and chat:write from the drop down menu.
  4. Click save changes.
  5. Scroll back to the top of this page and look for the button that says Install App to Workspace (or Reinstall App if you've done this before). Click it.

You'll now see a permission request screen to install your app to its original workspace.

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 settings 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 conversation

Now we need to find somewhere within your workspace that we'll send a message. This could be any Slack conversation, but we'll use a public channel in this guide.

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, then 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 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 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 });
// 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 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__)
channel_name = "needle" conversation_id = None try: # Call the conversations.list method using the WebClient for response in result 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.

Now that we've picked a destination, we need to decide what our message will look like.

Composing your message

Designing 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're going to bypass that and just publish a very simple 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 message

We'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:

Publishing a message
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 PublishingMessage {

    /**
     * Post a message to a channel your app is in using ID and message text
     */
    static void publishMessage(String id, String text) {
        // 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.postMessage method using the built-in WebClient
            var result = client.chatPostMessage(r -> r
                // The token you used to initialize your app
                .token("xoxb-your-token")
                .channel(id)
                .text(text)
                // You could also use a blocks[] array to send richer content
            );
            // Print result, which includes information about the message (like TS)
            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:");
    }

}
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 });
// Post a message to a channel your app is in using ID and message text async function publishMessage(id, text) { try { // Call the chat.postMessage method using the built-in WebClient const result = await app.client.chat.postMessage({ // The token you used to initialize your app token: "xoxb-your-token", channel: id, text: text // You could also use a blocks[] array to send richer content }); // Print result, which includes information about the message (like TS) console.log(result); } catch (error) { console.error(error); } } publishMessage("C12345", "Hello world :tada:");
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__)
# ID of channel you want to post message to channel_id = "C01JASD6802" try: # Call the conversations.list method using the WebClient result = client.chat_postMessage( channel=channel_id, text="Hello world!" # You could also use a blocks[] array to send richer content ) # Print result, which includes information about the message (like TS) print(result) except SlackApiError as e: print(f"Error: {e}")
HTTP
POST https://slack.com/api/chat.postMessage
Content-type: application/json
Authorization: Bearer xoxb-your-token
{
  "channel": "YOUR_CHANNEL_ID",
  "text": "Hello world :tada:"
}

Note that 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 unlike a typical mail service, 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, 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, like this:

Message in Slack conversation that says 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 some complexity.


Replying to your message

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

First, make sure the message you want to reply to isn't a reply itself, as shown in our retrieving messages guide.

For this guide, let's 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:

  • An access token with the right permissions, like the token created earlier.
  • The channel that the parent message lives in.
  • The ts value of the parent message.

You should still have the last two pieces of info 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:

Replying to a message
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 ReplyingMessage {

    /**
     * Reply to a message with the channel ID and message TS
     */
    static void replyMessage(String id, String ts) {
        // 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.postMessage method using the built-in WebClient
            var result = client.chatPostMessage(r -> r
                // The token you used to initialize your app
                .token("xoxb-your-token")
                .channel(id)
                .threadTs(ts)
                .text("Hello again :wave:")
                // You could also use a blocks[] array to send richer content
            );
            // Print result
            logger.info("result {}", result);
        } catch (IOException | SlackApiException e) {
            logger.error("error: {}", e.getMessage(), e);
        }
    }

    public static void main(String[] args) throws Exception {
        // Uses a known channel ID and message TS
        replyMessage("C12345", "15712345.001500");
    }

}
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 });
// Reply to a message with the channel ID and message TS async function replyMessage(id, ts) { try { // Call the chat.postMessage method using the built-in WebClient const result = await app.client.chat.postMessage({ // The token you used to initialize your app token: "xoxb-your-token", channel: id, thread_ts: ts, text: "Hello again :wave:" // You could also use a blocks[] array to send richer content }); // Print result console.log(result); } catch (error) { console.error(error); } } // Uses a known channel ID and message TS replyMessage("C12345", "15712345.001500");
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__)
# TS of message you want to post thread to message_ts = "1610144875.000600" # ID of channel you want to post message to channel_id = "C01JASD6802" try: # Call the chat.postMessage method using the WebClient # The client passes the token you included in initialization result = client.chat_postMessage( channel=channel_id, thread_ts=message_ts, text="Hello again :wave:" # You could also use a blocks[] array to send richer content ) print(result) except SlackApiError as e: print(f"Error: {e}")
HTTP
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!"
}

You'll see another API response payload containing info about the newly published threaded reply. Go back to the same conversation in Slack, and you'll see your original message now has a reply:

Message in Slack conversation that says Hello, world with a reply saying Hello 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.


Sending messages as other entities

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.

With all that in mind, the act of authoring a message as a user is relatively simple. With the aforementioned scope, you'll be able to make calls to chat.postMessage and provide:

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

Many ways to send messages

In this guide, we've shown you one way your app can publish messages to Slack, but there are many other ways to accomplish the same task.

Below we have collected links to the guides for each of the various methods that allow for the publishing of messages:

Method Description
chat.postMessage A Web API method that sends a message to a conversation. We used this above.
chat.postEphemeral A Web API method that sends an ephemeral message to a conversation.
Incoming webhooks A uniquely generated URL that is tied to a specific conversation. Send an HTTP POST to one to publish a message.
response_url A callback URL that you can use to post a response to a message interaction. You're going to use this a lot if you publish interactive messages.
Real Time Messaging API This API does not support layout blocks or attachments. We don't recommend you use it for messaging, but it does exist, so we'd be remiss if we didn't mention it.
Broadcast messages within the 'Messages' tab Apps can send messages in the Messages tab without listening for a reply back from a user.

Was this page helpful?