Skip to main content

Automated Slack message scheduler with cron-based scheduling, message rotation, and jitter support

Project description

slack-scheduler

Schedule and send Slack messages using browser session tokens — no Slack app or admin API approval required.

Quick Start

pip install slack-scheduler
slack-scheduler init
# Edit your config and credentials (paths shown by init)
slack-scheduler validate
slack-scheduler run

Getting Your Credentials

slack-scheduler authenticates using your browser session tokens (xoxc token + d cookie). Here's how to get them:

  1. Open your Slack workspace in a browser (e.g. https://your-company.slack.com)
  2. Open DevTools (F12)

Get the d cookie:

  1. Go to Application > Storage > Cookies > app.slack.com
  2. Find the cookie named d (its value starts with xoxd-)
  3. Copy the full value

Get the xoxc token:

  1. Go to the Console tab
  2. If on chrome enable pasting by typing "allow pasting"
  3. Run:
    JSON.parse(localStorage.getItem('localConfig_v2')).teams[Object.keys(JSON.parse(localStorage.getItem('localConfig_v2')).teams)[0]].token
    
  4. Copy the xoxc-... token

Save them to your credentials file:

slack-scheduler init  # creates the file with placeholders

Then edit the credentials file (path shown by init) and replace the placeholders:

SLACK_XOXC_TOKEN=xoxc-your-actual-token
SLACK_D_COOKIE=xoxd-your-actual-cookie

Note: These tokens expire (typically after ~1 year, shorter on workspaces with strict security policies). When they expire, repeat the steps above.

Configuration

After running slack-scheduler init, edit the config file:

# Default message selection mode: "random" or "cycle"
default_selection_mode: "random"

# Skip weekends globally (can also be set per channel entry)
# skip_weekends: true

# Skip country-specific bank holidays (ISO 3166 country code)
# Supports subdivisions: "US-CA", "DE-BY", etc.
# skip_holidays: "US"

# Dates to skip globally (YYYY-MM-DD)
skip_dates:
  - "2026-12-25"
  - "2026-01-01"

channels:
  - id: "C1234567890"        # Slack channel ID
    name: "standup"           # Human-readable label (for logs)
    skip_weekends: true

    messages:
      - "Good morning team! Ready for {day_of_week}."
      - "Morning all! Let's have a great {day_of_week}."
      - "Hey team, online and ready. Today is {date}."

    selection_mode: "cycle"   # Override default for this channel

    schedules:
      - cron: "0 9 * * 1-5"  # 9:00 AM, Monday-Friday
        jitter_minutes: 15    # Random delay up to 15 min (sends between 9:00-9:15)

  - id: "C0987654321"
    name: "random-chat"

    messages:
      - "Anyone up for a virtual coffee?"
      - "Happy {day_of_week}!"

    schedules:
      - cron: "0 14 * * 3"   # 2:00 PM every Wednesday
        jitter_minutes: 30

Tip: To find a channel's ID, right-click the channel name in Slack > "View channel details" > the ID is at the bottom.

CLI Commands

init — Set up config directories

slack-scheduler init

Creates config directories and template files at the OS-appropriate locations.

send — Send a message now

# Send a specific message
slack-scheduler send --channel C1234567890 --message "Good morning!"

# Pick randomly from multiple messages
slack-scheduler send --channel C1234567890 --message "Hello!" "Hey there!" "Morning!"

# Cycle through messages (no repeats until all are used)
slack-scheduler send --channel C1234567890 --message "Hello!" "Hey!" "Morning!" --selection-mode cycle

# Add a random delay before sending (useful with cron)
slack-scheduler send --channel C1234567890 --message "Good morning!" --jitter 15

# Preview without sending
slack-scheduler --dry-run send --channel C1234567890 --message "Test"
Flag Description
--channel (required) Target channel ID
--message (required) One or more messages (random selection by default)
--jitter <minutes> Random delay of 0 to N minutes before sending
--selection-mode random or cycle (overrides config)

trigger — Fire a config entry once

# Trigger a named channel config entry
slack-scheduler trigger --name standup-morning

# Override messages from config
slack-scheduler trigger --name standup-morning --message "Custom message"

# With jitter
slack-scheduler trigger --name standup-morning --jitter 10

# Respect skip rules (weekends, dates, holidays)
slack-scheduler trigger --name standup-morning --respect-skips

# Preview without sending
slack-scheduler --dry-run trigger --name standup-morning
Flag Description
--name (required) Name of the channel config entry to trigger
--message Override message (multiple for random/cycle selection)
--jitter <minutes> Random delay of 0 to N minutes before sending
--selection-mode random or cycle (overrides config)
--respect-skips Check the channel's skip rules before sending

run — Start the scheduler daemon

slack-scheduler run

# Skip bank holidays for a specific country
slack-scheduler run --skip-holidays NL

# Preview mode (logs what would be sent)
slack-scheduler --dry-run run
Flag Description
--skip-holidays <country> Skip country-specific bank holidays (e.g. US, NL, DE-BY)

Runs continuously and sends messages according to your config schedules. Stop with Ctrl-C.

status — Show upcoming messages

slack-scheduler status

# Show next 10 per schedule
slack-scheduler status --count 10

# Preview with holiday skipping
slack-scheduler status --skip-holidays US

validate — Check credentials

slack-scheduler validate

Global Flags

Flag Description
--config <path> Path to config.yaml (overrides default)
--env <path> Path to credentials file (overrides default)
--dry-run Preview actions without sending
--verbose Enable debug logging

Message Templates

Messages support variable substitution:

Variable Example Output
{date} 2026-03-04
{day_of_week} Monday
{time} 09:15
messages:
  - "Good morning! Today is {day_of_week}, {date}."
  - "Online and ready at {time}."

Unknown variables are left as-is (e.g. {foo} stays {foo}).

Message Selection Modes

Random (default)

Picks a random message from the pool each time.

Cycle

Shuffles the message pool, then sends each message once before reshuffling. Guarantees every message is used before any repeats. State is persisted across restarts.

selection_mode: "cycle"

Scheduling

Cron Format

Standard 5-field cron syntax: minute hour day month weekday

0 9 * * 1-5     # 9:00 AM, Monday-Friday
30 8 * * *      # 8:30 AM, every day
0 14 * * 3      # 2:00 PM, Wednesdays
0 9,14 * * 1-5  # 9:00 AM and 2:00 PM, weekdays

Jitter

Adds a random delay of 0 to N minutes to the scheduled time so messages don't arrive at exactly the same second every day.

jitter_minutes: 15  # Sends up to 15 minutes after the scheduled time

A schedule at 09:00 with jitter_minutes: 15 will fire between 09:00 and 09:15.

Skip Rules

Skip rules can be set globally or per channel entry. Both levels are merged: a channel entry skips if either the global or entry-level rule applies.

# Global: applies to all channels
skip_weekends: true
skip_dates:
  - "2026-12-24"
skip_holidays: "US"

channels:
  - id: "C1234567890"
    name: "standup"
    # Channel-level: merged with global rules
    skip_holidays: "NL"         # Netherlands holidays added on top of US

skip_weekends, skip_dates, and skip_holidays can all be set globally or per channel entry. Country codes follow ISO 3166; subdivisions use the CC-XX format (e.g. US-CA, DE-BY). See the python-holidays docs for the full list of supported countries.

File Locations

After running slack-scheduler init:

Linux macOS Windows
Config ~/.config/slack-scheduler/ ~/Library/Application Support/slack-scheduler/ %APPDATA%\slack-scheduler\
Credentials ~/.local/share/slack-scheduler/ ~/Library/Application Support/slack-scheduler/ %APPDATA%\slack-scheduler\
Logs ~/.local/share/slack-scheduler/logs/ ~/Library/Application Support/slack-scheduler/logs/ %APPDATA%\slack-scheduler\logs\

All paths are overridable with --config and --env flags.

Running as a Service

systemd (Linux)

Create ~/.config/systemd/user/slack-scheduler.service:

[Unit]
Description=Slack Scheduler

[Service]
ExecStart=slack-scheduler run
Restart=on-failure
RestartSec=30

[Install]
WantedBy=default.target
systemctl --user enable --now slack-scheduler

cron (alternative)

Instead of the daemon, use cron to call send or trigger directly:

# crontab -e
0 9 * * 1-5 slack-scheduler send --channel C1234567890 --message "Good morning!" --jitter 15

# Or trigger a named config entry
0 9 * * 1-5 slack-scheduler trigger --name standup-morning --jitter 15 --respect-skips

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

slack_scheduler-0.2.0.tar.gz (31.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

slack_scheduler-0.2.0-py3-none-any.whl (17.9 kB view details)

Uploaded Python 3

File details

Details for the file slack_scheduler-0.2.0.tar.gz.

File metadata

  • Download URL: slack_scheduler-0.2.0.tar.gz
  • Upload date:
  • Size: 31.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for slack_scheduler-0.2.0.tar.gz
Algorithm Hash digest
SHA256 f4865e7db43dcabac603bed148c2c762da6908f16fdee3bece03a86890965b35
MD5 4d5ec24f9194e2288953f2d8921e8534
BLAKE2b-256 64cd769ed4f1e0cf8e5a7c8254cf3e1fbb2704c80e938d50ff599dfbdc2f3c1e

See more details on using hashes here.

File details

Details for the file slack_scheduler-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for slack_scheduler-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f99b8ef60dfa78f648b54c9976a428901b7e84d92c976545d5551b30cb99ba89
MD5 ad6f1da4b6dc14bae95e0ca4b1584839
BLAKE2b-256 5c36d583238999b13998040ee74acc288de9de280761e39eebb91b26b354d3c0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page