Skip to content
PortBay

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.

The short answer

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.

In the app

Send from the app, read it next door

Mailpit — local inbox
smtp 127.0.0.1:1025ui 127.0.0.1:8025loopback only
Inbox
Waiting for mail…

The project sends a password reset, Mailpit catches it on the loopback interface, and the message is readable in the local inbox seconds later.

How it works

From send() to inbox

  1. Open PortBay
  2. Start your project
  3. Trigger a send
  4. Inspect it
  5. 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 :8025

Start 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=1025

Trigger 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:8025

Point 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.

Spec

Mail capture, tier by tier

CapabilityCommunityPro
Mailpit capture + web inboxIncludedIncluded
MAIL_* env injection at project launchIncludedIncluded

Laravel and Symfony naming; your project's own env values override.

Loopback-only listenersAlwaysAlways
Message store1,000 messages1,000 messages

SQLite, persists across restarts; oldest messages age out past the cap.

Projects sending into itUp to 6Unlimited

Mailpit ships inside the app bundle. If a build without the sidecar reports it missing, brew install mailpit restores it.

Without PortBay

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.

Questions

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.

PortBay mascot — a friendly blue tugboat

Give your projects and your agents a real local home.

Download for macOS

Free & open source · macOS 11+ on Apple Silicon · Pro from $10/mo