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

Simple Python code to send message to Slack channel Simple Python code to send message to Slack channel

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 really 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. Enojy!

  1. Intro
  2. Setup
  3. Posting to a channel
    1. Help, my icons are not showing?!
  4. How about blocks?
  5. How about files?
  6. Debugging
  7. Scripts: BASH me?
  8. Conclusion
  9. Improvements
  10. Comments

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 #).

slack_token = 'xoxb-my-bot-token'
slack_channel = '#my-channel'
slack_icon_emoji = ':see_no_evil:'
slack_user_name = 'Double Images Monitor'

I use the same bot for all my messages. To let my users know which application is sending the message, I use a per application icon and user name.

I usually use a single cell for my imports:

import requests
import json

Posting to a channel

The Slack API provides a nice chat.postMessage endpoint that we can use. We'll post a dictionary to the endpoint and a message will appear in our Slack channel:

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

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

slack_info = 'There are *{}* double images detected for *{}* products. Please check the <https://{}.s3-eu-west-1.amazonaws.com/{}|Double Images Monitor>.'.format(
  double_images_count, products_count, bucket_name, file_name)

post_message_to_slack(slack_info)		

It will look like this:

Screenshot of a Slack message with markdown.
These text messages will give your users direct information from your scripts and applications.

Help, my icons are not showing?!

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

How about blocks?

I. Love. The. Block. Kit. It allows us to build complex messages, like this:

Screenshot of a Slack Block Kit message.
Block Kit messages will give you even more control over the format of the message.

You can design block kit messages in the Block Kit Builder. I use Python objects to generate the blocks. A block-message can be sent by like this:

blocks = [{  
  "type": "section",
  "text": {  
    "type": "mrkdwn",
    "text": ":check: The script has run successfully on the dev."
  }
}]

post_message_to_slack(
   "Text shown in popup.",
   blocks);

How about files?

We can send files through the file.upload API. Unfortunately, it does not support the username and icon_url properties.

def post_file_to_slack(
  text, file_name, file_bytes, file_type=None, title=None
):
    return requests.post(
      'https://slack.com/api/files.upload', 
      {
        'token': slack_token,
        'filename': file_name,
        'channels': slack_channel,
        'filetype': file_type,
        'initial_comment': text,
        'title': title
      },
      files = { 'file': file_bytes }).json()

It makes sending text files super easy:

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

Which is displayed like this:

screenshot of a Slack message with a file attached.
Text files will appear in the slack interface.

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

import urllib.request

image = "https://images.unsplash.com/photo-1495954484750-af469f2f9be5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80"
response = urllib.request.urlopen(image)
data = response.read()  

post_file_to_slack(
  'Amazing day at the beach. Check out this photo.',
  'DayAtTheBeach.jpg',
  data)

Which looks like this:

Screenshot of a Slack message with a photo.
Images are shown 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. Because all of our methods return the JSON value, debugging is easy! Just print out the result:

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

This will tell you all you need to know:

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

#!/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:

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.

Improvements

2021-06-07: Added the 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. Added support for blocks.
2019-10-31: Added support for files.

  1. Jung says:

    Would there be a way to use the user token? I couldn’t find a way to get my user token.

    1. Kees C. Bakker says:

      I’m not sure, I always use bot tokens in my applications. The docs (https://api.slack.com/methods/chat.postMessage) say that a user token should be able to do the same operation.

  2. Gabriel Oliveira says:

    how do I notify someone inside the message? Tried @username and <@username>, but neither worked =/

    1. Kees C. Bakker says:

      What worked for me was:

      slack_info = ‘Hello world! <@kbakker>‘
      post_message_to_slack(slack_info)

      My usename (@kbakker) is not the same as my display name (@kz). It prints the following in my Slack: https://uploads.disquscdn.com/images/c4b338c2edb63673a2270a6ab3f7a1aaf6a69a4895e750f4cfa8bd8a3482bc78.png

  3. lavanya says:

    How do i send message to a user instead of a channel ?

    1. lavanya says:

      OP, I would like to say a big thank you for sharing these codes :)

  4. Eric DuBose says:

    Hi! How would we possibly send @Channel notifications in this? I tried the recommendation you put lower @keescbakker:disqus , but it only worked for individual users.

    Additionally, can we use this to call another slash command? Say I want to use a slash command called /exports that checks for how many people updated their files. Is there a mechanism that allows that to be passed, and read by the client in a way that the slash command executes as if i had typed it in?

    Thank you for this article and information!!!

    1. Kees C. Bakker says:

      As far as I know “@channel” and “@here” can only be invoked by users that are in the chat. The same goes for slash commands.

expand_less