madjeek-web/symfony-mailjet-bundle
A Symfony bundle that integrates the Mailjet email delivery service, letting you send transactional and marketing messages through Mailjet's API using Symfony's Mailer component with simple configuration and service wiring.
v1.0.0 is the first stable tag, so treat it as the point where the public API becomes something you can commit to. The practical first step is not to read the README end-to-end but to establish the support matrix before you install anything:
composer.json first — check the require block for the php constraint and the illuminate/* (or laravel/framework) range. A package that only supports ^10.0|^11.0 will silently constrain your upgrade path later, and this is the single most common reason a "small utility" becomes a blocker six months out.extra.laravel.providers / extra.laravel.aliases block means Laravel registers it for you. If it's absent, you register the provider in bootstrap/providers.php (Laravel 11+) or config/app.php (Laravel 10 and earlier). Getting this wrong yields a confusing "class not found" rather than a helpful error.composer require vendor/package:^1.0. Do not let Composer resolve ^0.x for you if you're migrating; the constraint should be deliberate.php artisan vendor:publish --provider="Vendor\Package\PackageServiceProvider" and read the resulting file. The config keys are the package's real API surface, more so than its classes.First use case: pick the narrowest thing the package does — one value object, one facade call, one command — and wire it in a single, non-critical path (a console command or a queued job) before putting it in an HTTP request cycle. That gives you a cheap loop for observing behaviour without exposing it to users.
If you were previously on a 0.x tag, read the repository's CHANGELOG/UPGRADE files directly. Terse release notes like "First stable release" almost never enumerate the renames and removals that happened during the pre-1.0 churn, and those are exactly what will break you.
Resolve through the container, never new. The whole benefit of a Laravel package is that its service provider has already bound the configured implementation. app(Client::class) or constructor injection respects the user's config; new Client() silently bypasses it and produces behaviour that looks like a config bug.
Wrap it in a thin adapter of your own. Even a 20-line interface plus one concrete implementation means a future major bump is a one-file change rather than a grep across the codebase. This is worth doing for anything that touches money, auth, or an external service.
Drive behaviour from config, not from call sites. Most packages expose either a manager with drivers or a config block of named clients. Prefer declaring two named configurations (services.foo.primary, services.foo.tenant) over branching in application code — it keeps environment-specific differences in config/ where config:cache can see them.
Test with the framework's fakes first. If the package makes HTTP calls, Http::fake() in the package's own test suite or yours will usually intercept it, no package-specific fake required. If it dispatches events or jobs, Event::fake() / Queue::fake() let you assert on intent rather than outcome. Only reach for the package's own testing helpers once you've confirmed the framework-level fake doesn't cover it.
Push long-running work into jobs. Anything that opens a socket, calls a third-party API, or does filesystem work should sit behind ShouldQueue so retries and backoff are handled by the queue rather than by you.
Scheduled tasks and commands are the lowest-risk integration point. A package that provides an artisan command or a schedule hook can be exercised in staging with zero blast radius, which makes it the right place to validate assumptions before broader adoption.
php artisan config:clear and php artisan package:discover (or optimize:clear) resolve the majority of "my config change did nothing" and "class not found" reports. In production, remember config:cache will freeze whatever the published config file said at build time — environment variables read at runtime from inside a package may be null under a cached config.vendor:publish without --force will not overwrite. If you upgrade and the package ships new config keys, your stale published file wins and the package falls back to defaults for keys it can't find, which can look like the feature simply doesn't work. Diff the package's config/ directory against yours after every major bump.0.x → 1.0.0 jump is the breaking change, even when the notes don't say so. The absence of a "Breaking Changes" heading in a terse release note is not evidence of none. Use composer why-not vendor/package ^1.0 to see what's holding you back, and composer update vendor/package --with-dependencies rather than a blanket composer update when you're testing.composer.lock in applications and upgrade this package deliberately. A first stable release is precisely the moment when other packages start widening their constraints to ^1.0, so a lockfile is what stops an unrelated composer update from moving it under you.php artisan tinker plus app(Thing::class) is the fastest way to confirm what the container actually resolved. If the package integrates with Laravel's manager/driver pattern, Thing::getDefaultDriver() tells you which config branch you're on — that one call short-circuits most "it's ignoring my config" investigations.boot(), Manager::extend() for custom drivers, and any Macroable traits. Registering a custom driver in AppServiceProvider is almost always preferable to patching vendor code, and it survives the next minor release.1.0.1 can quietly drop a PHP version or add a new illuminate/* constraint. Read the composer.json diff, not just the tag message, before letting CI pull it in.How can I help you explore Laravel packages today?