# Simple Python code to send messages to a Slack channel (without packages)

**Date:** 2019-10-25  
**Author:** Kees C. Bakker  
**Categories:** Chatops, Databricks / Spark, Python  
**Tags:** Slack  
**Original:** https://keestalkstech.com/simple-python-code-to-send-message-to-slack-channel-without-packages/

![Simple Python code to send message to Slack channel](https://keestalkstech.com/wp-content/uploads/2019/10/Simple-Python-code-to-send-message-to-Slack-channel.jpg)

---

Last week I was working on a Databricks script that needed to produce a Slack message as its final outcome. I lifted some code that used a Slack client that was PIP-installed. Unfortunately, I could not install that package on my cluster. Fortunately, the Slack API is so simple, that you don't need a package to post a simple message to a channel. In this blog, I'll show you the simplest way of producing awesome messages in Slack. No packages, just Python 🤠.

This code is not only applicable to Databricks. This will work in any Python script, application or notebook. Enjoy!

[outline]

## Manifest

Slack allows you to add manifests, so first we should create an app with the right settings:

```
display_information:
  name: Eve
  description: Posts messages (or files) to Slack.
  background_color: "#000000"
features:
  bot_user:
    display_name: Eve
    always_online: false
oauth_config:
  scopes:
    bot:
      - channels:read
      - chat:write
      - chat:write.customize
      - files:read
      - files:write
settings:
  org_deploy_enabled: false
  socket_mode_enabled: false
  token_rotation_enabled: false
```

[Manifests are awesome!](https://keestalkstech.com/2022/09/dont-use-slack-incoming-webhooks-app-creation-is-dead-simple/)

## Setup

I like to manage my settings in a central place, so let's create some variables to store the slack token and channel (note: it should start with a `#`).

```
import os

slack_token = os.getenv('SLACK_BOT_TOKEN')
slack_channel = '#chatops-tests'
slack_icon_emoji = None # ':see_no_evil:'
slack_user_name = None # "Eve 2"
```

You can use the same bot for message. To let my users know which application is sending the message, you could use a per application icon and user name.

The newer Slack endpoints want a **channel ID**, instead of a channel name. Let's turn our channel name into an ID:

```
import requests

def get_channel_id(channel_name):
    response = requests.get(
        'https://slack.com/api/conversations.list',
        headers={'Authorization': f'Bearer {slack_token}'},
        params={'exclude_archived': 'true', 'limit': 1000}
    ).json()
    
    if not response.get('ok'):
        raise Exception(f"Failed to fetch channels: {response.get('error')}")

    for channel in response.get('channels', []):
        if f"#{channel['name']}" == channel_name:
            return channel['id']
    
    raise Exception(f"Channel '{channel_name}' not found")

slack_channel_id = get_channel_id(slack_channel)
```

Your bot must be part of the channel, so make sure you've added it to the channel you want to post to.

## Posting to a channel

The Slack API provides a nice [chat.postMessage](https://api.slack.com/methods/chat.postMessage) endpoint that we can use. We'll post a dictionary to the endpoint and a message will appear in our Slack channel:

```
import json
import requests

def post_message_to_slack(text, blocks = None, unfurl_links = True):
    return requests.post('https://slack.com/api/chat.postMessage', {
        'token': slack_token,
        'channel': slack_channel,
        'text': text,
        'blocks': json.dumps(blocks) if blocks else None,
        'unfurl_links': unfurl_links,
        'icon_emoji': slack_icon_emoji,
        'username': slack_user_name,
    }).json()
```

The only thing we need to do is call the function with a text, for example:

```
wiki_link = "https://en.wikipedia.org/wiki/Three_Laws_of_Robotics#:~:text=A%20robot%20may%20not%20injure%20a%20human%20being%20or%2C%20through%20inaction%2C%20allow%20a%20human%20being%20to%20come%20to%20harm."
slack_info = 'A robot *may not injure a human being* or, through inaction, allow a human being to come to harm. .'.format(wiki_link)

response = post_message_to_slack(slack_info, unfurl_links = False)
```

It will look like this:

![](https://keestalkstech.com/wp-content/uploads/2019/10/eve-markdown-slack.png)
*You can use markdown to make the text stand out.*

### Help, my custom username is not showing?!

Slack did some changes, so your bot / integration needs to have the [chat:write.customize](write.customize) permission. If it does not have that permission the username and icon of the bot are shown.

## How about blocks?

[I. Love. Block. Kit.](https://api.slack.com/block-kit) It allows us to build complex messages, like this:

![](https://keestalkstech.com/wp-content/uploads/2019/10/eve-blocks-slack-1.png)
*This message contains markdown, a list and a context with a link to Wikipedia.*

You can design block kit messages in the [Block Kit Builder](https://api.slack.com/tools/block-kit-builder). I use Python objects to generate the blocks. A block-message can be sent by like this:

```
wiki_link = "https://en.wikipedia.org/wiki/Three_Laws_of_Robotics#:~:text=A%20robot%20may%20not%20injure%20a%20human%20being%20or%2C%20through%20inaction%2C%20allow%20a%20human%20being%20to%20come%20to%20harm."
slack_info = 'A robot *may not injure a human being* or, through inaction, allow a human being to come to harm. .'.format(wiki_link)

response = post_message_to_slack(slack_info, unfurl_links = False)
```

## How about files?

Slack deprecated the [file.upload](https://api.slack.com/methods/files.upload) API, so we need to use the new API. Unfortunately, it does not support the `username` and `icon_url` properties.

```
import requests

def post_file_to_slack(text, file_name, file_bytes, snippet_type=None, title=None):

    # Step 1: Get upload URL and file ID
    upload_url_data = requests.post('https://slack.com/api/files.getUploadURLExternal', {
        'token': slack_token,
        'filename': file_name,
        'length': len(file_bytes),
        'snippet_type': snippet_type
    }).json()
    
    if not upload_url_data.get('ok'):
        raise Exception(f"Failed to get upload URL: {upload_url_data.get('error')}")
    
    upload_url = upload_url_data['upload_url']
    file_id = upload_url_data['file_id']

    # Step 2: Upload the file to the provided URL
    requests.post(
        upload_url,
        files  = { 'file': file_bytes },
    )

    # Step 3: Complete the file upload
    complete_upload_data = requests.post('https://slack.com/api/files.completeUploadExternal',
        headers={
            'Authorization': f'Bearer {slack_token}',
            'Content-Type': 'application/json; charset=utf-8'
        },
        json={
            'files': [{ 'id': file_id, 'title': title or file_name }],
            'channel_id': slack_channel_id,
            'initial_comment': text,
    }).json()
    
    if not complete_upload_data.get('ok'):
        raise Exception(f"Failed to complete file upload: {complete_upload_data.get('error')}")
    
    return complete_upload_data
```

It makes sending text files super easy:

```
response = post_file_to_slack(
    'Check out my text file!',
    'Hello.txt',
    'Hello World!')
```

Which is displayed like this:

![](https://keestalkstech.com/wp-content/uploads/2019/10/eve-post-text-file.png)
*Text files will appear as snippets in the Slack interface.*

Sending binary data is easy. This code downloads the image URL and passes the bytes on to the function:

```
import urllib.request

# download image
image = "https://townsquare.media/site/442/files/2018/06/wall-e-eve.jpg"
response = urllib.request.urlopen(image)
data = response.read()

# send to slack
response = post_file_to_slack(
  ':palm_tree: Amazing day with *WALL-E*. Check out this photo! :sparkles:',
  'wall-e-and-eve-wallpaper-2.jpg',
  data,
  title="Day at the beach!")
```

Which looks like this:

![](https://keestalkstech.com/wp-content/uploads/2019/10/eve-file-upload-slack.png)
*The image is showing up in Slack.*

## Debugging

The Slack API works a little different from other APIs. When a request is invalid, [it returns an HTTP 200 with a JSON error message](https://api.slack.com/web#evaluating_responses). Because all of our methods return the JSON value, debugging is easy! Just print out the result:

```py
print(post_blocks_to_slack("What is the matter with you!?", {}))
```

This will tell you all you need to know:

```json
{'ok': False, 'error': 'invalid_blocks_format'}
```

## Scripts: BASH me?

Some people use Python in their scripting. If you are writing a BASH script, you *don't HAVE to use Python* to send something to Slack. Just use the following BASH function to send a text via cURL to Slack:

```sh
#!/bin/bash
function post_message_to_slack() {
    # Parameters:
    # $1 is the slack channel, should start with a #
    # $2 is the text message

    local slack_token='xoxb-'
    local slack_icon_url='https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTuGqps7ZafuzUsViFGIremEL2a3NR0KO0s0RTCMXmzmREJd5m4MA&s'
    local slack_user_name='Double Images Monitor'
    local slack_channel="$1"
    local text="$2"

    curl --request POST \
         --data-urlencode "token=$slack_token" \
         --data-urlencode "channel=$slack_channel" \
         --data-urlencode "icon_url=$slack_icon_url" \
         --data-urlencode "username=$slack_user_name" \
         --data-urlencode "text=$text" \
         'https://slack.com/api/chat.postMessage' \
         -w "\n"
}
```

You can now use it like this:

```sh
post_message_to_slack "#chatops-tests" "To be, or not to be! 🤔"
```

## Conclusion

*Slack API. Is. Super. Simple.* So... you don't really need a package to post a message to a Slack channel. Adding messages to your Python scripts and applications will give your end-users a richer experience. That's why I love [ChatOps](https://keestalkstech.com/category/automation/chatops/).

After thought: you might want to [consider to just add a simple web hook](https://keestalkstech.com/2022/09/dont-use-slack-incoming-webhooks-app-creation-is-dead-simple/).

The code is on GitHub, so check it out: [code gallery / 9. simple slack messages](https://github.com/KeesCBakker/keestalkstech-code-gallery/tree/main/09.simple-slack-messages).

## Improvements

- 2025-01-03: Added the [application manifest](#manifest) and implemented the new `getUploadURLExternal` API for [uploading files](#how-about-files).
- 2022-09-07: Added the webhook link.
- 2021-06-07: Added the [Help, my icons are not showing?!](https://keestalkstech.com/2019/10/simple-python-code-to-send-message-to-slack-channel-without-packages/#help-my-icons-are-not-showing) section. Also I swapped the icon URL out for a Slack emoji, which has way better security.
- 2020-03-14: Formatting / highlighting of the code.
- 2020-07-03: Added a pure BASH/cURL example.
- 2019-11-07: Merged sending of blocks and sending of text messages.
- 2019-11-05: Added `.json()` returns for all methods to support better debugging. Added [debugging section](#debugging). Added support for [blocks](#how-about-blocks).
- 2019-10-31: Added support for [files](#how-about-files).
