A zero-configuration local email inbox for Laravel. Capture, inspect, and test emails without external services—like Mailtrap, but self-contained within your application.
- Features
- Requirements
- Installation
- Configuration
- Usage
- Frontend Integration
- Storage Drivers
- Authorization & Security
- Testing
- Development
- Changelog
- Security Vulnerabilities
- Credits
- License
✨ Core Features:
- Local Mailtrap-style inbox — Capture all outgoing emails in a beautiful web interface
- Zero external dependencies — No Mailtrap, Mailhog, or third-party services required
- Self-contained Inertia.js dashboard — Vue 3-powered UI that's completely isolated from your host application
- Works with any frontend stack — Compatible with Blade-only, Vue, React, Livewire, or existing Inertia apps
- Multiple storage drivers — Database (SQLite, MySQL, PostgreSQL) or file-based storage
- Message normalization — Structured capture of headers, recipients, attachments, HTML/text bodies
- Automatic retention policies — Configure message pruning to prevent disk bloat
- Authorization middleware — Gate-based access control for production safety
- Attachment support — View and download email attachments
- Mark as read/unread — Track which messages you've reviewed
- Delete functionality — Clear entire inbox or delete individual messages with confirmation dialogs
- Responsive UI — Beautiful TailwindCSS-based interface with dark mode support
- Developer-friendly — Auto-enabled in non-production environments
- Test helpers — Send test emails directly from the dashboard
- PHP:
^8.3 - Laravel:
^10.0|^11.0|^12.0 - Node.js & NPM: Required only if rebuilding frontend assets (pre-built assets included)
For development environments (recommended):
composer require redberry/mailbox-for-laravel --devFor production (if you need to capture emails in production):
composer require redberry/mailbox-for-laravelphp artisan mailbox:installThis command will:
- Publish frontend assets to
public/vendor/mailbox/ - Run database migrations (creates
mailbox_messagestable by default) - Set up the necessary configuration
Available Flags:
--dev— Link assets for development (watches for file changes)--force— Force overwrite existing published assets--refresh— Runmigrate:refreshinstead ofmigrate(⚠️ drops tables)
Examples:
# Standard installation
php artisan mailbox:install
# Force reinstall (overwrites existing assets)
php artisan mailbox:install --force
# Development mode with linked assets
php artisan mailbox:install --dev
# Fresh install with database reset
php artisan mailbox:install --refreshSet your mail driver to mailbox to capture outgoing emails.
Add to your .env:
MAIL_MAILER=mailboxConfigure you new mailer, that will use mailbox transport config/mail.php:
'mailers' => [
'mailbox' => [
'transport' => 'mailbox',
],
],Note: Without this configuration, emails will be sent normally but won't be captured by the mailbox.
Visit the dashboard at:
http://localhost/mailbox
Or your configured route (see Configuration).
Note: The package is auto-discovered by Laravel. No manual service provider registration needed.
After installation, the configuration file is available at config/mailbox.php.
<?php
return [
/*
|--------------------------------------------------------------------------
| Enable Mailbox
|--------------------------------------------------------------------------
|
| By default, the mailbox is enabled in all environments except production.
| Set MAILBOX_ENABLED=true in production to capture emails.
|
*/
'enabled' => env('MAILBOX_ENABLED', env('APP_ENV') !== 'production'),
/*
|--------------------------------------------------------------------------
| Storage Configuration
|--------------------------------------------------------------------------
|
| Configure where captured emails are stored. Available drivers:
| - database: Store in a dedicated database connection (SQLite by default)
| - file: Store as JSON files on disk
|
*/
'store' => [
'driver' => env('MAILBOX_STORE_DRIVER', 'database'),
// Custom storage driver resolvers
'resolvers' => [
// 'custom' => fn() => new \App\CustomMessageStore,
],
// File storage options
'file' => [
'path' => env('MAILBOX_FILE_PATH', storage_path('app/mailbox')),
],
// Database storage options
'database' => [
'connection' => env('MAILBOX_DB_CONNECTION', 'mailbox'),
'table' => env('MAILBOX_DB_TABLE', 'mailbox_messages'),
],
],
/*
|--------------------------------------------------------------------------
| Message Retention
|--------------------------------------------------------------------------
|
| Automatically purge messages older than the specified number of seconds.
| Default: 24 hours (86400 seconds).
|
*/
'retention' => [
'seconds' => (int) env('MAILBOX_RETENTION', 60 * 60 * 24),
],
/*
|--------------------------------------------------------------------------
| Authorization Gate
|--------------------------------------------------------------------------
|
| Define which Laravel Gate ability is checked before allowing access
| to the mailbox dashboard. Default: 'viewMailbox'
|
| Define in AuthServiceProvider:
| Gate::define('viewMailbox', fn ($user) => $user->isAdmin());
|
*/
'gate' => env('MAILBOX_GATE', 'viewMailbox'),
/*
|--------------------------------------------------------------------------
| Unauthorized Redirect
|--------------------------------------------------------------------------
|
| Where to redirect users who fail authorization.
| Default: null (shows Laravel's 403 page)
|
*/
'unauthorized_redirect' => env('MAILBOX_REDIRECT', null),
/*
|--------------------------------------------------------------------------
| Dashboard Route
|--------------------------------------------------------------------------
|
| The URI prefix where the mailbox dashboard is accessible.
| Default: 'mailbox'
|
*/
'route' => env('MAILBOX_DASHBOARD_ROUTE', 'mailbox'),
/*
|--------------------------------------------------------------------------
| Middleware Stack
|--------------------------------------------------------------------------
|
| Middleware applied to all mailbox routes.
| Default: ['web']
|
*/
'middleware' => ['web'],
];Add these to your .env file to customize behavior:
# Enable in production (default: auto-enabled in non-production)
MAILBOX_ENABLED=true
# Storage driver (database or file)
MAILBOX_STORE_DRIVER=database
# Database connection (for database driver)
MAILBOX_DB_CONNECTION=mailbox
MAILBOX_DB_TABLE=mailbox_messages
# File storage path (for file driver)
MAILBOX_FILE_PATH=/path/to/storage/mailbox
# Message retention (in seconds, default: 24 hours)
MAILBOX_RETENTION=86400
# Authorization gate
MAILBOX_GATE=viewMailbox
# Redirect on unauthorized access
MAILBOX_REDIRECT=/login
# Dashboard route prefix
MAILBOX_DASHBOARD_ROUTE=mailboxThe package uses a separate SQLite database by default to avoid cluttering your main database. This is configured in config/mailbox.php:
'store' => [
'driver' => env('MAILBOX_STORE_DRIVER', 'database'),
// ...
'database' => [
'connection' => env('MAILBOX_DB_CONNECTION', 'mailbox'),
'table' => env('MAILBOX_DB_TABLE', 'mailbox_messages'),
],
// ...
],and then we inject 'mailbox' database connection into the config array, like so:
config([
'database.connections.mailbox' => [
'driver' => 'sqlite',
'database' => storage_path('app/mailbox/mailbox.sqlite'),
'prefix' => '',
'foreign_key_constraints' => true,
],
]);If you want to override the default connection, you can add a new connection or use an existing one. All you need to do is add a new connection into your config/database.php:
'connections' => [
'custom_connection' => [
'driver' => 'sqlite',
'url' => env('MAILBOX_DB_URL'),
'database' => env('MAILBOX_DB_DATABASE', database_path('mailbox.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
],
]Then set the new custom connection in .env
MAILBOX_DB_CONNECTION=custom_connectionOr use your main database connection:
MAILBOX_DB_CONNECTION=mysql # or pgsql, sqlsrv, etc.The mailbox dashboard provides a modern email client interface with:
- Message list — All captured emails sorted newest-first
- Preview pane — View email content (HTML, plain text, raw source)
- Recipient filtering — Filter by To, Cc, Bcc recipients
- Read/Unread tracking — Mark messages as seen
- Attachment viewer — Download email attachments
- Test email sender — Send sample emails for testing
- Clear inbox — Remove all captured messages with confirmation
- Delete messages — Remove individual messages with confirmation
Navigate to your configured route (default: /mailbox):
http://localhost/mailbox
http://yourapp.test/mailbox
https://staging.yourapp.com/mailbox
The package registers the following HTTP endpoints:
| Method | Endpoint | Description |
|---|---|---|
GET |
/mailbox |
Load the dashboard (Inertia page) |
DELETE |
/mailbox/messages |
Delete all captured messages |
DELETE |
/mailbox/messages/{id} |
Delete a specific message |
POST |
/mailbox/test-email |
Send a test email |
POST |
/mailbox/messages/{id}/seen |
Mark a message as read/unread |
Example: Mark Message as Read
// From frontend (Inertia)
router.post(`/mailbox/messages/${messageId}/seen`, { seen: true })
// Response: Updated message object
{
"id": "msg_123",
"seen_at": "2025-11-19T10:30:00.000000Z",
...
}Example: Delete a Specific Message
// From frontend (Inertia)
router.delete(`/mailbox/messages/${messageId}`)
// Response:
{
"status": "deleted"
}Example: Clear All Messages
// From frontend (Inertia)
router.delete('/mailbox/messages')
// Response: Empty JSON
{}Use the "Send Test Email" button in the dashboard, or programmatically:
use Illuminate\Support\Facades\Mail;
use Illuminate\Mail\Message;
Mail::raw('This is a test email', function (Message $message) {
$message->to('[email protected]')
->subject('Test Email')
->from('[email protected]');
});- Your application sends an email using Laravel's
Mailfacade - Mailbox Transport intercepts the outgoing message
- Message is normalized into a structured format (headers, body, attachments)
- Stored in configured driver (database or file system)
- Displayed in dashboard with full content and metadata
Behind the scenes:
// MailboxTransport::doSend()
$payload = MessageNormalizer::normalize($original, $envelope, $raw, true);
$key = $this->mailbox->store($payload);Stored payload structure:
{
"from": "[email protected]",
"to": ["[email protected]"],
"cc": [],
"bcc": [],
"subject": "Test Email",
"date": "2025-11-19T10:30:00+00:00",
"text": "Plain text body",
"html": "<html>HTML body</html>",
"attachments": [],
"raw": "Full RFC 822 message",
"timestamp": 1732017000,
"saved_at": "2025-11-19T10:30:00.000000Z",
"seen_at": null
}Email attachments are captured and stored alongside the message. Access them via:
- Dashboard UI: Click "View" on the attachment
- Direct download:
/mailbox/messages/{id}/attachments/{index}
Attachment metadata:
{
"filename": "document.pdf",
"content_type": "application/pdf",
"size": 102400,
"content": "base64-encoded-data"
}From Dashboard:
- Click the "Clear Inbox" button in the filter bar (with trash icon)
- Confirm the action in the dialog that appears
- All messages will be permanently deleted
From Message Detail:
- Click the trash icon button in the top-right corner of the message preview
- Confirm the deletion in the dialog
- The specific message will be permanently deleted
Programmatically:
use Redberry\MailboxForLaravel\Facades\Mailbox;
// Clear all messages
Mailbox::clearAll();
// Delete a specific message
Mailbox::delete($messageId);Via Artisan (future feature):
php artisan mailbox:clearNote: Both clear and delete operations show confirmation dialogs in the UI to prevent accidental data loss.
The mailbox uses Inertia.js + Vue 3 for its dashboard, but operates in complete isolation from your host application's frontend stack.
Key isolation mechanisms:
- Namespaced components — All Inertia renders use the
mailbox::prefix - Dedicated middleware —
mailbox.inertiamiddleware handles Inertia responses separately - Scoped assets — Built to
public/vendor/mailbox/with independent manifest - Separate Vue instance — Creates its own app, doesn't mount to your app's root
Backend (Controller):
use Inertia\Inertia;
return Inertia::render('mailbox::Dashboard', [
'messages' => $messages,
'title' => 'Mailbox for Laravel',
]);Frontend (Entry Point):
// resources/js/dashboard.js
import { createInertiaApp } from '@inertiajs/vue3'
createInertiaApp({
resolve: (name) => {
const pageName = name.replace(/^mailbox::/, '')
const pages = import.meta.glob('./Pages/**/*.vue', { eager: true })
return pages[`./Pages/${pageName}.vue`]
},
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) })
.use(plugin)
.mount(el)
},
})✅ Works alongside your existing frontend:
- Blade-only apps — No conflicts, package bundles its own JS
- Vue without Inertia — Separate Vue instances, no shared state
- React — No conflicts with React or other frameworks
- Existing Inertia apps — Uses different middleware and namespaces
- Livewire — Fully compatible
Pre-built assets are included, but you can rebuild if needed:
# Install dependencies
npm install
# Build for production
npm run build
# Watch for changes (development)
npm run devBuilt assets output to:
public/vendor/mailbox/manifest.jsonpublic/vendor/mailbox/assets/dashboard-[hash].jspublic/vendor/mailbox/assets/dashboard-[hash].css
Stores messages in a dedicated SQLite database (database/mailbox.sqlite).
Pros:
- Fast queries and filtering
- ACID compliance
- Supports complex queries
- Easy to inspect with database tools
Configuration:
MAILBOX_STORE_DRIVER=database
MAILBOX_DB_CONNECTION=mailbox
MAILBOX_DB_TABLE=mailbox_messagesStores each message as a JSON file in storage/app/mailbox/.
Pros:
- No database required
- Easy to inspect/debug
- Portable (copy files between environments)
Configuration:
MAILBOX_STORE_DRIVER=file
MAILBOX_FILE_PATH=/path/to/storage/mailboxImplement the MessageStore contract:
namespace App\Storage;
use Redberry\MailboxForLaravel\Contracts\MessageStore;
class RedisMessageStore implements MessageStore
{
public function store(array $payload): string|int
{
// Implementation
}
public function get(string|int $key): ?array
{
// Implementation
}
// ... other methods
}Register in config/mailbox.php:
'store' => [
'resolvers' => [
'redis' => fn() => new \App\Storage\RedisMessageStore,
],
],Use via .env:
MAILBOX_STORE_DRIVER=redisBy default, access is controlled via Laravel's Gate system using the viewMailbox ability.
Define in AuthServiceProvider:
use Illuminate\Support\Facades\Gate;
public function boot()
{
Gate::define('viewMailbox', function ($user) {
return $user->isAdmin();
});
}Or use a Policy:
Gate::define('viewMailbox', [MailboxPolicy::class, 'view']);To disable authorization (e.g., for local development):
// In AuthServiceProvider::boot()
Gate::define('viewMailbox', fn () => true);Or create a custom gate in config:
MAILBOX_GATE=alwaysAllowGate::define('alwaysAllow', fn () => true);- Captured emails may contain sensitive data (passwords, tokens, etc.)
- Always require authentication in production
- Consider IP whitelisting for staging environments
- Use
MAILBOX_ENABLED=falsein production unless necessary
Recommended production config:
# Disable by default
MAILBOX_ENABLED=false
# Enable only for admins
MAILBOX_GATE=viewMailbox
# Redirect unauthorized users
MAILBOX_REDIRECT=/loginThe package includes comprehensive test coverage using Pest.
# Run all tests
composer test
# Run with coverage report
composer test-coverage
# Run only unit tests
./vendor/bin/pest --filter=Unit
# Run only feature tests
./vendor/bin/pest --filter=FeatureTarget coverage: 90%+ lines, 80%+ branches
# Generate HTML coverage report
./vendor/bin/pest --coverage --coverage-html=coverage
# Fail if coverage drops below threshold
./vendor/bin/pest --coverage --min=90Run PHPStan for type safety:
composer analyse
# Or directly
./vendor/bin/phpstan analyseFormat code with Laravel Pint:
composer format
# Or directly
./vendor/bin/pintTests follow the repository structure:
tests/
├── Architecture/ # Arch tests for architectural rules
├── Feature/ # Integration tests for controllers, commands
└── Unit/ # Unit tests for services, transport, storage
Example test:
use Redberry\MailboxForLaravel\CaptureService;
it('stores a message and returns a key', function () {
$service = app(CaptureService::class);
$key = $service->store([
'from' => '[email protected]',
'subject' => 'Test',
'raw' => 'Full message',
]);
expect($key)->toBeString();
expect($service->get($key))->toBeArray();
});The package uses Orchestra Testbench Workbench for local development.
1. Clone the repository:
git clone https://github.com/RedberryProducts/mailbox-for-laravel.git
cd mailbox-for-laravel2. Install dependencies:
composer install
npm install3. Set up the database:
php artisan mailbox:install --dev4. Start the development server:
# Terminal 1: Laravel dev server
php artisan serve
# Terminal 2: Vite dev server (hot reload)
npm run dev5. Visit the dashboard:
http://localhost:8000/mailbox
The package includes a Workbench app for testing integration:
# Run Workbench
php artisan serve
# Access at http://localhost:8000Workbench configuration:
workbench/
├── app/ # Test application code
├── bootstrap/ # Workbench bootstrap
├── config/ # Test config files
├── database/ # Test migrations/seeders
└── routes/ # Test routes
Development mode (with hot reload):
npm run devProduction build:
npm run buildLink assets for development:
php artisan mailbox:install --devThis creates symlinks instead of copying files, allowing hot module replacement.
We welcome contributions! Please follow these guidelines:
Code Standards:
- Follow Laravel coding style (PSR-12)
- Run
composer formatbefore committing - Ensure PHPStan passes:
composer analyse - Write tests for new features (90%+ coverage required)
Pull Request Process:
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Make changes with tests and documentation
- Run tests:
composer test && composer analyse - Format code:
composer format - Commit with conventional commit messages:
feat: add message filtering - Push and open a Pull Request
Commit Convention:
feat:— New featuresfix:— Bug fixesdocs:— Documentation changestest:— Test additions/changesrefactor:— Code refactoringchore:— Maintenance tasks
Branch Naming:
feature/description— New featuresfix/description— Bug fixesdocs/description— Documentation updates
All notable changes to this project are documented in CHANGELOG.md.
If you discover a security vulnerability within this package, please email [email protected] instead of using the issue tracker. All security vulnerabilities will be promptly addressed.
- Nika Jorjoliani — Creator & Maintainer
- Redberry — Development Agency
- All Contributors
The MIT License (MIT). Please see LICENSE.md for more information.