Skip to content

About

Slack-integrated on-call management tool backed by Notion

Resources

Code of conduct

Contributing

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

notion-oncaller

License: MIT Node.js TypeScript

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.


Features

  • πŸ“† 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, @oncall group updates, channel notifications
  • πŸ”’ Secure β€” Slack signature verification, OIDC-authenticated cron, shared secret validation

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  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)

Prerequisites

  • 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

Quick Start

  1. Clone and install:

    git clone https://github.com/roeezolantz/notion-oncaller.git
    cd notion-oncaller
    npm install
  2. Set up Notion integration:

  3. Set up Slack app:

  4. Configure environment:

    cp .env.example .env
    # Fill in your Notion, Slack, and app values
  5. Build and test:

    npm run build
    npm test
  6. Deploy:

    ./deploy/deploy.sh
    ./deploy/setup-scheduler.sh <function-url>

Slash Commands

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

How Replacement Works

One-way coverage β€” someone takes your shift, no trade needed:

  1. Run /oncall replacement β€” a modal shows your upcoming shifts
  2. Pick which shift you need covered
  3. Bot posts to the channel: "Alice needs someone to cover May 4-11"
  4. A teammate clicks "I'll cover"
  5. The shift is reassigned to them in Notion β€” done
  6. The original message updates to show the result (buttons removed)

How Swap Works

Two-way trade β€” both shifts change owners:

  1. Run /oncall swap β€” pick which shift you want to trade
  2. Bot posts to the channel: "Alice wants to swap her May 4-11 shift"
  3. Teammates click "Propose my shift" β€” a modal shows their shifts to offer
  4. You receive a DM for each proposal with Accept / Decline buttons
  5. Click Accept on the one you want β€” both shifts swap in Notion
  6. Both parties and the channel are notified

How Blocks Work

Block out dates when you're unavailable:

  1. Run /oncall block β€” a modal opens with date pickers
  2. Select start date, end date, and optional reason
  3. The block is saved to the Notion Constraints database
  4. If your blocked dates overlap an existing shift, you'll get a warning
  5. View your blocks anytime with /oncall my-blocks

How Broadcast Works

Send every on-call team member a DM with their upcoming shifts β€” useful after schedule changes:

  1. Anyone runs /oncall broadcast β€” an ephemeral preview shows who would be DM'd and the exact shift list per person.
  2. Admins (listed in ONCALL_ADMINS) see Send and Cancel buttons; non-admins see the same preview with a notice that only admins can send.
  3. 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
  4. The preview message is replaced with a result line: Sent to N. Skipped M (no Slack mapping). Failed K (see logs).
  5. People with no upcoming shifts or no Slack mapping are skipped automatically.

Daily Automation

Every day at 09:30 (configurable timezone), Cloud Scheduler triggers the /cron/daily endpoint:

  1. Shift transition β€” detects if the on-call person changed today
  2. User group update β€” sets @oncall to the current on-call person
  3. Channel notification β€” posts a shift change message if applicable
  4. Upcoming reminders β€” DMs people whose shifts start in 1 day or 7 days

Security

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

Notion Permissions

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

Slack Bot Scopes

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

GCP Services Used

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

Environment Variables

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)

Development

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

Local Development

  1. Copy .env.example to .env and fill in your values
  2. Run npm run dev to start the local server with file watching
  3. Use a tunnel (e.g., ngrok) to expose your local server to Slack

Deployment

Deploy the Cloud Function

./deploy/deploy.sh

Set Up the Scheduler

# 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/Jerusalem

Project Structure

notion-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

Contributing

See CONTRIBUTING.md for guidelines.

License

MIT β€” see LICENSE

About

Slack-integrated on-call management tool backed by Notion

Resources

Code of conduct

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages