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

Kanpen Laravel Package

mydnic/kanpen

Kanpen is a Laravel package that adds a lightweight, customizable Kanban board to your application. Create boards and columns, manage cards with drag-and-drop, and organize workflows through a simple, ready-to-use interface that fits existing Laravel projects.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and publish its assets:

composer require mydnic/kanpen
php artisan vendor:publish --provider="Mydnic\Kanpen\KanpenServiceProvider"
php artisan migrate

If the publish command offers tags, publish selectively so you can review each piece before it lands in your app: kanpen-config, kanpen-migrations, kanpen-views (email templates), and whatever tag ships the front-end routes/controllers. Publish the views even if you don't plan to edit them — you will want to change the footer and the unsubscribe copy.

Where to look first, in this order:

  1. config/kanpen.php (or similarly named) — this is the contract for the whole package. Look for: the subscriber model, the mailer/queue connection it sends on, route prefix and middleware, tracking toggles, and the "from" address.
  2. routes/ inside the package — subscribe, confirm, unsubscribe, open-tracking pixel, and click-redirect endpoints. Knowing these URLs is 80% of integrating it.
  3. src/Models — Subscriber, Campaign, List/Segment, EmailEvent (or equivalent). These are ordinary Eloquent models; you can query and build against them directly.
  4. database/migrations — check table names and whether anything touches your users table before you run migrate in production.
  5. src/Jobs and src/Listeners — the sending pipeline and the events you can hook.

First use case to ship: a single opt-in newsletter signup on an existing marketing page, wired to a campaign you send manually to yourself. Concretely:

// routes/web.php
Route::post('/newsletter', [NewsletterController::class, 'store'])->name('newsletter.store');
public function store(Request $request)
{
    $request->validate(['email' => ['required', 'email']]);

    Kanpen::subscribe($request->email); // or Subscriber::create([...]) depending on API

    return back()->with('status', 'Check your inbox to confirm.');
}

Then create a campaign, add one or two subscribers, and send it to yourself with MAIL_MAILER=log first. That single end-to-end pass exercises the queue, the template rendering, the tracking pixel, and the unsubscribe link — the four things that break in production.

Implementation Patterns

Model-backed subscribers, not a black box. Treat the package's subscriber table as a real table in your schema. You'll want to join it against users for reporting, add a source or utm_source column to know where signups came from, and soft-delete rather than hard-delete for compliance history. Wrap the package's subscribe call in a small application-level service (App\Services\Newsletter) so you have one place to add source, fire your own analytics event, and later swap providers.

Double opt-in as an explicit state machine. Most of these packages model subscribers as pending → confirmed → unsubscribed. Don't fight it. Drive it from your own controller so you control the redirect, the flash message, and the confirmation email's branding. If the package ships its own endpoints, either use them (and style the published views) or disable the routes in config and implement your own — but don't do both, or you'll have two confirmation flows.

Campaign sending is a queued job — lean on that. Structure sends as: create campaign row → attach recipients (a query or a pivot) → dispatch the package's send job(s) → let workers drain the queue. For anything over a few hundred recipients, chunk the audience yourself and dispatch per-chunk jobs so a single failure doesn't lose the whole campaign, and so you can throttle to stay inside SES/Postmark/Resend rate limits.

Subscriber::query()
    ->where('status', 'confirmed')
    ->whereNull('unsubscribed_at')
    ->chunkById(500, fn ($subscribers) => SendCampaignChunk::dispatch($campaign, $subscribers->pluck('id')));

Email templates as Blade + a layout. Keep a single emails/layout.blade.php that does @yield('content'), and have every campaign template extend it. This is the cheapest way to keep branding, the unsubscribe footer, and List-Unsubscribe headers consistent. If the package stores campaign HTML in the DB, render it with a strict sanitizer or restrict editing to admins — stored email HTML is a phishing vector if it's user-editable.

Events and listeners for the interesting bits. Subscribe, confirm, unsubscribe, and bounce/complaint events are the seams you build on. A typical set:

Event::listen(SubscriberConfirmed::class, AddToCrmContact::class);
Event::listen(SubscriberUnsubscribed::class, SyncSuppressionToCrm::class);
Event::listen(CampaignSent::class, LogCampaignCost::class);

