Mirroring Slack notifications on your phone

3 minute read

Slack has a little big issue in their notification system. If you’re active on desktop, you won’t get mobile notifications until you become inactive. The issue is that this might take a while (until your computer sleeps or your screen turns off) and you’ll miss messages in the meanwhile.

I’ve read reports of people experiencing this same issue (1, 2, 3), but no easy or clear solution. Logging in on the web version of Slack forwards notifications to mobile as well, but I’ve had issues with Chrome memory management freezing my tab when not interacting with it, and that would make me appear offline even when I was working.

However, since this behavior is not configurable by the user, I’m prone to think it is intentional. I would not be surprised to find out that notification mirroring was supported in the past.

I hacked together a solution on Windows, which securely forwards Windows Action Center notifications to my Android phone. Might be even easier to implement on other systems.

Forwarding Windows Action Center notifications

The first obvious problem to solve is forwarding Windows notifications. I found an unfortunately unnoticed software on GitHub, which does exactly that. Download here, source here.

After installing, you can select which apps to forward and the endpoint to send them to.

Configuring apps forwarding.

Setting up a receiving Python server

I set up a simple Python server to handle requests. You can find a basic one here (not written by me). The notification forwarding software sends a POST request to the root of the endpoint you configure, with the following data:

{
  "ClientVersion": "1.1.24.0",
  "Notifications": [
    {
      "App": {
        "AppUserModelId": "com.squirrel.slack.slack",
        "Id": "",
        "DisplayName": "Slack",
        "ForwardingEnabled": false
      },
      "TimeStamp": "2020-05-11T12:21:53.9043650",
      "Title": "New message from Slackbot",
      "Content": "What do you know, it works?"
    }
  ]
}

Since the content is still unencrypted, you want to send this to either the same machine you’re receiving notifications on, or to another in your local network. Sending the above payload unencrypted over the Internet is a bad idea!

We can easily extract the notification title and content from the JSON:

post_data_json = json.loads(post_data.decode("utf-8"))

if "NotificationForwarder" in self.headers["User-Agent"] and "Notifications" in post_data_json:
    for notification in post_data_json["Notifications"]:
        title = notification["Title"] if "Title" in notification else ""
        content = notification["Content"] if "Content" in notification else ""
        # send payload to mobile device

Sending notifications to your phone

I use Join for this purpose. There is an API which is very easy to use with Python (docs here). Of course you can use any other forwarding platform you’d like (Pushbullet, Pushover, Telegram to name a few).

Encryption is not documented in the API but you can get it working by encrypting messages the same way the Chrome extension does (1, 2).

Anyway, here’s a basic message encryption function:

# adapted from https://www.reddit.com/r/JoinApp/comments/83bv12/encrypted_push_support_in_join/
import base64
import hashlib

from Crypto import Random
from Crypto.Cipher import AES

JOIN_PASSWORD = "YOUR_PASSWORD"
JOIN_EMAIL = "YOUR_EMAIL"

key = hashlib.pbkdf2_hmac('SHA1', JOIN_PASSWORD.encode(), JOIN_EMAIL.encode(), 5000, 32)
pad = lambda s: s + (AES.block_size - len(s) % AES.block_size) * chr(AES.block_size - len(s) % AES.block_size)

def encrypt(text):
    iv = Random.new().read(AES.block_size)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    return base64.b64encode(iv).decode() + "=:=" + base64.b64encode(cipher.encrypt(pad(text))).decode()

and sending function:

import urllib.parse
import requests

JOIN_API_KEY = "YOUR_API_KEY"
JOIN_SEND_URL = "https://joinjoaomgcd.appspot.com/_ah/api/messaging/v1/sendPush"

def join_push(title, text):
    query_string = '?apikey={0}&deviceNames={1}&title={2}&text={3}'.format(JOIN_API_KEY, "YOUR_DEVICE", urllib.parse.quote(title), urllib.parse.quote(text))
    requests.get(JOIN_SEND_URL + query_string)

The only issue I’m having is that notification titles are not getting decrypted by the mobile app. This can be easily worked around by concatenating the title to the text, or setting a safe title like “New Slack notification”

Receiving notifications on my phone.

Keeping it running

I keep the server up and running with supervisor:

sudo apt install supervisor
sudo nano /etc/supervisor/supervisord.conf

with this sample config:

[program:notiforward]
user=ftruzzi
directory=/home/ftruzzi/
command=python3 -u notiforward.py
startsecs=3
autostart=true
autorestart=unexpected
stdout_logfile=/var/log/supervisor/notiforward.log
stderr_logfile=/var/log/supervisor/notiforward.log

Improving it

  • Maximize security: If you’re using Slack for work, you should avoid forwarding any sensitive data even if encrypted. In Slack, you can disable notification message previews (no content will be displayed in notifications) and/or modify Notification Forwarder (or your Python server) to send a generic title which is the same for all notifications, such as “New Slack notification”.

  • Skip the middle endpoint: Ideally, we should send notifications directly from the source computer and skip relaying the notification to a different endpoint before sending it to the phone. Modifying the Notification Forwarder app with custom encrypt and send logic would be the best way to do this.

  • Open Slack automatically: Join notifications can also be configured to trigger actions such as opening apps. A nice improvement would be a “Open Slack” button.

Enjoy notification freedom!

Updated: