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:
- Open your Slack workspace in a browser (e.g.
https://your-company.slack.com) - Open DevTools (F12)
Get the d cookie:
- Go to Application > Storage > Cookies >
app.slack.com - Find the cookie named
d(its value starts withxoxd-) - Copy the full value
Get the xoxc token:
- Go to the Console tab
- If on chrome enable pasting by typing "allow pasting"
- Run:
JSON.parse(localStorage.getItem('localConfig_v2')).teams[Object.keys(JSON.parse(localStorage.getItem('localConfig_v2')).teams)[0]].token
- 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f4865e7db43dcabac603bed148c2c762da6908f16fdee3bece03a86890965b35
|
|
| MD5 |
4d5ec24f9194e2288953f2d8921e8534
|
|
| BLAKE2b-256 |
64cd769ed4f1e0cf8e5a7c8254cf3e1fbb2704c80e938d50ff599dfbdc2f3c1e
|
File details
Details for the file slack_scheduler-0.2.0-py3-none-any.whl.
File metadata
- Download URL: slack_scheduler-0.2.0-py3-none-any.whl
- Upload date:
- Size: 17.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f99b8ef60dfa78f648b54c9976a428901b7e84d92c976545d5551b30cb99ba89
|
|
| MD5 |
ad6f1da4b6dc14bae95e0ca4b1584839
|
|
| BLAKE2b-256 |
5c36d583238999b13998040ee74acc288de9de280761e39eebb91b26b354d3c0
|