notify

Broadcast channel

The realtime broadcast notification channel.

This subpackage gives notify its live-push channel: import it when a notification needs to reach connected clients immediately, on top of or instead of the database channel's durability.

import "github.com/gp-system/notify/broadcast"

Broadcast

notify/broadcast is the live-push side of a notification: a thin, Valkey-backed publisher built on centrifugal/centrifuge, the library behind Centrifugo and Grafana Live. It is deliberately small: publishing is fire-and-forget, and durability lives entirely in the database channel, not here. The package is shared between two roles: any process (typically the worker) imports it just to publish, while the dedicated realtime gateway also uses it to hold connections and relay what gets published; see that page for the gateway side (cmd/realtime, scaling, the client).

A notification opts into this channel through a third content interface, alongside Mailable and Databasable:

type Broadcastable interface {
    ToBroadcast(ctx context.Context, r Recipient) (broadcast.Message, error)
}

ValkeyConfig

type ValkeyConfig struct {
    Addr     string
    Password string
    DB       int
}

ValkeyConfig deliberately carries no env struct tags of its own: notify/broadcast is a library, not a place to own an env namespace. The realtime chassis builds one from its own VALKEY_*-tagged configuration and passes it in (realtime.Config.BroadcastValkey()); a non-chassis publisher does the same from whatever config it already has. See Realtime for the exact mapping.

Config

type Config struct {
    ChannelPrefix string `env:"CHANNEL_PREFIX" envDefault:"rt"`
}

Unlike ValkeyConfig, Config does carry its own env tag (CHANNEL_PREFIX). The kit embeds it under its own REALTIME_ prefix, so in a generated project it surfaces as REALTIME_CHANNEL_PREFIX; see Realtime and the configuration reference for the full variable list. ChannelPrefix namespaces every Valkey channel this package touches, so multiple projects (or multiple environments) can share one Valkey instance without colliding.

Publishing

pub, err := broadcast.NewPublisher(ctx, valkeyCfg, cfg)
pub := broadcast.MustNewPublisher(ctx, valkeyCfg, cfg) // panics on error; the usual main() constructor

err := pub.Publish(ctx, channel, msg)
err = pub.Close()

Publisher is a publish-only centrifuge.Node sharing the project's Valkey broker; it never accepts a connection, so a process that only publishes (a worker, a one-shot script) depends on nothing but a network client to Valkey. Publish is fire-and-forget: a failed publish is returned to the caller, not retried and not queued, which is why the database channel is the durability story, not this one.

notify.NewBroadcastChannel(pub, cfg.ChannelPrefix)

wraps a Publisher into the notify.Channel the Hub dispatches Broadcastable content to.

Channel naming: UserChannel and TopicChannel

func UserChannel(prefix, subject string) string  // a recipient's own channel
func TopicChannel(prefix, topic string) string    // a shared, opt-in topic channel

Every connection the realtime gateway accepts is server-side subscribed to its own UserChannel, which is how a notification addressed to a Recipient.ID reaches exactly that user's live connection with no client-side subscribe call. TopicChannel names the shared channels used for broadcast beyond single-recipient notifications (announcements, admin alerts); see Realtime: shared topic channels for the TopicAuth/Allow/AllowPrefix authorization these require, which relies on auth/rbac's Identity.

NewNode

func NewNode(cfg ValkeyConfig, centrifugeCfg centrifuge.Config) (*centrifuge.Node, error)

NewNode is the shared constructor behind both roles: a publish-only node (what NewPublisher wraps) and the full connection-accepting node the realtime gateway runs. It wires the Valkey broker centrifuge needs to fan a publish out across every gateway instance, regardless of which process published it.

Memory

mem := broadcast.NewMemory()
pub := mem.All() // []broadcast.Published, everything captured so far

Memory is a test double: it captures every publish instead of touching Valkey, so a notification's ToBroadcast (or a channel wired with notify.NewBroadcastChannel(mem, prefix)) can be asserted on directly, the same way mail.NewMemory() and notifydatabase.NewMemoryStore() stand in for their real drivers.

With the kit

The gateway that turns a Publish call into a live client push, its scaling model, and the client-side subscription story are all on the Realtime page. The authorization model for shared topic channels builds on auth/rbac's Identity. Wiring the broadcast channel into a notification's Via is covered by add notification.

  • Overview: the Hub, Notification, and the mail channel.
  • Database: the durable channel this one complements, never replaces.
  • Realtime: the gateway, its scaling model, and the client.
Copyright © 2026