Slack-integrated on-call management tool backed by Notion. Deployed as a single GCP Cloud Function.
Manage your team's on-call schedule entirely through Slack slash commands, with Notion as the source of truth. Daily automations handle shift transitions, reminders, and @oncall user group updates β no manual bookkeeping needed.
- π Shift management β view, switch, and swap on-call shifts from Slack
- π Smart reminders β DMs sent 1 day and 7 days before your shift
- π Shift swaps β broadcast requests to the team or ask someone directly
- π« Constraints β block out dates you're unavailable
- :robot_face: Daily automation β shift transitions,
@oncallgroup updates, channel notifications - π Secure β Slack signature verification, OIDC-authenticated cron, shared secret validation
βββββββββββ βββββββββββββββββββββββββββ βββββββββββ
β Slack βββHTTPββ>β GCP Cloud Function βββAPIββ> β Notion β
β (UI) β<ββββββ β (Node.js / TypeScript) β<ββββββ β (Data) β
βββββββββββ βββββββββββββββββββββββββββ βββββββββββ
^
β HTTP (OIDC auth)
βββββββββββ΄ββββββββββ
β Cloud Scheduler β
β (daily 09:30) β
βββββββββββββββββββββ
- Runtime: Node.js 20, TypeScript (strict mode)
- Data store: Notion databases (sole source of truth)
- Interface: Slack (sole UI β slash commands + interactive messages)
- Hosting: GCP Cloud Function (event-driven, scales to zero)
- GCP account with billing enabled (Cloud Functions, Cloud Scheduler, Artifact Registry)
- Slack workspace with admin access to create apps
- Notion workspace with an integration and on-call databases set up
-
Clone and install:
git clone https://github.com/roeezolantz/notion-oncaller.git cd notion-oncaller npm install -
Set up Notion integration:
- Create an integration at notion.so/my-integrations
- Share your On-Call Schedule and Constraints databases with the integration
- See docs/SETUP.md for detailed instructions
-
Set up Slack app:
- Create a new app at api.slack.com/apps
- Configure slash commands, interactivity, and bot scopes
- See docs/SLACK_APP_SETUP.md for detailed instructions
-
Configure environment:
cp .env.example .env # Fill in your Notion, Slack, and app values -
Build and test:
npm run build npm test -
Deploy:
./deploy/deploy.sh ./deploy/setup-scheduler.sh <function-url>
| Command | Description |
|---|---|
/oncall list |
Show all upcoming shifts |
/oncall mine |
Show your upcoming shifts |
/oncall now |
Who's on-call right now? |
/oncall replacement |
Need someone to cover your shift (one-way) |
/oncall swap |
Trade shifts with someone (two-way, proposal-based) |
/oncall block |
Block out dates you're unavailable |
/oncall my-blocks |
Show your blocked dates |
/oncall broadcast |
Preview, then DM every on-call their upcoming shifts (admin-only send) |
/oncall help |
Show available commands |
One-way coverage β someone takes your shift, no trade needed:
- Run
/oncall replacementβ a modal shows your upcoming shifts - Pick which shift you need covered
- Bot posts to the channel: "Alice needs someone to cover May 4-11"
- A teammate clicks "I'll cover"
- The shift is reassigned to them in Notion β done
- The original message updates to show the result (buttons removed)
Two-way trade β both shifts change owners:
- Run
/oncall swapβ pick which shift you want to trade - Bot posts to the channel: "Alice wants to swap her May 4-11 shift"
- Teammates click "Propose my shift" β a modal shows their shifts to offer
- You receive a DM for each proposal with Accept / Decline buttons
- Click Accept on the one you want β both shifts swap in Notion
- Both parties and the channel are notified
Block out dates when you're unavailable:
- Run
/oncall blockβ a modal opens with date pickers - Select start date, end date, and optional reason
- The block is saved to the Notion Constraints database
- If your blocked dates overlap an existing shift, you'll get a warning
- View your blocks anytime with
/oncall my-blocks
Send every on-call team member a DM with their upcoming shifts β useful after schedule changes:
- Anyone runs
/oncall broadcastβ an ephemeral preview shows who would be DM'd and the exact shift list per person. - Admins (listed in
ONCALL_ADMINS) see Send and Cancel buttons; non-admins see the same preview with a notice that only admins can send. - Click Send β the plan is rebuilt against current Notion state (a true dry-run preview), and DMs go out one by one with action buttons:
- View Full Schedule β opens
NOTION_SCHEDULE_URL - Request Replacement β opens the same picker as
/oncall replacement - Request Swap β opens the same picker as
/oncall swap
- View Full Schedule β opens
- The preview message is replaced with a result line:
Sent to N. Skipped M (no Slack mapping). Failed K (see logs). - People with no upcoming shifts or no Slack mapping are skipped automatically.
Every day at 09:30 (configurable timezone), Cloud Scheduler triggers the /cron/daily endpoint:
- Shift transition β detects if the on-call person changed today
- User group update β sets
@oncallto the current on-call person - Channel notification β posts a shift change message if applicable
- Upcoming reminders β DMs people whose shifts start in 1 day or 7 days
| Layer | Mechanism |
|---|---|
| Slack requests | Slack signing secret verification (HMAC-SHA256) |
| Cron endpoint | OIDC token authentication via GCP service account |
| Cron validation | Shared CRON_SECRET header check |
Your Notion integration needs these capabilities:
| Permission | Why |
|---|---|
| Read content | Query on-call schedule and constraints |
| Insert content | Create new constraint entries |
| Update content | Swap/replace shift assignments |
Your Slack app needs these OAuth scopes:
| Scope | Why |
|---|---|
chat:write |
Post messages to the on-call channel |
commands |
Register slash commands |
im:write |
Send reminder DMs to users |
users:read |
Look up user info for display names |
users:read.email |
Match Slack users to Notion entries by email |
usergroups:write |
Update @oncall user group membership |
usergroups:read |
Read current user group membership |
| Service | Purpose |
|---|---|
| Cloud Functions | Hosts the application (single function, scales to zero) |
| Cloud Scheduler | Triggers the daily automation cron job |
| Artifact Registry | Stores container images for deployment |
| Variable | Description |
|---|---|
NOTION_API_KEY |
Notion integration secret token |
NOTION_ONCALL_DB_ID |
Data source ID from On-Call Schedule database |
NOTION_CONSTRAINTS_DB_ID |
Data source ID from On-Call Constraints database |
NOTION_CONSTRAINTS_PAGE_ID |
Database page ID from On-Call Constraints (for creating pages) |
NOTION_SCHEDULE_URL |
Full Notion URL to your On-Call Schedule (used in Slack buttons) |
SLACK_BOT_TOKEN |
Slack Bot User OAuth Token (xoxb-...) |
SLACK_SIGNING_SECRET |
Slack App Credentials signing secret |
SLACK_ONCALL_CHANNEL |
Slack channel ID for notifications |
SLACK_ONCALL_USERGROUP_ID |
Slack @oncall user group ID |
CRON_SECRET |
Shared secret for cron endpoint authentication |
ONCALL_ADMINS |
Comma-separated emails allowed to send /oncall broadcast (e.g. alice@example.com,bob@example.com) |
npm run build # Compile TypeScript
npm run dev # Watch mode + local server
npm test # Run tests
npm run test:watch # Watch mode tests
npm run test:coverage # Tests with coverage report
npm run lint # Lint source files- Copy
.env.exampleto.envand fill in your values - Run
npm run devto start the local server with file watching - Use a tunnel (e.g., ngrok) to expose your local server to Slack
./deploy/deploy.sh# Default: Asia/Jerusalem timezone
./deploy/setup-scheduler.sh <function-url>
# Custom timezone
./deploy/setup-scheduler.sh <function-url> <project-id> <region> <service-account> America/New_York
# Arguments:
# 1. function-url (required) β deployed function URL
# 2. project-id (optional) β GCP project, defaults to gcloud config
# 3. region (optional) β GCP region, defaults to me-west1
# 4. service-account (optional) β OIDC service account
# 5. timezone (optional) β defaults to Asia/Jerusalemnotion-oncaller/
βββ src/
β βββ index.ts # Cloud Function entry point
β βββ config.ts # Environment configuration
β βββ utils.ts # Shared utilities
β βββ types/
β β βββ index.ts # TypeScript type definitions
β βββ handlers/
β β βββ slack.ts # Slash command handlers
β β βββ interactions.ts # Interactive message handlers
β β βββ cron.ts # Daily automation handler
β β βββ __tests__/ # Handler tests
β βββ services/
β βββ notion.ts # Notion API client
β βββ slack.ts # Slack API client
β βββ userMapping.ts # Slack β Notion user mapping
β βββ __tests__/ # Service tests
βββ deploy/
β βββ deploy.sh # Cloud Function deployment script
β βββ setup-scheduler.sh # Cloud Scheduler setup script
β βββ setup-constraints-db.ts # Constraints DB setup helper
βββ docs/ # Setup guides
βββ .env.example # Environment template
βββ tsconfig.json # TypeScript configuration
βββ jest.config.js # Jest configuration
βββ package.json
See CONTRIBUTING.md for guidelines.
MIT β see LICENSE