Upgrading

What changed in each release of @byo-oss/notifications, from the better-inbox rename through the latest release.

Each section covers one change. Read the sections between your current version and the latest. The default docs describe the latest published release. Sections marked next describe main, which isn’t published yet.

From better-inbox (0.7)

The package was renamed from better-inbox to @byo-oss/notifications in 0.7. The API is the same. Change your imports:

lib/auth.ts
// before
import { inbox } from "better-inbox";

// after
import { inbox } from "@byo-oss/notifications";

0.6

0.6 adds a seenAt column to notification and two tables, notificationDelivery and inboxPresence. Re-run migrate (or generate) after upgrading, even if you don’t use email.

If you hand useInbox your own client object rather than authClient, add markSeen to it:

ts
markSeen: ({ ids }) => authClient.inbox.markSeen({ ids })

InboxFetchClient now requires it. Without it nothing is marked seen, and every unread email goes out.

0.8

  • Every reference now declares onDelete: "cascade": notification.userId, notificationDelivery.notificationId, notificationDelivery.userId and inboxPresence.userId. better-auth’s own migrate already cascaded. If you generate migrations with drizzle or prisma, regenerate and apply them so your foreign keys match.
  • The plugin now deletes notifications when an organization is deleted or a member is removed or leaves. See Notify an organization. If you wired your own cleanup for this, you can remove it.
  • The panel’s tabs render “Unread” and “All” as text, instead of unread and all capitalized by CSS. Tests that match the tab text exactly need the capitalized form.
  • Deleting a user now also removes their rows on adapters without foreign keys (memory, MongoDB).
  • New, additive: exportNotificationsData, PermanentDeliveryError, labels (including badge), organizationCleanup, and @byo-oss/notifications/testing, which needs vitest (an optional peer).

0.8.1

  • The package ships an agent skill, byo-notifications, in skills/. Install it with npx skills add byo-oss/notifications, or run npx skills sync to pick it up from node_modules. See Set up with a coding agent. Nothing else changed.

0.9

No schema change and no new options. Behaviour changes:

  • Export cursor. exportNotificationsData now pages by createdAt and resumes after the last row it returned, instead of by offset. A row deleted mid-export can no longer make the next page skip one, so you don’t need to stop writes first. Rows inserted mid-export are included if their createdAt is at or after the page you’re on; a row backdated behind it (an import, say) is not. A cursor from an earlier release (a bare number) now throws, because it can’t be resumed safely. Start the export again with no cursor, and keep one copy of each id, since rows exported before the restart come back.
  • Inside a better-auth transaction. notify, deleteOrganizationData, deleteMemberData and exportNotificationsData now run in the caller’s transaction (runWithTransaction), so their writes commit and roll back with it. Before, they wrote outside it, and on a one-connection database (PGlite) they waited on it forever. notify never sends inside a transaction: emails due right away wait for the next processDeliveries run. If you deferred notify until after the transaction yourself, you can call it directly now.
  • wrapsEmail. A missing or malformed recipient now fails at once with PermanentDeliveryError instead of retrying until maxAttempts. Bounces and suppressions still arrive later as SES events, not as send errors.

0.10

No schema change. To upgrade:

  • Install @better-auth/core. The better-auth peer is now ^1.7.7 (it was ^1.6.23), and @better-auth/core ^1.7.7 is a new required peer, used for the after-commit hooks behind events. Install it at the same version as better-auth, and add it to any package that imports the auth client or server. @byo-oss/core is a dependency and installs itself.
  • Don’t forward notifications.* into notify. If an onEvent handler turns events into notifications, skip the plugin’s own types first. notify rejects them (RESERVED_NOTIFICATION_TYPE), and a routes key with that prefix throws when the plugin is created. Events are in Events.
  • Check batchSize. inbox() throws unless batchSize is an integer from 1 to 1000, the range processDeliveries already accepts for limit.

Breaking:

  • Reserved types. notify rejects types that start with notifications., the prefix for the plugin’s own events.
  • Stale sends with no attempts left. A sweep now fails them one row at a time, at most limit rows per sweep, instead of all at once. When it stops at that cap after failing rows there, processDeliveries returns hasMore: true, so hasMore can be true with nothing due to send. Loop while hasMore is true. Exhausted rows past the cap stay sending until a later sweep fails them; they’re never retried past maxAttempts. Rows the sweep can’t update (row-level security, say) still count against limit, so once limit of them (up to 180) come first in scan order, later exhausted rows wait.

New:

  • onEvent, with notifications.notification.created and notifications.delivery.failed, delivered after commit. See Events. Of the calls that emit events, only notify joins a caller’s transaction. Call processDeliveries (or sweepAll) outside one: its writes commit as they happen, and its delivery.failed events fire at once.
  • byo hooks for @byo-oss/core (sweepAll, deleteOrganizationData, deleteMemberData). The cleanups are left out under organizationCleanup: false.
  • NOTIFICATIONS_EVENT_TYPES and the event types (NotificationsEvent, NotificationCreatedEvent, DeliveryFailedEvent), exported for routing on an event bus.

0.11

No schema change and no new inbox() options. To upgrade:

  • New: mailerChannel. mailerChannel({ mailer, replyTo?, render? }), from @byo-oss/notifications/mailer, sends email through any Mailer from @byo-oss/mailer. The mailer owns the sender, so the channel has no from. Install @byo-oss/mailer (0.1.1 or later) in your app, since your code imports its adapter, along with the SDK your mailer needs, such as @wraps.dev/email for wrapsMailer.
  • wrapsEmail is deprecated. It still works and is unchanged, but it will be removed in a later breaking release. Move to mailerChannel with wrapsMailer. from and render map across as below; client maps only if it returns { messageId } (see the differences):
lib/auth.ts
import { WrapsEmail } from "@wraps.dev/email";
import { mailerChannel } from "@byo-oss/notifications/mailer";
import { wrapsMailer } from "@byo-oss/mailer/wraps";

// before
channels: { email: wrapsEmail({ from: "Acme <notify@acme.com>", client: new WrapsEmail({ region }), render }) },

// after
channels: { email: mailerChannel({ mailer: wrapsMailer({ from: "Acme <notify@acme.com>", client: new WrapsEmail({ region }) }), render }) },
  • Check the differences. wrapsMailer differs from wrapsEmail in four ways. It fails permanently on every @wraps.dev/email ValidationError (a malformed from, replyTo or recipient, or an empty subject) and on SES MessageRejected and InvalidParameterValue, where wrapsEmail fails only a bad recipient at once and retries the rest, so fixing the config no longer rescues deliveries already queued. It throws a TypeError when it is created if from is empty, where wrapsEmail failed at the first send. Its client must resolve { messageId }, so a fake or wrapper that returns anything else, such as a WrapsEmailSender, needs changing. And it sends a non-ASCII display name in from RFC 2047 encoded, which needs @byo-oss/mailer 0.1.1 or later (0.1.0 sends it as given). See Wraps.
Edit this page on GitHub ↗@byo-oss/notifications 0.11.4