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

Reminder Laravel Package

ruwork/reminder

ruwork/reminder is a PHP package for scheduling and managing reminders in your application. Define reminder rules, persist pending notifications, and trigger them when due through a clean, framework-friendly API—useful for automating time-based follow-ups and user notifications.

View on GitHub
Deep Wiki
Context7

Getting Started

Before writing any code, treat this as a source-first package. With zero stars and zero dependents there is no community surface, no Stack Overflow corpus, and no blog coverage — the src/ directory and composer.json are the documentation.

  1. Audit compatibility first. Open composer.json on the repo and check the require block. Ruwork packages are historically Symfony-oriented (bundles, Doctrine integrations, PSR-18 clients), so confirm whether you are pulling in symfony/framework-bundle, Doctrine ORM, Messenger, or Mailer as transitive dependencies. If your project is Laravel, decide up front whether you are willing to pull a Symfony bundle in alongside it (you can — bundles are just DI containers — but auto-discovery and Kernel::registerBundles() do not exist in Laravel, so you would instantiate services manually or via a small service provider).
  2. Install into a throwaway app. Do not install straight into production. Use a scratch Symfony skeleton or a fresh Laravel app and run composer require ruwork/reminder. If it resolves cleanly, you immediately learn the supported PHP and framework versions from the lockfile diff.
  3. Read the tests. If tests/ exists it is the only executable spec you have. Read them before the README; the tests encode the real contract (what a "reminder" is, how it is persisted, how it is emitted).
  4. Locate the three moving parts. Any reminder package has (a) a definition of a reminder — an entity, DTO, or attribute/annotation marking a remindAt field; (b) a storage/query layer that finds due reminders; (c) a transport that fires them (Messenger message, mailer, event dispatcher, webhook). Find each one before designing anything.
  5. First use case — a single due reminder. Build the smallest possible loop: create one reminder row with a timestamp a few seconds in the future, run the package's console command (or whatever the dispatcher entry point is — look in Command/ or Console/), and confirm exactly one side effect occurs. That single end-to-end run exposes the timezone assumption, the polling interval, and the dispatch mechanism in one shot.

If the README is empty and the tests are thin, budget half a day to reverse-engineer the contract. With a package this small, that is realistic; it is also the point at which many teams decide to hand-roll the equivalent in ~150 lines instead.

Implementation Patterns

Cron plus a locking guard. The canonical deployment for any reminder package is a scheduler entry running every minute. Pair it with a distributed lock (flock, database advisory lock, Redis SET NX, or Symfony's Lock component) so overlapping cron runs do not double-fire reminders. This is the single most important pattern for this category of package, because reminder dispatch is rarely idempotent on its own.

Status state machine, not deletion. Model reminders with explicit states — pending → claimed → sent (plus failed, cancelled) — rather than deleting on dispatch. Claim-then-send (mark claimed with a worker ID and a claimed_at before actually sending) gives you crash safety: a reminder stuck in claimed past a timeout can be reclaimed and retried. If the package ships its own entity, check whether it already models status; if it only has remind_at and sent_at, you will need to extend or wrap it.

cron: * * * * *  ->  dispatcher --limit=100
                     ├── SELECT ... WHERE remind_at <= now() AND status = 'pending' FOR UPDATE SKIP LOCKED
                     ├── UPDATE status = 'claimed', claimed_at = now()
                     ├── emit side effects (message / mail / event)
                     └── UPDATE status = 'sent' (or 'failed' + scheduled retry)

Batch with a hard limit. Always cap how many reminders one run processes. A backlog (cron was down over the weekend) must not produce a thundering herd of emails. If the package exposes a --limit/--batch option, use it; if not, wrap the call or subclass the command.

Idempotency keys at the consumer. Even with locking, at-least-once delivery is the realistic guarantee. Give every emitted reminder a stable identifier (reminder ID + recipient ID) and make the downstream consumer deduplicate — a unique index in a notifications table, or an idempotency key passed to your mail/webhook provider. Assume the package will occasionally fire twice and design so that firing twice is harmless.

Separate "find due" from "send". The healthy integration splits the dispatcher into two phases: a fast query that enqueues (Messenger message, Laravel job, queue row) and a slow worker that sends. This keeps the cron tick short, isolates retries from discovery, and lets you scale sending independently. If the package is monolithic (query + send in one command), wrap it: run the discovery query yourself and hand results to your queue.

Per-tenant / per-channel partitioning. In multi-tenant apps, run the dispatcher once per tenant with a tenant ID argument, or include tenant ID in the claim query. Shared dispatch across tenants is fine only if your downstream consumers are already tenant-aware; otherwise you will leak reminders across boundaries during a retry.

Timezone discipline. Store remind_at in UTC, always. Resolve user-facing times at the boundary: what the user meant ("9am local") should be converted to a UTC instant at creation time, not at dispatch time — otherwise a DST transition silently shifts everyone's reminders by an hour.

Testing. Use a fake clock. Symfony's ClockMock (or Laravel's Carbon::setTestNow) lets you assert that "2 seconds ago" is due and "1 second from now" is not, without sleeping. Wrap the dispatcher in a test seam so tests can invoke one tick synchronously.

Gotchas and Tips

  • Zero stars, zero dependents is a maintenance signal, not just a vanity metric. You are likely the first or second team to run this in anger. Before committing, check the last commit date, whether CI is green, and whether open issues exist. If the repo has gone quiet, plan to fork it into a private vendor namespace so an upstream composer update cannot break you. Lock the exact version ("ruwork/reminder": "x.y.z", not ^x.y).
  • The "opportunity" score is misleading. It rewards the absence of prior art, and in this case the absence is because almost nobody uses the package — not because there is a gap waiting to be filled. A 9.65 opportunity score with 0 dependents usually means "unclaimed territory," and territory is unclaimed for a reason. Do not read it as endorsement.
  • Confirm the framework. If it is a Symfony bundle, composer require will not auto-register anything in Laravel, and there is no ServiceProvider. You will construct the dispatcher manually with explicit dependencies — which is fine, but do not expect Laravel's facade/container magic to reach into it.
  • The cron interval defines your accuracy. A one-minute scheduler gives you ±60 seconds of drift per reminder, which is almost always acceptable but is not "exact." If the product promises precision, set user expectations or run a tighter loop with a lock.
  • Missed ticks are silent. Cron does not alert you when it does not run. Add a heartbeat: a last_run_at row that a monitor checks against now() - interval. Otherwise a broken cron means reminders quietly stop and you find out from a customer.
  • Watch the WHERE remind_at <= now() index. Without a composite index on (status, remind_at) the dispatcher degrades linearly as the table grows; on a table that only ever accumulates, this bites within months.
  • Debugging tip: dry-run before you fire. If the package lacks a --dry-run, add one in a subclass or a wrapper that logs what would be claimed. Running the live dispatcher for the first time against production data is how teams blast old reminders to thousands of users. Seed with remind_at set in the future, not in the past.
  • Retries are usually your problem, not the package's. If a send throws, the package may mark it sent, leave it pending to re-fire forever, or drop it depending on how the try/catch is written — read the send path carefully. Add a attempts counter and a dead-letter state, and treat "failed N times" as a business event someone should see.
  • Extension points are the escape hatch. Most Ruwork packages are built on Symfony DI and Doctrine, which means the intended extension mechanism is overriding a service in the container or subclassing the dispatcher/entity. Once you have read the service definitions, prefer subclassing over forking the whole repo — it keeps upstream diffs manageable if the package ever sees a release.
  • Final call: if after reading src/ you find the package is ~200 lines of query-plus-send with no locking, no status model, and no tests, consider implementing the pattern yourself. At this scale, vendor lock-in buys you little, and in-house code you fully understand will be cheaper to operate than an unmaintained dependency. Adopt the package only if it genuinely saves work — for example if it already implements the claim/retry state machine and Doctrine query correctly.
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.
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
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console