go-notificationgo-notification
Features

Broadcast (SendMulti)

Fan a single notification out to many recipients efficiently.

Sometimes you need to send the same notification to many users — a weekly digest, an incident announcement, a new-feature rollout. That's SendMulti.

Basic usage

main.go
users := []notification.Notifiable{u1, u2, u3, ...}

notifier.SendMulti(ctx, users, WeeklyDigest{Week: "2026-W16"})

Behavior:

  • Each recipient's Via() is resolved independently (they can differ — one user wants mail + database, another wants only database).
  • Sends are enqueued on the worker pool (or executed synchronously in sync mode).
  • Retry, rate limiting, and error handling apply per-send just like Send().

Chunking large sends

For tens-of-thousands of recipients, don't build a single giant slice — that uses a lot of memory and pressures the queue. Process in chunks:

main.go
const chunkSize = 500

for i := 0; i < len(allUsers); i += chunkSize {
    end := i + chunkSize
    if end > len(allUsers) { end = len(allUsers) }

    notifier.SendMulti(ctx, allUsers[i:end], WeeklyDigest{})
}

How SendMulti dispatches

SendMulti is a thin loop: it calls Send once per notifiable and joins any errors. It does not coalesce recipients into provider batch APIs — each recipient is dispatched independently (on the worker pool in async mode). One recipient failing does not stop the others.

The one place batching happens is within a single message: FCM multicasts when a push.Message carries multiple Tokens (set via SetTokens(...) in ToPush). That's per-message, not across SendMulti recipients.

main.go
err := notifier.SendMulti(ctx, users, WeeklyDigest{}) // errors.Join of per-recipient failures

When not to use SendMulti

  • Rate-limited channels (SMS, WhatsApp) — you'll hit provider caps fast. Use a slower pipeline, not SendMulti.
  • Highly personalized content — if every recipient gets meaningfully different data, the batch optimization disappears and you're just using Send in a loop with extra abstraction.

Per-recipient error handling

Errors from individual recipients fire the usual OnError callback, which receives the failing notifiable and channel:

main.go
OnError: func(ctx context.Context, n notification.Notifiable, channel string, err error) {
    slog.Error("send failed",
        "notifiable", n.GetID(),
        "channel",    channel,
        "err",        err,
    )
}

One recipient failing does not abort the rest.

Ordering

SendMulti does not guarantee ordering. If you need ordered delivery (rare for notifications), send one-by-one synchronously. For everything else, assume sends complete in worker-pool order.