A Slack bot that brings OpenMetadata into the channels where your data team already lives.
DataPulse is a production-grade Slack bot that integrates with OpenMetadata to surface data quality, lineage, and ownership information directly inside Slack. Instead of context-switching to a separate UI to ask "is this table healthy?", "where does this data come from?", or "who owns this asset?", your team can just type /datapulse and get a Block Kit answer in seconds.
It also runs in the background: every 15 minutes it polls OpenMetadata for newly-failing data quality tests and posts an alert to your #data-health channel, and every morning at 9am it posts a daily digest with the tables that need attention, the assets without owners, and an overall health score for the platform.
- 🔍
/datapulse search <query>— find tables by free-text search - 📊
/datapulse quality <fqn>— pass / fail / aborted counts with the latest failures - 🔗
/datapulse lineage <fqn>— upstream and downstream tree (2 levels) - 👤
/datapulse owner <fqn>— owner, team, last-updated, description completeness score - 🚨 Real-time alerts — new test failures posted to
#data-healthevery 15 minutes - 🌅 Daily digest — 9am summary with health score, failing tables, and unowned assets
- 🛡️ Resilient — retries, circuit breaker, per-user rate limiting, friendly error messages
- 🐳 Container-ready — runs anywhere Node.js or Docker runs
┌────────────────────┐
│ Slack │
│ (slash commands, │
│ buttons) │
└─────────┬──────────┘
│ Bolt.js
▼
┌────────────────────┐
│ DataPulse │
│ ┌──────────────┐ │ polls every 15m
│ │ command │ │ ┌──────────────┐
│ │ router │──┼────▶│ alert engine │
│ └──────────────┘ │ └──────┬───────┘
│ ┌──────────────┐ │ │
│ │ rate limiter │ │ ┌──────▼───────┐
│ └──────────────┘ │ │ scheduler │ 9am cron
│ ┌──────────────┐ │ └──────┬───────┘
│ │ Block Kit │ │ │
│ │ renderer │ │ │
│ └──────┬───────┘ │ │
│ │ │ │
│ ┌──────▼───────┐ │ │
│ │ OM client │◀─┼────────────┘
│ │ (retry + │ │
│ │ breaker) │ │
│ └──────┬───────┘ │
└─────────┼──────────┘
▼
┌────────────────────┐
│ OpenMetadata │
│ REST API │
└────────────────────┘
- Go to https://api.slack.com/apps → Create New App → From scratch.
- Under Socket Mode, enable Socket Mode and generate an app-level token (
xapp-…) with theconnections:writescope. - Under OAuth & Permissions, add the bot scopes:
chat:write,commands,chat:write.public. - Under Slash Commands, create
/datapulsewith descriptionQuery OpenMetadata. - Under Interactivity & Shortcuts, enable Interactivity (no Request URL needed in Socket Mode).
- Install the app to your workspace and grab:
- Bot User OAuth Token (
xoxb-…) - Signing Secret (Basic Information → App Credentials)
- App-Level Token (
xapp-…)
- Bot User OAuth Token (
cp .env.example .env
# fill in SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET, SLACK_APP_TOKENThe OpenMetadata sandbox at https://sandbox.open-metadata.org is open — leave OPENMETADATA_TOKEN empty. For self-hosted instances, use a service-account JWT.
npm install
npm startYou should see:
[INFO] DataPulse Slack app started {"mode":"socket"}
[INFO] Alert engine started {"intervalMs":900000,"channel":"#data-health"}
[INFO] Daily digest scheduler started {"cron":"0 9 * * *"}
Then in Slack: /datapulse search customers.
| Command | Description | Example |
|---|---|---|
/datapulse search <query> |
Search tables by name / description | /datapulse search orders |
/datapulse quality <fqn> |
Show pass/fail/aborted test counts + latest failures | /datapulse quality svc.db.shop.orders |
/datapulse lineage <fqn> |
Show upstream/downstream lineage tree | /datapulse lineage svc.db.shop.orders |
/datapulse owner <fqn> |
Show owner, team, last-updated, description score | /datapulse owner svc.db.shop.orders |
/datapulse help |
Show all commands | /datapulse help |
Append --help to any subcommand (e.g. /datapulse quality --help) for detailed usage.
All thresholds live in src/alerts/thresholds.js:
| Setting | Default | Purpose |
|---|---|---|
failingTestsPerTable |
2 |
Digest threshold for "needs attention" |
maxAlertsPerCycle |
10 |
Cap per polling cycle to avoid Slack flooding |
consecutivePollFailures |
3 |
Trip the engine breaker after this many failed polls |
pollCooldownMs |
5 * 60_000 |
How long to pause when the breaker trips |
Operational knobs are set via environment variables:
| Var | Default | Purpose |
|---|---|---|
ALERT_POLL_MINUTES |
15 |
How often to poll for new failures |
DIGEST_CRON |
0 9 * * * |
Cron expression for the daily digest |
DATA_HEALTH_CHANNEL |
#data-health |
Where alerts and the digest are posted |
Production note: alert dedupe state and the rate limiter both use an in-memory
Map. For multi-replica deployments, swap them for Redis — both modules call out the swap in their JSDoc.
railway initand link your repo.- In the Railway dashboard, set the env vars from
.env.example. - Set the start command to
npm start. Railway auto-detects the Dockerfile if present. - Deploy — the bot will connect via Socket Mode, no public URL needed.
- New → Web Service → connect your repo.
- Environment: Docker.
- Add the env vars from
.env.exampleunder "Environment". - Set health check path to
/(or disable, since Socket Mode means no inbound HTTP).
docker compose up -d --buildThe included docker-compose.yml reads .env and runs the bot with tini as PID 1 so SIGTERM shuts the cron + alert loop down cleanly.
- Fork and clone.
npm install.- Write your change with a test in
tests/. npm test— all tests must pass.- Open a PR. CI runs
npm testplus a Docker build smoke test.
Style notes:
- All public functions get a JSDoc block.
- Async/await everywhere — no callbacks.
- Keep Block Kit renderers pure (no I/O) so they stay easy to test.
- Add new alert thresholds to
src/alerts/thresholds.js, not inline.
DataPulse • sandbox.open-metadata.org