notify

Database channel

The durable database notification channel.

This subpackage gives notify its durable, queryable channel: import it when a notification needs a persisted, readable history, not just a mail that went out or a live push.

import notifydatabase "github.com/gp-system/notify/database"

Database

notify/database (+notify/database/bunx) is the durable, queryable side of a notification: every Databasable notification lands in a notifications table, so a recipient gets a persisted, "notification inbox" pattern (the Laravel-notifications-esque part of the module) on top of whatever else fires. It builds on dbx, the same way the rest of the kit's persistence does.

store := notifydatabase.NewStore(pg.NewDB(pool))       // pgx
store := notifydatabasebunx.NewStore(bunDB)             // bun, via notify/database/bunx
channel := notifydatabase.NewChannel(store)

Record and Store

Persists Databasable notifications into a notifications table:

CREATE TABLE notifications (
    id           uuid        PRIMARY KEY,
    recipient_id text        NOT NULL,
    name         text        NOT NULL,
    payload      jsonb       NOT NULL DEFAULT '{}'::jsonb,
    read_at      timestamptz,
    created_at   timestamptz NOT NULL DEFAULT now()
);

No notifiable_type column (the kit has no polymorphic user model, a recipient is just a string id), no updated_at (rows only ever gain read_at). The channel requires Recipient.ID; an on-demand send (no ID) errors on this channel: name only notify.ChannelMail in Via for those.

The Store interface covers the universal 90%: Insert, List (ListOptions{UnreadOnly, Limit, Offset}), CountUnread, MarkRead, MarkAllRead. Every read/update takes recipientID explicitly, so a handler built on top can never touch another recipient's rows by accident. Building the actual HTTP inbox (response DTOs, the rbac.Identity.SubjectrecipientID guard) is your module's own territory: the kit stops at the Store, the same boundary the outbox draws around its own table.

NewChannel(store) wraps a Store into the notify.Channel the Hub dispatches to; it is what a Databasable notification's content routes through.

There is no NOTIFY_* env config: channel enablement is the wiring; the Hub contains exactly the channels the entry point constructs.

MemoryStore

store := notifydatabase.NewMemoryStore() // implements the full Store, including the read side

MemoryStore is a test double: it implements every Store method, so a product can test its inbox handlers (list, mark read, count unread) without Postgres. Pair it with notifydatabase.NewChannel(store) for content-rendering coverage in a Hub, the same way mail.NewMemory() stands in for a real mailer.

Migration

The notifications table arrives as a goose migration, the same pattern outbox.MigrationSQL uses: notifydatabase.MigrationSQL is the schema's source of truth in the package, and add notification's first run writes it into the project's migrations/ directory at the next available timestamp. Run it before starting the service that owns the inbox: go run ./cmd/migrate up (see Migrations).

  • Overview: the Hub, Notification, and the mail channel.
  • Broadcast: the live-push companion to this durable channel.
  • dbx: pgx and dbx: bunx: the executors NewStore and the bunx adapter build on.
Copyright © 2026