Every email your app sends, caught on your Mac
PortBay bundles Mailpit as a supervised sidecar: your projects' outgoing mail lands in a local inbox at 127.0.0.1:8025 instead of a real mailbox, with zero configuration.
How do I test emails locally without sending them?
Point the app's SMTP settings at a local mail sink instead of a real server. A catcher like Mailpit listens on 127.0.0.1:1025, accepts every message the app hands it, and shows the result in a web inbox on 127.0.0.1:8025, so a password-reset test never reaches a real mailbox. The manual version is brew install mailpit, running it yourself, and editing MAIL_HOST and MAIL_PORT in every project. PortBay bundles Mailpit and starts it with the app; both listeners are loopback-only and fall forward to the next free port when one is taken. While it runs, MAIL_HOST, MAIL_PORT, MAIL_MAILER, MAIL_ENCRYPTION and MAIL_FROM_ADDRESS are injected into every project process at launch, matching the names Laravel and Symfony read out of the box; your own env values always win. Messages persist in a local SQLite store capped at 1,000.
Verified against the product, 2026-07-10.
Send from the app, read it next door
The project sends a password reset, Mailpit catches it on the loopback interface, and the message is readable in the local inbox seconds later.
From send() to inbox
- Open PortBay
- Start your project
- Trigger a send
- Inspect it
- Point non-framework code at it
Open PortBay
Mailpit starts with the app as a supervised sidecar. The Services tab shows its card with the live ports when it is up.
smtp :1025 · ui :8025Start your project
PortBay injects the MAIL_* variables into the process at launch, using the names Laravel and Symfony expect. No .env edits; a project that sets its own MAIL_HOST keeps it.
MAIL_HOST=127.0.0.1 MAIL_PORT=1025Trigger a send
A password reset, a welcome mail, an invoice. The message lands in the local inbox within seconds instead of a real mailbox.
Inspect it
Click Open inbox on the Mailpit card (or visit 127.0.0.1:8025). Read the HTML, the plain-text part and the headers; forwarded nowhere, stored locally.
http://127.0.0.1:8025Point non-framework code at it
Anything that speaks SMTP works: Nodemailer with host 127.0.0.1 port 1025, or MAILER_DSN=smtp://127.0.0.1:1025 for Symfony.
Mail capture, tier by tier
Laravel and Symfony naming; your project's own env values override.
SQLite, persists across restarts; oldest messages age out past the cap.
Mailpit ships inside the app bundle. If a build without the sidecar reports it missing, brew install mailpit restores it.
The usual three ways to test email
Without a catcher you send real mail to a test address, grep HTML out of a log file, or assemble and babysit the catcher yourself.
PortBay ships the catcher, supervises it, and wires every project into it at launch.
# Option A: real SMTP + a spare Gmail — password resets hit real inboxes# Option B: MAIL_MAILER=log, then read raw HTML out of the log$ tail -f storage/logs/laravel.log # Option C: run the catcher yourself, per machine$ brew install mailpit$ mailpit --smtp 127.0.0.1:1025 --listen 127.0.0.1:8025 &$ nano .env # MAIL_HOST, MAIL_PORT — in every project# New laptop or new project: repeat.
One app, not a stack of tools
Local databases
Six engines, one wizard: isolated data dirs, dedicated ports, supervision, and connection vars injected into linked projects on start.
Explore →HTTPS + .test domains
https://name.test per project: mkcert certificates, managed local DNS and a bundled Caddy edge, with expiry and reissue in view.
Explore →Agent dev environment
Stack detection, runtimes, HTTPS, databases and mail, provisioned per project — the running app your agents verify their work against.
Explore →Asked before downloading
Not through Mailpit. Both listeners bind to 127.0.0.1 only, so mail routed to the catcher cannot leave the machine. The risk case is a project that overrides MAIL_HOST with a real SMTP server; PortBay respects your overrides, so check the project's own env if you set one.
No. When Mailpit is running, PortBay injects MAIL_HOST, MAIL_PORT, MAIL_MAILER, MAIL_ENCRYPTION and MAIL_FROM_ADDRESS into every project process at launch, using the names Laravel and Symfony read by default. A project that already sets any of these keeps its own value.
Point any SMTP client at 127.0.0.1:1025. With Nodemailer: createTransport({ host: '127.0.0.1', port: 1025, secure: false }). Generic SMTP_HOST/SMTP_PORT env vars work too, and Symfony takes MAILER_DSN=smtp://127.0.0.1:1025.
In a local SQLite database under PortBay's app-data folder. The store is capped at 1,000 messages; when the inbox exceeds that, the oldest age out. Messages persist across app restarts until they age out or you delete them in the web UI.
The sidecar binary was not found, which is unusual since it ships in the app bundle. Install it with brew install mailpit and restart PortBay; everything else keeps working in the meantime.

Give your projects and your agents a real local home.
Download for macOSFree & open source · macOS 11+ on Apple Silicon · Pro from $10/mo