Pushrail Docs
Open app
Guides · Destinations

Databases

Insert events into Postgres or MySQL tables for transactional consumption.

Databases

Database destinations insert events as rows in a Postgres or MySQL table. This is the right destination when a downstream service needs to read events from a familiar transactional store, joining against other application tables, building a materialized projection, or driving a notification queue.

When to use

Use a database destination when the consumer already runs on Postgres or MySQL and wants events alongside its existing data. The consumer can read events with the same connection pool, transactions, and ORM it uses for everything else.

If the consumer is an analytics workload, a warehouse is cheaper and faster. If the consumer is a stream processor, a queue or stream gives you better backpressure semantics.

Auth options

Both engines use connection-string-style credentials:

  • Username / password, the default, encrypted at rest.
  • TLS client certificate, for Postgres deployments that enforce mTLS (most managed Postgres providers support this).
  • IAM auth, RDS Postgres and Aurora support IAM database auth; Pushrail mints short-lived tokens on each connection.

The database user needs INSERT on the target table and USAGE on the schema. Do not give it SELECT, UPDATE, or DELETE, Pushrail never reads or modifies existing rows, and a tight grant limits blast radius if a credential leaks.

Config reference

Postgres

Required: host, database, schema, table, auth. Optional: port (default 5432), ssl (default require).

{
  "type": "postgres",
  "host": "events.acme.internal",
  "port": 5432,
  "database": "events_db",
  "schema": "public",
  "table": "pushrail_events",
  "ssl": "require",
  "auth": { "type": "password", "username": "pushrail_writer", "password": "{{secret}}" }
}

The target table is your DDL, Pushrail expects columns to exist for whatever fields you map. A reasonable starting schema:

CREATE TABLE pushrail_events (
  id            uuid PRIMARY KEY,
  event_type    text NOT NULL,
  occurred_at   timestamptz NOT NULL,
  customer_id   text NOT NULL,
  source        text NOT NULL,
  payload       jsonb NOT NULL,
  received_at   timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX ON pushrail_events (event_type, occurred_at);
CREATE INDEX ON pushrail_events (customer_id, occurred_at);

MySQL

Required: host, database, table, auth. Optional: port (default 3306), tls (default required).

{
  "type": "mysql",
  "host": "events.acme.internal",
  "port": 3306,
  "database": "events_db",
  "table": "pushrail_events",
  "tls": "required",
  "auth": { "type": "password", "username": "pushrail_writer", "password": "{{secret}}" }
}

MySQL inserts use multi-row INSERT batches of up to 1,000 rows. The default received_at column can be a TIMESTAMP DEFAULT CURRENT_TIMESTAMP to avoid clock-skew issues between Pushrail and your database.

Common patterns

Project the payload columns you actually query: a JSONB blob plus three or four extracted columns gives you both flexibility and good index coverage. Use a transform to extract payload.orderId into a top-level order_id column before insert.

Upsert on id for replay safety: the event id is stable across retries and replays. An INSERT ... ON CONFLICT (id) DO NOTHING (Postgres) or INSERT IGNORE (MySQL) target makes replays idempotent at the database layer. Pushrail's default insert behavior is plain INSERT; switch to upsert by enabling "Insert mode: upsert" in the destination config.

One table per environment: Development, Staging, and Production destinations should point at different databases (or at least different schemas). Never share a pushrail_events table across environments.

Verifying delivery

The delivery log shows the row(s) inserted, the SQL latency, and any constraint violations the database returned (unique_violation, foreign_key_violation, etc). Constraint violations are classified as permanent, they will not retry on their own.

If you see a sustained spike in latency, check connection-pool exhaustion on the database side; Pushrail uses a small pool per destination (default 5 connections) and surfaces a clear error when the pool is saturated.