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

Sendportal Laravel Package

mettle/sendportal

SendPortal is a self-hosted email marketing platform built on Laravel. Manage subscriber lists, compose and send campaigns through your own SMTP or API providers, and track opens and clicks in a clean dashboard. Open source, with no per-subscriber fees.

View on GitHub
Deep Wiki
Context7

Getting Started

SendPortal is a self-hosted email marketing platform you install into a Laravel app rather than a drop-in CRUD package — it brings its own models, routes, views, jobs, and migrations under the Sendportal\Base namespace.

Minimal path:

  1. composer require mettle/sendportal in a fresh-ish Laravel app (check the package composer.json for the supported Laravel/PHP matrix — the 4.x line expects modern PHP 8.x, which matters if you're bolting this onto a legacy app).
  2. php artisan sp:install — publishes config/views/assets, runs migrations, and creates the initial admin user. On 4.0.0 this command silently skipped migrations; the fix is in 4.0.0.1, so pin ^4.0.0.1 or newer and verify tables exist afterwards.
  3. Log in at the published route prefix, then work left-to-right through the UI: Email Services (provider credentials) → Lists → Subscribers → Templates → Campaigns.

Where to look first:

  • config/sendportal.php — route prefix, middleware stack, model bindings, provider config. This is the file you'll touch most.
  • vendor/mettle/sendportal/src/Base/ — the Services/ directory is where the real logic lives (message dispatch, content parsing, segment resolution).
  • Publish the views (vendor:publish with the sendportal tags) before you start restyling; don't edit anything in vendor/.

First real use case: a one-off newsletter to an existing user table. Import subscribers into a list via the UI/CSV or a small artisan command, build a Blade-style template, create a campaign against that list, and send. That flow exercises every subsystem you'll later need to extend.

Implementation Patterns

Provider abstraction. Each sending provider is an EmailService record with a type and a JSON settings blob. Under the hood these resolve to message-service classes that implement a common contract, which is the natural extension point if you need a provider that isn't baked in — implement the contract, register the mapping, and it slots into the campaign sending flow.

Campaign lifecycle is queue-driven. Draft → queued → sending → sent. Campaigns fan out into per-recipient jobs, so a real queue connection and a running worker are non-negotiable for anything beyond a smoke test. Pair this with rate limiting (job middleware / Redis::throttle) if your provider caps throughput.

Scheduling. Scheduled campaigns rely on Laravel's scheduler, not a daemon inside the package. Ensure schedule:run is on cron (or schedule:work locally) or scheduled campaigns will sit in draft forever with no error.

Segments vs. lists vs. tags. Lists are static membership; tags are labels; segments are saved rule sets evaluated at send time. If you need dynamic audiences from your own domain tables, the segment rule engine is the thing to study and extend rather than duplicating subscriber rows.

Templates and merge tags. Templates are Blade-flavoured with per-recipient merge tags resolved at send time. Before authoring a large template library, read the content-parsing service in src/Base/Services/Content/ and confirm the exact tag syntax and escaping behaviour — the escaping rules differ from raw Blade and will bite you on names containing quotes or HTML.

Webhooks. Open/click/bounce tracking depends on provider webhooks pointing at the package routes. In practice: configure them per environment, keep the endpoint public, and treat signature verification failures as a first-class debugging concern — dropped webhooks mean silently wrong stats, not errors.

Extending without forking. Publish views, swap model bindings via config, hook package events (MessageSent, subscriber events) into your own listeners, and prefer decorating the service classes over editing vendor code. Multi-tenancy/workspaces is not built in — if you need per-account isolation, expect to fork or wrap it, and budget accordingly.

Gotchas and Tips

  • Upgrade to 4.0.0.1 before wiring CI. The duplicate logout route in 4.0.0 breaks php artisan route:cache, which will fail your deploy pipeline even though the app runs fine locally. The install fix is equally relevant to anyone provisioning fresh environments.
  • After any upgrade, run route:clear and then route:cache as an explicit check. Route-cache regressions are the classic "works in dev, dies in deploy" failure for this package.
  • Verify migrations actually ran after sp:install — don't trust the command's silence, especially if you're on a pinned older patch. php artisan migrate:status is your friend.
  • Config caching: config:cache after editing config/sendportal.php, then config:clear when debugging why a route prefix or middleware change "didn't take".
  • Migrate once, never edit. Package migrations live in vendor/; publish/run them, then make your own additive migrations for custom columns rather than patching the originals.
  • Failed sends surface in failed_jobs, not the UI. When a campaign reports "sending" indefinitely, check the queue worker first, the job payload second, and the provider credentials last.
  • Subscriber deletion semantics affect historical stats — decide early whether you soft-delete or purge, and check how cascades behave on messages/events before letting users loose on the delete button.
  • Local webhook testing needs a tunnel. Stats will look broken in dev simply because the provider can't reach localhost; don't chase a phantom bug.
  • Big-version jumps are structural. 3.x → 4.x reorganised the package significantly (namespace, install flow, routes). Pull the upgrade notes from the repo rather than inferring from the changelog, which only covers patch diffs.
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.
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
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect