PostgreSQL → Slack

PostgreSQL query results, posted to Slack

A PostgreSQL Slack integration without the webhook script. Write the query once, pick a channel and a cadence, and the numbers arrive where your team already talks. From €19/month.

RDS, Aurora, Neon, Supabase & self-hosted · No webhook code · No credit card required

daily_revenue.sqlpostgres
SELECT
  sum(amount) AS revenue,
  count(*) AS orders
FROM payments
WHERE created_at >= current_date - 1;
Every morning
#leadership08:00

SaturnSQLAPP

Daily revenue

Revenue yesterday: €48,290

Orders: 214

Open in SaturnSQL

Sheet or channel?

Sheets are a pull destination, good for lists people work through. Slack is a push destination, good for numbers people need to notice. Most teams run both, and Postgres to Google Sheets covers the other half.

The DIY routes

Three ways to get Postgres data into Slack yourself

All of them work. The real question is who maintains the plumbing once it is running.

A cron job: psql plus an incoming webhook

Create a Slack app with an incoming webhook, then let cron run a script that queries Postgres and posts the result. This one needs psql 12 or newer for --csv, plus jq to build the JSON safely.

#!/usr/bin/env bash
set -euo pipefail

rows=$(psql "$DATABASE_URL" -X --csv -c "
  SELECT count(*) AS signups
  FROM users
  WHERE created_at >= current_date - 1
    AND created_at <  current_date")

jq -n --arg rows "$rows" \
  '{text: ("Signups yesterday\n```\n" + $rows + "\n```")}' |
  curl -fsS -X POST -H 'Content-type: application/json' \
    --data @- "$SLACK_WEBHOOK_URL"
# crontab: every day at 08:00
0 8 * * * /opt/reports/signups-to-slack.sh

Good for

One report, owned by an engineer, on a server you already run. It costs nothing in software and you control every byte.

The catch

A box that must stay up, a database password and webhook URL stored on it, and failed runs nobody hears about unless you build alerting too. Each incoming webhook posts to a single channel, and the message is raw CSV unless you write the formatting.

Inside the database: pg_cron

Good for

Scheduling SQL that changes data, such as refreshing a materialized view or pruning old rows. It runs on many managed hosts, including RDS, Cloud SQL, Supabase, and Neon.

The catch

pg_cron runs SQL only and cannot make HTTP requests, so it cannot reach Slack by itself. On Supabase, pg_net adds HTTP calls from SQL and the two combine, but then you build and format the Slack payload in SQL, from your production database.

Automation tools: Zapier or n8n

Good for

Event-shaped alerts. Zapier’s PostgreSQL triggers (New Row, New Row (Custom Query), New or Updated Row) poll your database and fire on new rows, which suits a ping per signup.

The catch

A morning summary is a different shape from a per-row event. n8n’s Postgres Trigger works by creating a trigger and procedure on your table, so its database user needs permission to create them. Either way the SQL lives in one tool and the Slack formatting in another.

With SaturnSQL

Set it up in five minutes

No webhook to host. No cron box. No workflow tool in the middle.

1

Connect Postgres

Add your connection with a read-only user and turn on read-only mode. RDS, Aurora, Neon, Supabase, and self-hosted Postgres all work.

2

Aggregate in SQL

Write the query so it returns a few rows, not a raw extract. Slack rewards a summary.

3

Schedule to a channel

Pick the channel and a cadence, optionally template the message, preview it, and send a test post before saving.

Schedules run hourly, daily, weekly, monthly, or on a custom cron expression in the timezone you choose. Public channels appear in a dropdown; for a private channel, invite @SaturnSQL and paste its channel ID. A failed run retries automatically for up to seven days and emails whoever created the schedule. Step by step, in the Slack export help guide.

Patterns

Three flows worth wiring up

#leadership

The morning digest

Yesterday’s revenue, signups, and churn as three numbers at 08:00. Short enough to read on a phone, which is the entire point of putting it in chat rather than a dashboard.

#support

The work queue

Stuck orders or failed payments from the last hour, posted to the team that clears them. The thread under each post becomes the record of who handled what.

#data-alerts

The health check

One row counting failed jobs or unprocessed events. It posts 0 on a good day and the real number on a bad one, so the channel also proves the check actually ran.

Message templates

A sentence beats a table

By default the result posts as a monospace table with a link back to the query. Alias a column in SQL and reference it in braces, and the post reads like something a person wrote.

Template

Revenue yesterday: **{revenue}** across {orders} orders.
Run {date} · {query_name}

Posts as

Revenue yesterday: 48,290 across 214 orders.
Run 2026-10-03 · Daily revenue

Placeholders resolve against the first result row, so {revenue} means “the revenue column of row one”. Built-ins {results}, {row_count}, {query_name} and {date} are always available. An unknown placeholder fails the delivery rather than posting a blank, because a silently wrong KPI is worse than a missing one.

Postgres specifics

Four details worth getting right

Read-only, enforced

Turn on read-only mode for the connection and every query runs inside a READ ONLY transaction, so a scheduled report cannot write to production even if the SQL tries. A read-only database role is still the better first line.

Whose yesterday?

current_date follows the database session's TimeZone setting, not your schedule's. For date windows, use a query variable such as '{{report_date}}' with the default yesterday. A scheduled run resolves it in the schedule's timezone.

Format numbers in SQL

Values reach the message as Postgres returns them, so a numeric(12,2) total posts as 48290.50. Wrapping it in to_char(sum(amount), 'FM999,999,990') posts 48,290 instead.

Plan for the quiet day

sum() over zero rows returns NULL, which a template prints as nothing. coalesce(sum(amount), 0) makes a quiet day read 0 rather than a blank.

Writing the query in the browser comes with schema-aware autocomplete in the PostgreSQL SQL editor, and our PostgreSQL how-to guides cover date math, aggregates, and the rest of the SQL behind a good summary.

Where SaturnSQL is the wrong tool

Every scheduled run posts. There is no threshold rule that stays silent until a value crosses a line, so if you need someone paged only when something breaks, an alerting tool or a small script is the right fit. Schedules can run at most every five minutes, so this is not a real-time event stream either.

Slack also caps a message section at 3,000 characters, and SaturnSQL fails the delivery rather than truncating it. If the honest answer is 500 rows, schedule that query to Google Sheets and post a short summary to Slack alongside it. A schedule has a single destination, so that is two schedules on the same saved query. Posting from MySQL, SQL Server, or a warehouse instead? See database to Slack.

FAQ

Frequently asked questions

How do I send PostgreSQL query results to Slack?

Connect Postgres to SaturnSQL with a read-only user, write and save the query, then add a schedule with Slack as the destination and pick a channel. The query runs on your cadence and the result posts itself. There is no webhook to host and no cron job to maintain.

Can pg_cron send query results to Slack on its own?

No. pg_cron schedules SQL inside the database and cannot make HTTP requests, so it has no way to reach Slack by itself. Some hosts, Supabase among them, offer the pg_net extension for HTTP calls from SQL, but then you are building and formatting the Slack payload in SQL.

How large a result can I post to Slack?

Slack caps a message section at 3,000 characters. SaturnSQL fails the delivery rather than truncating it, so you find out instead of quietly posting half a table. Slack suits aggregates and short lists. For full extracts, schedule the same query to Google Sheets instead.

What happens if the query returns no rows?

Without a template, the message still posts and shows 0 rows. With a template that references a column, the delivery fails, because there is no first row to read the value from. An aggregate such as count(*) without GROUP BY always returns exactly one row, which makes it the safest shape for a templated post.

Does it work with Supabase, Neon, Aurora and RDS?

Yes. SaturnSQL connects to PostgreSQL and Postgres-compatible hosts including Amazon RDS, Aurora Postgres, Neon, Supabase, and Crunchy. For databases on a private network, the Starter plan includes SSH tunnels and a static IP address you can allowlist.

Which plan includes the Slack integration?

Starter, at €19 per user per month, which also includes scheduling and Google Sheets export. The free plan does not include scheduling. You can try Starter free for 14 days without a credit card.

Your Postgres numbers, in the channel

From €19/mo. Five minutes to set up, and no script to babysit.

Start free for 14 days

No credit card required.