PHPackages                             webteractive/mailulator - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Testing &amp; Quality](/categories/testing)
4. /
5. webteractive/mailulator

ActiveLibrary[Testing &amp; Quality](/categories/testing)

webteractive/mailulator
=======================

Self-hosted Laravel-native email testing service. Captures outbound email for inspection — installable into any Laravel app.

v0.1.2(1mo ago)00MITPHPPHP ^8.3CI passing

Since Apr 25Pushed 1mo agoCompare

[ Source](https://github.com/webteractive/mailulator)[ Packagist](https://packagist.org/packages/webteractive/mailulator)[ Docs](https://github.com/webteractive/mailulator)[ RSS](/packages/webteractive-mailulator/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (4)Dependencies (14)Versions (5)Used By (0)

Mailulator
==========

[](#mailulator)

Self-hosted, Laravel-native email testing. One package ships both sides:

- **Receiver** — stores ingested email in an isolated database (default SQLite; any Laravel-supported driver works), serves a Vue 3 inbox UI at `/mailulator`.
- **Driver** — a Symfony Mailer transport registered as `mailulator`. Set `MAIL_MAILER=mailulator` and outbound mail flows to the receiver — over HTTP for [standalone](#2-standalone--ui-lives-in-a-dedicated-app-shared-by-many-senders) deployments, in-process for [in-app](#1-in-app--ui-lives-with-the-app-it-captures) ones.

Requirements
------------

[](#requirements)

- PHP `^8.3`
- Laravel `^11 || ^12 || ^13`

Install
-------

[](#install)

```
composer require webteractive/mailulator
php artisan mailulator:install
```

`mailulator:install` publishes:

- `app/Providers/MailulatorServiceProvider.php` — customize the auth gate here.
- `config/mailulator.php` — receiver + driver config.

It then runs migrations against the isolated `mailulator` connection and creates a protected `Default` inbox (it cannot be renamed or deleted; at least one inbox must always exist).

The install prints the `Default` inbox's bearer token. [In-app](#1-in-app--ui-lives-with-the-app-it-captures) deployments don't need it for delivery, but save it anyway — any sender app you later point at this receiver will need it, and it isn't shown again.

Publish compiled UI assets:

```
php artisan vendor:publish --tag=mailulator-assets
```

Re-run this tag after each `composer update`.

Deployment modes
----------------

[](#deployment-modes)

Two shapes, distinguished by where the inbox UI lives.

### 1. In-app — UI lives with the app it captures

[](#1-in-app--ui-lives-with-the-app-it-captures)

The app sending the mail is also the app you read it from. Install Mailulator there, set `MAIL_MAILER=mailulator`, and the captured mail shows up at `/mailulator` on the same host. One app, one inbox, no plumbing.

```
# .env
MAIL_MAILER=mailulator
```

No URL, no token. The transport detects in-app mode (driver enabled + receiver enabled + no `MAILULATOR_URL`) and writes directly to the `Default` inbox via `StoreIncomingEmail`, bypassing HTTP entirely. Open `/mailulator` on the same app to read the captured mail.

Best for: local development, staging, demo environments — any app that just wants to "swallow" its own outbound mail and read it back in place.

### 2. Standalone — UI lives in a dedicated app, shared by many senders

[](#2-standalone--ui-lives-in-a-dedicated-app-shared-by-many-senders)

A single Laravel app exists just to host inboxes and the UI. Any number of other apps point at it via `MAILULATOR_URL` and route to the right inbox via `MAILULATOR_TOKEN`. One UI, many senders.

**On the standalone receiver** — install the package as receiver-only:

```
# .env
MAILULATOR_RECEIVER_ENABLED=true
MAILULATOR_DRIVER_ENABLED=false
```

Create one inbox per sender app from the UI (or reuse the seeded `Default`). Each inbox has its own bearer token; that token is the only thing that ties a sender to its inbox.

**On each sender app** — install the package as driver-only and point it at the receiver with the inbox's token:

```
# .env
MAIL_MAILER=mailulator
MAILULATOR_URL=https://mailulator.your-domain.test
MAILULATOR_TOKEN=
        in_array(optional($user)->email, [
            'you@example.com',
        ])
    );
}
```

Default: local environment only. Non-local without a customized gate → 403.

Optional hooks in the same provider:

```
public function boot(): void
{
    parent::boot();

    Mailulator::canViewInbox(fn ($user, $inboxId) =>
        $user->inboxes()->where('inboxes.id', $inboxId)->exists()
    );

    Mailulator::manage(fn ($user) => $user->is_admin ?? false);
}
```

Sender-side (.env)
------------------

[](#sender-side-env)

VariableDefaultPurpose`MAIL_MAILER`—Set to `mailulator` to route outbound email through this driver.`MAILULATOR_URL`—Base URL of the receiver.`MAILULATOR_TOKEN`—Per-inbox bearer token printed by `mailulator:install` or the admin UI.`MAILULATOR_TIMEOUT``5`HTTP timeout (seconds) for ingest calls.`MAILULATOR_ON_FAILURE``log``log` (warn + return), `silent` (return), or `throw` (raise `TransportException`).`MAILULATOR_DRIVER_ENABLED``true`Set `false` to install as receiver-only.A receiver outage **will not** break the sender app's request unless `MAILULATOR_ON_FAILURE=throw`.

Receiver-side (.env)
--------------------

[](#receiver-side-env)

VariableDefaultPurpose`MAILULATOR_RECEIVER_ENABLED``true`Turn receiver off to install as driver-only.`MAILULATOR_DB_CONNECTION``mailulator`Connection **name** to use. Set to any connection defined in your host app's `config/database.php` (e.g. `mysql`) to share that DB; leave as `mailulator` for an isolated, package-managed connection.`MAILULATOR_DB_DRIVER``sqlite`Driver for the auto-managed connection — only used when `MAILULATOR_DB_CONNECTION=mailulator` and the host hasn't pre-defined it.`MAILULATOR_SQLITE_PATH``database_path('mailulator.sqlite')`SQLite file, auto-touched.`MAILULATOR_DB_HOST` / `_PORT` / `_DATABASE` / `_USERNAME` / `_PASSWORD` / `_CHARSET`—Credentials for the auto-managed connection (non-SQLite drivers).`MAILULATOR_ATTACHMENTS_DISK``local`Filesystem disk for attachment bytes.`MAILULATOR_RATE_LIMIT``600`Ingest requests/min per inbox.`MAILULATOR_RETENTION_DAYS``30`Default retention for newly created inboxes. Per-inbox override available; `null` keeps forever.`MAILULATOR_UI_PATH``mailulator`SPA path prefix.`MAILULATOR_UI_DOMAIN`—Optional subdomain (e.g. `mail.your-staging.com`).`MAILULATOR_REALTIME_ENABLED``true`Master switch for realtime updates.`MAILULATOR_REALTIME``polling``polling` or `broadcast`.`MAILULATOR_POLL_INTERVAL``3`Polling interval (seconds).`MAILULATOR_BROADCASTER``reverb``reverb` or `pusher` when `MAILULATOR_REALTIME=broadcast`.Ingest API
----------

[](#ingest-api)

`POST /api/emails` — bearer-token authenticated, rate-limited per inbox.

Accepts JSON (base64 attachments) or `multipart/form-data` (UploadedFile attachments). Returns `201 { "id":  }`.

Realtime
--------

[](#realtime)

Three states, controlled by two env vars:

`MAILULATOR_REALTIME_ENABLED``MAILULATOR_REALTIME`Behavior`true` (default)`polling` (default)UI polls every `MAILULATOR_POLL_INTERVAL` seconds. Zero extra deps.`true``broadcast`Echo subscribes to `mailulator.inbox.{id}` private channels.`false`—Static UI. No polling, no broadcast.To enable broadcasting, install Reverb / Pusher in the host app, then:

```
MAILULATOR_REALTIME=broadcast
MAILULATOR_BROADCASTER=reverb
MAILULATOR_ECHO_KEY=...
MAILULATOR_ECHO_CLUSTER=...      # Pusher only
MAILULATOR_ECHO_HOST=...          # Reverb only
MAILULATOR_ECHO_PORT=...
MAILULATOR_ECHO_SCHEME=https

```

`EmailReceived` dispatches to `mailulator.inbox.{id}` on every ingest. Channel authorization routes through `Mailulator::canViewInbox`. If `MAILULATOR_REALTIME=broadcast` is set without a configured `MAILULATOR_ECHO_KEY`, the client logs a warning and falls back to polling.

Inboxes
-------

[](#inboxes)

- Each inbox has a name, optional retention period, optional color (UI accent), and a hashed bearer token.
- The seeded `Default` inbox is protected — it cannot be renamed or deleted.
- The last remaining inbox cannot be deleted regardless of name.
- Regenerating a key invalidates the previous token immediately at the ingest boundary.

Retention
---------

[](#retention)

Set `retention_days` per inbox; the daily `PruneEmails` job deletes older emails and cleans their attachment files. `null` = keep forever.

The job is auto-scheduled. Ensure your host app runs `schedule:run` via cron or `schedule:work`.

Upgrade
-------

[](#upgrade)

```
composer update webteractive/mailulator
php artisan vendor:publish --tag=mailulator-assets --force
php artisan migrate --database=mailulator
```

License
-------

[](#license)

MIT.

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance91

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

4

Last Release

43d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/b9cd718c82f37ed135a350effd6b2b7c3a75a815a029bbfccbf8c777d1aa3184?d=identicon)[hadefication](/maintainers/hadefication)

---

Top Contributors

[![hadefication](https://avatars.githubusercontent.com/u/6673244?v=4)](https://github.com/hadefication "hadefication (11 commits)")

---

Tags

testinglaravelmailemailmailtrapstaging

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/webteractive-mailulator/health.svg)

```
[![Health](https://phpackages.com/badges/webteractive-mailulator/health.svg)](https://phpackages.com/packages/webteractive-mailulator)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.4k51.0M7.4k](/packages/larastan-larastan)[calebdw/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

15104.9k4](/packages/calebdw-larastan)[laravel/ai

The official AI SDK for Laravel.

9782.1M153](/packages/laravel-ai)[api-platform/laravel

API Platform support for Laravel

59156.3k10](/packages/api-platform-laravel)[pressbooks/pressbooks

Pressbooks is an open source book publishing tool built on a WordPress multisite platform. Pressbooks outputs books in multiple formats, including PDF, EPUB, web, and a variety of XML flavours, using a theming/templating system, driven by CSS.

45344.0k1](/packages/pressbooks-pressbooks)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