Register these in EventServiceProvider — packages usually make their events opt-in, so you get the hooks without inheriting side effects.

Tracking: read it, don't trust it. Open tracking is a 1×1 pixel; click tracking is a redirect through your app. Both are read-only value-adds. Wire them into your own reporting table only if you need to correlate with product usage; otherwise just surface the package's numbers in an admin screen. Clicks are far more reliable than opens — segment on clicks.

Scheduling. Scheduled campaigns belong in the scheduler, not in a cron script you maintain. The package likely exposes a campaigns:send-scheduled (name may differ) command; put it in routes/console.php under ->everyMinute()->withoutOverlapping() and make the command idempotent (mark the campaign sending with a started_at before dispatching recipients) so a double run doesn't double-send.

Multi-tenant / multi-list. If you serve more than one brand or audience, treat "list" as a first-class scope and add a global scope or explicit where('list_id', ...) in every query you write against the package's models. Retro-fitting tenancy onto a package that assumes a single newsletter is painful.

Gotchas and Tips

A queue worker is not optional. Package sends are almost always ShouldQueue jobs. QUEUE_CONNECTION=sync will work in local dev and then time out spectacularly on a 5,000-recipient campaign. Set the connection explicitly in config, and verify the worker is running (php artisan queue:monitor) before every send.

Open rates are fiction now. Apple Mail Privacy Protection pre-fetches tracking pixels. Expect 30–50% of your "opens" to be machine-generated, and never make product decisions on open rate alone. If the package reports opens prominently, add a note in your admin UI or compute a "clicks / delivered" metric instead.

Unsubscribe must be one-click and unauthenticated. Both Gmail/Yahoo bulk-sender rules and basic decency require it. Verify the package's unsubscribe URL is signed or tokenized (not [email protected], which lets anyone unsubscribe anyone), that it works without a session, and that it also emits a List-Unsubscribe and List-Unsubscribe-Post header. If the headers aren't there, add them in your Mailable's headers().

Migration collisions. Before php artisan migrate, read the package migrations. Any that add columns to users or assume a name column will conflict with apps that use full_name or a UUID primary key. Copy the migration into database/migrations and edit it rather than publishing blind.

Test with fakes, not real sends. Mail::fake() and Queue::fake() around your controller and job tests, then assert the job dispatched with the right recipient set. For template rendering, render the Mailable to HTML in a test and assert the unsubscribe link and physical address are present — broken unsubscribe links are the single most common newsletter bug.

Timezones and send windows. Scheduled sends run in config('app.timezone'), but recipients live everywhere. If you promise "9am local," you need to bucket recipients by timezone and dispatch per-bucket jobs. Check whether the package stores a timezone per subscriber; if not, add the column.

Config publishing is one-way. vendor:publish --force will overwrite your customized config and views. Keep your edits in config/kanpen.php under version control and prefer extending package views (@extends('kanpen::layout')) over editing in place, so a composer update doesn't wipe your branding.

Rate limits and reputation. Sending 10k emails in a minute through a shared IP is how domains get blocklisted. Throttle the job (->onConnection('redis')->delay(...) or a RateLimited middleware), honor provider per-second limits, and make sure bounce/complaint webhooks are wired to a suppression list. The package can track sends; only your app can suppress an address that hard-bounced twice.

Extension points worth knowing. Most of these packages let you swap the subscriber model, the Mailable, and the routes via config. Prefer that over forking. If you need behavior the package doesn't expose, wrap the model in your own App\Models\Subscriber extends \Mydnic\Kanpen\Models\Subscriber and point the config at it — you keep the package's internals working while adding scopes, accessors, and relations.

Small package, small surface — expect to read source. At 26 stars this is a single-maintainer package, which means the README won't cover edge cases and issues may sit. Budget an afternoon to read src/ end to end before building anything load-bearing on it, pin the version in composer.json, and keep your own thin abstraction layer (App\Services\Newsletter) so a future swap to Laravel's native Markdown mailables or a paid ESP is a one-file change rather than a refactor.

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.
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
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource