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

Helo Laravel Laravel Package

beyondcode/helo-laravel

Helo is a Laravel debugging assistant that captures dumps, queries, mails, and other debug output so you can inspect everything in a clean interface instead of cluttering your app or logs. Install via Composer and start debugging without leaving your workflow.

View on GitHub
Deep Wiki
Context7

Getting Started

What this package actually is: a Laravel mail transport. It registers a helo driver with Laravel's mail manager so every message sent through Mail:: is handed to the Helo desktop client instead of an SMTP server, a SaaS sink, or a real inbox. There is no application-code integration surface — no facade, no helper, no trait. You install it, select the driver, and the existing mail pipeline keeps working unchanged.

Five-minute setup. Install it as a dev dependency, publish the config, and point your personal .env at the new driver:

composer require --dev beyondcode/helo-laravel
php artisan vendor:publish --tag=helo-config   # verify the exact tag in the package README
# .env — this developer's machine only
MAIL_MAILER=helo
HELO_ENABLED=true

Start the Helo client, then prove the wiring without touching a controller:

php artisan tinker
>>> Mail::raw('hello', fn ($m) => $m->to('[email protected]')->subject('Helo smoke test'));

Message appears in the viewer → done. Message doesn't appear → exactly two suspects: the client isn't listening on the configured host/port, or config is cached (php artisan config:clear). Resist the urge to debug your Mailable before ruling out both.

Where to look first.

  1. config/helo.php — this file is the package. The endpoint host/port, the enabled flag, and the route prefix + middleware for the viewer all live here. Everything you'll want to tune is in this one file.
  2. config/mail.php — confirm nothing at the repo level hardcodes a helo mailer entry. MAIL_MAILER in .env should be the only place the driver name appears anywhere in the codebase.
  3. composer.json — verify it landed under require-dev, not require. This is the cheapest insurance against the worst failure mode (see Gotchas).

Your first real use case. Skip the synthetic Mail::raw as anything other than a smoke test. Pick the flow that actually costs money or trust when it's wrong — password reset, invite, receipt, digest — trigger it through the real code path, and check the three things a log line can never show you: does the HTML render, do the links point at the correct environment's host, and are the recipients what you expected. That check is the reason the package exists.

Implementation Patterns

The env-swap is the pattern. Resist everything else.

The healthy shape is one line in a developer's .env and zero code changes:

# .env.example — the shared default, stays boring
MAIL_MAILER=log

# .env — per-developer opt-in
MAIL_MAILER=helo

Because the driver plugs into the mail manager rather than into your code, it composes for free with everything that goes through it: Mailable classes, Markdown mail, Notifiable models using the mail channel (toMail() returns a Mailable, so it's captured), raw Mail::send, and mailables dispatched from queued jobs.

Build a preview command for your transactional catalogue.

The highest-leverage thing you can add on top of this is a command that fires every user-facing email on demand. It doesn't ship with the package, and it's maybe twenty lines:

// app/Console/Commands/PreviewMail.php
class PreviewMail extends Command
{
    protected $signature = 'app:preview-mail';

    public function handle()
    {
        $user  = User::factory()->make(['email' => '[email protected]']);
        $order = Order::factory()->make();

        Mail::to($user)->send(new WelcomeMail($user));
        Mail::to($user)->send(new PasswordResetMail($user, 'fake-token'));
        Mail::to($user)->send(new ReceiptMail($order));

        $this->info('Sent to Helo — open the viewer.');
    }
}

Now "verify the emails render" is one command plus a scroll through the inbox — a repeatable pre-release check instead of manual copy-paste from a staging SMTP account.

Keep the test suite out of it.

Mail::fake() short-circuits the mail manager before the transport is ever resolved, so Pest/PHPUnit assertions are unaffected and don't need any awareness of Helo. That's the right division of labour: tests assert that mail was sent to whom; Helo is for a human checking what it looks like. Don't try to make Helo part of CI — it depends on a running desktop client and cannot run headless.

Scope it with config, not with discipline.

Drive the enabled flag from the environment rather than trusting everyone's .env to be correct:

// config/helo.php
'enabled' => env('HELO_ENABLED', app()->environment('local')),

Better still, make a misfire loud instead of silent:

// AppServiceProvider::boot()
if (config('mail.default') === 'helo' && ! app()->environment(['local', 'testing'])) {
    throw new RuntimeException('Helo mailer active outside local — fix MAIL_MAILER.');
}

That single check converts your highest-severity risk from "hope nobody copies the wrong .env" into a boot-time exception.

Restrict the viewer route.

The packaged route exposes configurable middleware for a reason. On shared networks, coffee-shop Wi-Fi, or a remote box, bind it to web + auth behind a known admin gate, or restrict by IP. Treat captured payloads as production-grade sensitive: they routinely contain live password-reset tokens and real customer data.

Coexistence, not replacement.

The driver is selected per environment, so Mailpit/Mailtrap remain the correct answer for CI, containerized preview environments, and SSH workflows. Write that down next to the Helo note in onboarding, or you'll field "Helo doesn't work on the remote box" within a month.

Container caveat. If you develop through Sail or Docker, the client runs on your host, not in the container. The configured endpoint needs host.docker.internal (or the platform equivalent) rather than 127.0.0.1, or every send returns connection-refused.

Gotchas and Tips

The signature failure mode is silent mail swallowing. If the helo driver is somehow active in staging or production, outbound mail goes into a void — no bounce, no error, no log line. Customers just never receive their password reset. Three cheap layers of defence:

  • composer require --dev and deploy with --no-dev. With the package absent, an active helo mailer throws Unsupported mail transport [helo] — a loud, obvious failure at the first send. This is the single best reason to keep it out of require.
  • Never commit MAIL_MAILER=helo to .env.example, .env.ci, or any Docker/K8s manifest. Grep for it before rollout.
  • Add the boot-time guard above.

config:cache bites in both directions. Locally, a stale cached config keeps an old MAIL_MAILER after you edit .env — always php artisan config:clear after changing it. In the other direction, a cached config containing helo that reaches a production image produces a hard failure at the first mail send. Config caching is env-specific in normal operation, but it's the mechanism by which an accidental value travels between them.

Client not running means slow requests, not just missing mail. The transport attempts a connection per message. If the client is closed, you get connection-refused or timeouts on every send, which can make a registration flow feel broken for reasons unrelated to your code. Check the client before debugging the Mailable. Consider setting a low connection timeout in the transport config so a dead client fails fast.

Long-running queue workers cache both config and connections. Changed MAIL_MAILER? Run php artisan queue:restart. Otherwise the worker keeps the old mailer and you'll swear the package is broken. Queued mailables do pass through the mail manager and are captured — but only by a worker with the correct environment loaded.

Multiple apps, one viewer. Every Laravel app on your machine pointing at the same client shares one inbox. There's no per-app separation beyond what the client offers. Practical mitigations: prefix subjects from your preview command with the app name, or keep only one app configured at a time.

Attachments and inline images are where rendering checks earn their keep. Notification mail channels pass through transparently. But CID-referenced inline images are precisely the class of bug that never surfaces in a Mail::fake() assertion — verify they actually resolve in the viewer rather than showing broken-image icons.

Version and compatibility hygiene. 2.1.0 is a Laravel Shift constraint PR adding Laravel 13.x support — a maintenance signal, not a feature release. Before bumping, read the resolved composer.json and confirm two things the changelog doesn't state: whether 10/11/12 support was retained or replaced, and whether the Laravel 13 constraint drags in a minimum PHP bump that conflicts with your service images. Pin the version and let dependency automation surface future releases.

Small-vendor coupling is a real, if low, risk. Beyond Code is a small vendor and Helo is a commercial desktop product, so package viability tracks that app's continuity. The upside: the integration surface is deliberately shallow — a service provider, a config file, a transport — so swapping to Mailpit is measured in minutes. Name the risk, document the fallback, and don't build team process that depends on this package's roadmap.

Extension points worth knowing. Because it's just a mail transport, Mail::extend() lets you wrap or decorate the helo driver — for example to tag each message with the originating app or tenant before it reaches the client. And the viewer route's middleware is configurable, which is the supported way to gate access. Both beat forking.

Don't put it in the Docker image or the CI pipeline. No configuration makes a desktop-dependent capture tool work headless. The correct posture is per-developer opt-in locally, Mailpit for shared and CI environments, and one line in onboarding so nobody rediscovers this the hard way.

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.
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
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo