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.
Architecture fit. The package is a thin adapter over one framework seam: it registers a service provider, ships a config file, and adds a custom transport to Laravel's mail manager so that MAIL_MAILER=helo routes messages to a local HTTP endpoint consumed by the Helo desktop client. It does not touch controllers, models, or mailable classes. If the application sends mail through Laravel's built-in mailer, it composes for free with Mailable classes, Markdown mail, Notifiable models using the mail channel, raw Mail::send, and mailables dispatched from queued jobs. If the application sends mail some other way (direct SMTP sockets, a third-party SDK), this package is irrelevant. That is the entire architectural decision surface.
Integration feasibility. High. The documented path is a composer require --dev, a config publish, and one line in a developer's personal .env. Verification is unambiguous and binary: send a real transactional message, look in the viewer, it is there or it is not. There are only two failure causes at first contact — the client is not listening on the configured host/port, or config is cached. There is no schema change, no queue change, no deploy change, and no production runtime surface as long as the dependency stays in require-dev.
Technical risk (ranked by severity, not likelihood).
helo transport is active in staging or production, outbound mail goes into a void — no bounce, no exception, no log line. Customers simply never receive password resets. Severity is high, likelihood is low, and detectability from application telemetry is effectively zero. Mitigations are cheap and layered: --dev + deploy with --no-dev (package absent ⇒ Unsupported mail transport [helo], a loud failure), never commit the value to .env.example/CI/Docker/K8s manifests, and a boot-time guard that throws when the mailer is helo outside local/testing..env; conversely, a cached config containing helo reaching a production image produces a hard failure at first send. Both are operational hygiene, not code defects.host.docker.internal rather than 127.0.0.1. Predictable, but it will generate tickets.Key questions for the team.
Stack fit. Laravel application using the built-in mailer, with MAIL_MAILER as the only place the driver name appears. Shared .env.example stays boring (log); helo is a per-developer opt-in. composer require --dev so deployed images never contain the transport. Tests are unaffected: Mail::fake() short-circuits the mail manager before the transport is resolved, so Pest/PHPUnit assertions about that mail was sent to whom keep working untouched. Helo covers the complementary question of what it looks like, which no assertion covers.
Migration path. There is no code migration — the change is additive and reversible in minutes. What actually migrates is the workflow: sample-message inspection moves from shared inboxes and paid third-party sinks to the developer's machine. That has a privacy upside ("message content never leaves the laptop" is a much simpler review answer than a SaaS data-processing assessment) and a documentation obligation, because the old workflow must be explicitly retired for local use while remaining the sanctioned answer for CI, containerized preview environments, and SSH workflows.
Compatibility. Release 2.1.0 is a Laravel Shift constraint PR adding Laravel 13.x support — a maintenance signal, not a feature release, and a positive one: it answers "does adopting this block our upgrade?" with no. Two things the changelog does not state and that must be read off the resolved composer.json before bumping: whether 10/11/12 support was retained or replaced, and whether the 13.x constraint implies a PHP minimum that conflicts with existing images. MIT license removes legal friction. Public adoption signal is thin (roughly 90 stars, zero dependents), so we are early adopters — acceptable here only because the blast radius and exit cost are both small.
Sequencing.
MAIL_MAILER=helo locally, and validate against a real flow — password reset or invite — not a synthetic Mail::raw. Check three things a log can never show: HTML renders, links point at the correct environment host, attachments and inline CID images resolve.require-dev, add a pipeline grep for MAIL_MAILER=helo in committed env/manifest files, and set a conservative transport timeout.config:clear and queue:restart reminders, the host.docker.internal caveat, and the Mailpit fallback for CI/containers. Name an owner for the "Helo doesn't work on the remote box" ticket class.app:preview-mail console command that fires the full transactional catalogue at a factory-made user with prefixed subjects, and add "run preview-mail and eyeball every template" as a line in the release checklist. This is the step that converts a nice tool into a release gate, and it is a day of work.Do not sequence this against a release freeze, and fold the Laravel 13 compatibility check into the existing dependency-validation pass rather than creating a second review cycle.
Maintenance. Near zero and mostly non-code. No database, no migrations, no scheduled jobs, no production runtime. The recurring cost is version hygiene — a Composer bump plus a read of the release notes — and documentation drift, which is the real long-term liability. Pin the version, let dependency automation surface future releases, and treat any major bump as a review trigger.
Support. MIT, community-supported, no SLA, no certification, small vendor whose continuity is coupled to a commercial desktop application. This is acceptable precisely because the tool is development-time and the swap cost is minutes. Internally, support burden shifts to developer experience: without a documented answer, "how do I see the email the app just sent" becomes tribal knowledge and then a recurring interruption.
Scaling. Linear in headcount at roughly zero marginal cost per seat — every developer configures their own .env and runs their own client. It does not scale to shared or headless contexts, and it has no multi-tenant separation: every Laravel app on a machine pointing at the same client shares one inbox. Mitigate with subject prefixes in the preview command or by keeping one app configured at a time. For CI and shared preview environments, Mailpit is the correct answer and should be documented next to Helo, not discovered later.
Failure modes and controls.
| Failure | Severity | Detection | Control |
|---|---|---|---|
helo active in staging/prod |
High | None from app telemetry | require-dev + --no-dev; boot-time exception; pipeline grep for the env value |
| Client not running | Low | Obvious (no mail arrives) but slow requests | Low transport timeout; "check the client first" triage note |
| Stale cached config | Low–Medium | Confusing, intermittent | config:clear locally; queue:restart after env changes |
Cached helo config in a prod image |
Medium | Loud failure at first send | Deploy-time env validation; the same grep |
| Docker/Sail networking | Low | Connection refused on every send | host.docker.internal in the documented setup |
| Viewer route exposed on a shared network | Medium–High | Probably silent | Bind to web + auth + admin gate, or restrict by IP; treat payloads as sensitive |
| Laravel/PHP constraint conflict | Low | Dependency resolution failure | Read resolved composer.json before bumping; pin |
| Vendor discontinuity | Low | — | Documented Mailpit fallback; shallow surface keeps exit cheap |
Ramp-up. Minutes per developer for setup; the real ramp is the habit shift from "check the shared inbox" to "check the viewer." Budget roughly half a day of engineering for the pilot, the boot guard, and the preview command, plus a paragraph of onboarding documentation. Measure adoption by counting developers with the mailer configured, watch for repeat tickets in the container/remote class, and track the one metric that matters: incidents of real users not receiving transactional mail due to a mis-scoped mail transport. That number should be zero, and the layered controls above are what keep it there.
How can I help you explore Laravel packages today?