Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filagora Notifications Laravel Package

filagora/filagora-notifications

Adds a ready-to-use notification layer to Filagora, with configurable channels, database and mail delivery, and clean integration into Laravel's notification system so marketplace users stay informed about activity.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Bottom line: v0.1.0 is a coherent, well-scoped preference engine with real production lineage (it is an extraction of a live site's behaviour, and the differences are documented). It is integrable within a sprint for a Laravel team, but it is a 0.x release with deliberate behavioural tightenings, an incomplete compliance story (no audit, no retention), and one gap that must be closed by the host (entitlement-based email suppression). Treat it as adoptable behind flags, not as drop-in parity with the site it came from.

Architecture fit

  • Idiomatic Laravel: service provider + config publishing, migration, Eloquent model, backed enums, contracts for replaceable behaviour (EmailSuppression), events (PreferenceChanged), a Filament relation manager, and a privacy contributor. Low conceptual overhead for a Laravel team.
  • The category registry (NotificationCategories, first registration wins) is the right call over a fixed enum, but it converts a compile-time concept into a runtime, boot-order-dependent one. Category ids are effectively a public contract because they are persisted per row.
  • Preference resolution is layered and predictable: stored value → per-person resolver → category default (on). This is the correct precedence order and is easy to explain to stakeholders.
  • Read caching ("one read per person, forgotten at every queue job") is a pragmatic N+1 killer, but it is lifecycle-scoped rather than model-scoped. It assumes a request/job boundary — long-lived workers (Octane, Horizon, queue workers handling batches) need explicit verification.
  • Bridges to sibling packages are bound after all providers boot and only while the sibling's own default is still bound. That is a considerate design (host bindings win), but it is subtle ordering logic that is hard to reason about in a large app with many providers.
  • Unsubscribe endpoints sit outside the web group — no session, no CSRF — which is correct for RFC 8058 one-click and relies entirely on signature integrity. Sound, but it needs a security review sign-off rather than a rubber stamp.
  • The package is a member of a suite (filagora-*). Coupling is currently optional and toggleable, but roadmap and versioning will track the suite, not this package alone.

Integration feasibility

  • Install path is standard Composer + publish + migrate, with filagora:adopt and a schema snapshot indicating a supported path for adopting an existing user_notification_preferences table rather than recreating it. That materially lowers migration risk for sites coming from the source implementation.
  • The public API surface is small and legible: allows(), mode(), set(), setMode(), matrix(), isSuppressed(), canDeliver(), plus Unsubscribe::url/oneClickUrl/decorate/apply. A single developer can wire the read path in a day.
  • Two integration tasks are product decisions, not code tasks, and are the real schedule risk: (1) agreeing the category taxonomy and which channels each category supports, and (2) deciding the suppression policy the host must now own.
  • Bridges mean you can defer integrating push/forums/messages independently — good for incremental rollout.

Technical risk

  • Pre-1.0 stability. No backwards-compatibility guarantee. Category ids are stable ("the stored ids are unchanged"), but class names, config keys and method signatures are not committed. Budget for upgrade churn across at least one or two minor releases.
  • Behaviour change: InvalidPreference is thrown where the source site answered "on". Unknown category, unsupported channel, or mode/switch mismatch now raises. In a notification send path this converts a silent default into an exception — the blast radius depends on whether callers wrap it. This is the single highest-risk delta for a team porting from the source site.
  • Suppression is no longer fully provided. The source site's entitlement-revocation rule (inactive-subscriber command) is explicitly left to the host. If the host does nothing, previously suppressed recipients start receiving email. This is a compliance-adjacent regression if not explicitly addressed at cutover.
  • No audit trail. The source site recorded an audit entry per change; that is deferred to a future package. If preference changes are in scope for audit/regulatory review, this is a gap that must be accepted in writing.
  • No retention rule in the privacy contributor. Export and erasure are covered; retention is not.
  • Erasure semantics are deliberately asymmetric: a person's own choices are removed, but choices a staff member set for others are retained without the link to the erased staff member. Defensible (it preserves other data subjects' states) but surprising, and it needs a documented rationale for the privacy reviewers.
  • Fail-open defaults. Preferences default to on unless a category says otherwise, and private-message contactability defaults to on when allow_private_messages is missing. Sensible for engagement, risk-bearing for anything regulated or high-volume.
  • Gate dependency: the relation manager is gated on filagora.notifications.view, declared in filagora-entitlements when installed. Sites without that package must define the ability themselves or the UI is invisible/leaky.
  • Silent divergence risk between register_standard and stored data. Turning standard registration off, or re-registering a category id with different channel support, will make existing rows unreadable or cause InvalidPreference on a channel the category no longer supports.

Key questions for the maintainers

  1. What is the compatibility policy pre-1.0 — which parts of the surface (category ids, config keys, method names, table/column names) are frozen?
  2. Supported matrix: Laravel and PHP versions, database targets, and the Filament major required by the relation manager.
  3. How is cache invalidation guaranteed under Octane and long-running Horizon workers, given the cache is "forgotten at every queue job"?
  4. What is the recommended pattern for wrapping InvalidPreference at the send site — is there a safe allows()-style non-throwing variant, or must every caller try/catch?
  5. What are the defaults for unsubscribe.signed_ttl and the exact signature parameters — is a link reusable within its TTL, and is there replay protection on the one-click POST?
  6. Does filagora:adopt handle an existing table with differing indexes or extra columns, and is it reversible?
  7. Is there guidance for hosts that must reproduce entitlement-based suppression — where should a custom EmailSuppression be bound relative to the bridge bindings?
  8. What is the intended retention story, and when is the audit package expected?

Integration Approach

Stack fit

  • Laravel (service provider, config, migrations, events, contracts), Filament for the admin surface, optionally filagora-entitlements for gate declaration, and optional sibling packages (filagora-push, filagora-forums, filagora-messages) via bridges.
  • If the host is not on Filament, the package still works; simply don't register the panel plugin. Confirm the relation manager is opt-in at the plugin level and not auto-discovered in a way that breaks non-Filament panels.
  • The package is additive: it owns one table and one model, and reads user attributes (receives_email, email_status, allow_private_messages) without writing them, except where the host chooses to sync.

Migration path

  1. Decide the taxonomy first. Enumerate categories and the channels each supports. Compare against whatever the current system stores (user columns, bitmasks, boolean flags) and against the seven standard ids. Freeze the ids — they are now persisted data.
  2. Choose adoption vs. fresh migration. If the table already exists (source-site descendants), use filagora:adopt and verify the snapshot matches reality, including indexes. Otherwise run the package migration.
  3. Backfill existing choices into user_notification_preferences with PreferenceSource set distinctly (e.g. imported/legacy vs. user-chosen) so the source is filterable in the relation manager and attributable later.
  4. Handle the split-brain field. The digest frequency now lives only in the preference row; the source site kept a user column. Keep the column temporarily and sync it from PreferenceChanged, or drop the column after backfill and cut readers over in one release.
  5. Bind the host's EmailSuppression if entitlement-based revocation is required. Do this before switching any send path to canDeliver().
  6. Wire unsubscribe before enabling routes. Mail headers (decorate()) can ship while routes.enabled is false — but note the routes reuse the paths the existing site already mailed (email-preferences/unsubscribe/{user}/{category}), so any link already in a customer's inbox will 404 until routes are on. Turn routes on in the same release you start emitting new links, and keep the paths unchanged.
  7. Enable bridges one at a time, verifying that a host-defined binding still wins and that the sibling's default is what you expect to be replaced.
  8. Register the privacy contributor and record the retention decision (or the explicit deferral) in the privacy register.

Compatibility

  • Behavioural, not just API: callers that relied on "unknown = on" must be changed or wrapped. Audit every allows()/mode() call site for a category that might be unregistered at runtime (for example, a notification dispatched by a package whose category registration was disabled).
  • Channel declarations are per category. Adding a channel to a category later is additive; removing one will strand existing rows and can trigger InvalidPreference.
  • The relation manager is read-only. Confirm this is acceptable for support workflows, or plan a small internal write path (or a later release) — otherwise support cannot fix a user's preferences for them.
  • register_standard off means you own all seven ids yourself; only do this if you are replacing, not supplementing, the standard taxonomy.

Sequencing (suggested releases)

  • R1 — Foundations (dark): install, config, migration/adopt, backfill, taxonomy registered, suppression contract bound. No behaviour change in send paths.
  • R2 — Read path: adopt allows()/canDeliver() in one low-risk notification stream, with InvalidPreference handled. Observe.
  • R3 — User-facing controls: enable unsubscribe routes and mail headers, plus the manage-preferences link. Verify existing mailed links resolve.
  • R4 — Admin + integrations: Filament relation manager, gate wiring, then bridges (push → forums → messages), each independently toggleable.
  • R5 — Privacy and cleanup: privacy contributor registered, retention decision recorded, legacy columns dropped, digest dual-write retired.

Operational Impact

Maintenance

  • Pre-1.0 means recurring upgrade work: expect breaking changes to config keys and class names before 1.0. Pin exact versions and review every release note rather than floating constraints.
  • Category ids and stored channel names are data contracts — changes require a data migration, not just a code change. Keep a short internal doc listing each id, its channels, and its default.
  • The bridges are the most fragile surface for maintenance: their binding depends on provider order and on the sibling's default still being bound. Any change to boot order or to a sibling package's binding is a potential silent regression.
  • Deferred items (audit package, retention) are future upgrade obligations, not permanent absences. Assume a future release adds an audit table and plan schema capacity.

Support

  • New support surface: "I'm not getting emails" now has four distinct causes — preference off, category default off, EmailSuppression (attribute or host policy), or an InvalidPreference exception silently killing the send. Support needs a diagnostic runbook and ideally a log line at the send site.
  • The read-only relation manager helps (support can see stored choices filtered by channel and source) but cannot fix them. Expect escalations to engineering for corrections.
  • Gate dependency (filagora.notifications.view) must be assigned to the support/admin role explicitly; default-deny will look like a broken UI.

Scaling

  • Row count is people × categories × channels, and only for stored (non-default) values if writes are sparse — but set()-on-every-save patterns can make it dense. At 7 categories × 3 channels and a large user base, plan indexing around (user, category, channel) and verify matrix() — it is the query that will be called on admin screens and preference pages.
  • matrix() on a per-user page is fine; matrix() in a loop over users is not. Any reporting or admin grid over all users' preferences needs a different query strategy.
  • The per-person read cache reduces per-request cost; the "forgotten at every queue job" behaviour is good for correctness but means a job fanning out to N users pays N reads unless batched. For high-volume digest sends, batch-load preferences rather than resolving per recipient.
  • Cache invalidation on set() is covered by the PreferenceChanged event only if the host uses it; in-process caches in singleton services must be cleared explicitly.

Failure modes

  • Uncaught InvalidPreference in a send path → notifications silently dropped, or worse, an exception that fails the whole job/batch. Highest-severity failure; mitigate with a wrapper and alerting on the exception class.
  • Suppression not bound by the host → email delivered to bounced, complained, or entitlement-revoked addresses. Reputational and compliance impact; mitigate with a pre-cutover checklist item and a test that asserts a known-suppressed user is blocked.
  • Unsubscribe routes left off while links are being mailed → 404 on a legally expected mechanism; also breaks the one-click contract advertised in List-Unsubscribe-Post. Enable atomically with link emission.
  • Route paths preserved from the old site → any pre-existing mailed links now land on the new package's routes once enabled. Verify signature/signed_ttl semantics match the old links, or those legacy links will fail validation and appear broken.
  • Bridge misbinding or a sibling package upgrading its default binding → preferences silently ignored (push sent to users who opted out, or DMs allowed against a user's choice). Silent, so detectable only by assertion tests.
  • Category registration order change (first wins) → a host registering an id before a package, or vice versa, flips behaviour between deploys with no diff in application code.
  • Erasure leaves retained rows with a null actor → expected by design, but must be reconciled with any downstream audit or reporting that assumed a non-null changer.
  • Schema drift on adopt → filagora:adopt succeeding against a table with different indexes can hide a performance cliff that only appears at scale.

Ramp-up

  • Engineering: roughly a sprint for the read path and one more for routes, admin UI and bridges. The API is small; the ordering rules and exception semantics are the learning curve.
  • Product/design: the taxonomy workshop is the gating item. Deciding which categories exist, which channels each supports, and which defaults are on vs. off is a cross-team decision that no amount of code review resolves.
  • Support: half a day for the diagnostic runbook and gate assignment.
  • Legal/privacy: a review pass for the suppression gap, the erased-actor retention rule, and the explicit no-retention-policy statement. Do this before R3, not after.
  • Overall: reasonable to adopt behind feature flags with named owners for the suppression policy and the category taxonomy; not reasonable to treat as transparent parity with the site it was extracted from.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core