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 Bundle Laravel Package

ruwork/reminder-bundle

Ruwork ReminderBundle is a Symfony bundle for scheduling and processing reminders. Create reminders tied to Doctrine entities or dates, persist them with Doctrine, and run a console command to dispatch due reminders so your application can send notifications or trigger custom actions. Delivery chann...

View on GitHub
Deep Wiki
Context7

Getting Started

Read the source before the README. This is an archived, zero-star, zero-dependent Symfony bundle. There is no community knowledge base, no Stack Overflow corpus, and no guarantee the docs match the code. Budget an hour to read these files in order:

  1. composer.json — PHP and Symfony version constraints. This tells you immediately whether it will even install on a current stack.
  2. src/DependencyInjection/*Extension.php and Resources/config/services.* — the actual public surface. Config keys live here, not necessarily in the docs.
  3. src/Entity/ (or Model/) — the persisted reminder shape: due date, status, recipient, payload, recurrence.
  4. src/Command/ — the console command that actually fires reminders. This is the heart of the bundle.
  5. README.md last, as a cross-check.

Mental model for a Laravel developer. A Symfony bundle is roughly a Laravel package plus its service provider, but registration is split across config/bundles.php and config/packages/*.yaml instead of config/app.php and a provider's register()/boot(). The bundle's console command is the equivalent of an artisan command you'd schedule in app/Console/Kernel.php. There is no Reminder::dispatch() facade here — the model is "persist a row, poll it on a schedule."

First use case. Get one reminder end-to-end before touching configuration:

  • Install from VCS ("repositories": [{"type": "vcs", "url": "..."}]) since the package is abandoned on Packagist and may be uninstallable by name on modern constraints. If it won't resolve, fork it and bump constraints — that is the expected path, not a failure.
  • Register the bundle, run bin/console list and confirm the reminder command appears.
  • Create a reminder through the bundle's own API (repository save() or a factory), not by inserting rows by hand.
  • Run the command with -vvv and confirm it picks up and processes the row.
  • Only then wire it to cron or a scheduler.

If steps 1–2 fail on your PHP/Symfony version, stop. Forking an unmaintained bundle to satisfy a modern framework is a maintenance commitment, and you should compare that cost against writing ~150 lines of Doctrine entity plus a command yourself.

Implementation Patterns

Treat the bundle as a scheduling primitive, not a notification system. The durable pattern is: your application writes reminder rows, the bundle's command polls and marks them done, and your code decides what "done" means (email, webhook, queue push). Keep the transport outside the bundle so you can swap it and so failures don't corrupt reminder state.

Wrap it in an application-level service. Never let controllers or handlers talk to the bundle's repository or entity directly. Expose something like ReminderScheduler::schedule(string $key, \DateTimeImmutable $at, array $payload) from your own namespace, and have it delegate. When the bundle is eventually replaced — and on an archived package, plan for it — the blast radius is one class instead of every call site. A stable $key per logical reminder also gives you natural idempotency: upsert on key rather than blindly inserting.

One-shot and recurring need different code paths. If the bundle supports recurrence, verify how it computes the next occurrence. The safe pattern is to have the command close the current row and insert the next one with an explicit future timestamp — not to mutate dueAt in place. In-place mutation makes history unqueryable and makes it impossible to tell a fired reminder from a rescheduled one during debugging.

Wire it as a short-interval cron, not a long sleeper. * * * * * bin/console ruwork:reminder:process --env=prod or a systemd timer. For anything with sub-minute precision, run the worker more often and let it select rows where dueAt <= now, rather than trying to nail the exact second.

Testing. Point the bundle at SQLite in-memory and freeze the clock. If the bundle calls new \DateTime() internally with no injection point, that's a signal about code quality — wrap the command in a test that seeds a row with a past dueAt, invokes the command via CommandTester, and asserts your notifier double was called. Assert on your notifier, never on the bundle's internals; internal assertions will break the moment you fork.

Gotchas and Tips

Archived means no security fixes. The bundle handles scheduled outbound actions, often with recipient data. If a CVE lands in a transitive dependency and this package pins it low, you own the fork. Check whether the dependency constraints are ^-style (survivable) or exact pins (a treadmill). Pin the commit SHA in composer.json, not a branch name.

Overlapping cron runs are the most likely production bug. A slow notification path plus a one-minute schedule means two workers can claim the same row. Mitigate outside the bundle: a Symfony Lock (flock or Redis) around the command, or a status column with an atomic UPDATE ... WHERE status = 'pending' claim. Verify whether the bundle already does a conditional update before adding a lock — but do not assume it does.

Timezones and DST. Reminders stored as naive datetimes fire an hour off twice a year, or twice at all during a fall-back repeat. Store UTC, resolve display timezone only at send time, and test a reminder set for 02:30 on a DST transition date. This is where most reminder systems quietly break.

Idempotency on retry. If your notification throws, does the row stay pending (retry forever, possibly spamming) or get marked sent (silently lost)? Decide explicitly. Prefer attempts + lastError columns and a terminal failed state over infinite retry.

Debugging workflow. bin/console debug:container | grep -i reminder to see what was actually registered; -vvv on the command for per-row output; the Symfony profiler if you're running it inside a web request (unusual, but some bundles expose a controller). When nothing fires, check in this order: command scheduled at all → command registered → rows match the query's status filter → dueAt is genuinely in the past in UTC.

Extension points to look for. Symfony bundles of this shape usually expose an event dispatched per reminder — that's the cleanest integration point, letting you listen and do your own sending without forking. If instead the only seam is a hardcoded mailer call inside the command, the practical extension point is the fork. Also check for a NotifierInterface-style service you can alias in services.yaml; that's a far cheaper override than editing vendor code.

Config quirks. Look for the bundle's configuration tree in the Extension class before writing YAML — archived bundles frequently have config nodes that appear in the README but were removed from the code, or vice versa. Symfony silently ignores unknown keys under some setups, so a typo'd config key produces a default-behaving system with no error. Validate by dumping the resolved parameters.

Do not attempt this in Laravel. Zero dependents and a Symfony bundle structure mean there's no Laravel bridge. The pattern — persisted due-dated rows plus a scheduled command plus idempotent claiming — ports cleanly to Laravel's scheduler and Eloquent in an afternoon. For a zero-star archived package, reimplementing the concept is usually the lower-risk choice than adopting the code.

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.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
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