Skip to content

feat(gateway): serve the development mail inbox at mail.<domain> - #6934

Merged
otavio merged 1 commit into
masterfrom
feat/mailpit-dev-stack
Aug 21, 2026
Merged

feat(gateway): serve the development mail inbox at mail.<domain>#6934
otavio merged 1 commit into
masterfrom
feat/mailpit-dev-stack

Conversation

@geovannewashington

Copy link
Copy Markdown
Member

What

Routes mail.<domain> to Mailpit's web UI in the development stack, so the inbox is opened by name like docs.<domain> and website.<domain> rather than by remembering a container port.

Part of https://github.com/shellhub-io/team/issues/223

Companion PR (cloud repo, the SMTP provider and the Mailpit container): shellhub-io/cloud/pull/2505 That one is where the work actually is: this PR only routes to it. On its own, this block has no upstream and the hostname would 502. Merge order doesn't matter.

Why

The cloud repo's development stack now runs Mailpit, which catches every email the stack sends and shows it rendered. Nothing routed to it. The alternative was publishing its UI port to the host, which every other development-only service in this stack avoids.

Changes

One vhost in gateway/Caddyfile.tmpl, proxying mail.<domain> to mailpit:8025 and importing the shared forwarded snippet like every other route in the file.

It sits inside two conditionals: .Development, alongside the website. and docs. blocks, and EnableEnterprise, alongside idp.. The second guard is there because both the mail code and the Mailpit container ship with the cloud repository, so a community stack has no upstream here and the name would only ever error. That is the same reason the SAML test IdP is gated the way it is.

Testing

TestCaddyfileServesTheDevelopmentMailInbox pins both halves of the gating: the hostname is present in the development enterprise config, absent from a community development stack, and absent from a non-development enterprise stack. An edition-gated block is invisible until someone runs that edition, which is what makes this worth a test rather than a read.

The existing TestCaddyfileAdapts already covers the block for free: it hands every configuration shape to the real Caddy adapter, so a typo or an unresolvable directive fails there rather than at boot. TestEveryUpstreamReceivesTheClientAddress likewise checks the new reverse_proxy carries the client address.

To exercise it, run a development stack with SHELLHUB_EDITION=cloud (or enterprise, though nothing will send mail on that edition) and open http://mail.localhost. Mailpit's UI answers through the gateway:

curl -s -o /dev/null -w '%{http_code}\n' http://mail.localhost/

Note that curl -I returns 405 here: Mailpit's UI does not accept HEAD. That is the proxy working, not failing. Verified against a running stack: 200 on the UI and on its /api/v1/messages endpoint.

The development stack now runs Mailpit, which catches every email the
stack sends and shows it rendered. Nothing routed to it, so opening the
inbox meant remembering a container port.

Add a vhost beside the existing `website.` and `docs.` blocks, proxying
`mail.<domain>` to Mailpit's web UI. It is gated on development and on
the enterprise edition, like the `idp.` block above it: both the mail
code and the Mailpit container ship with the cloud repository, so a
community stack has no upstream here and the name would only ever 502.

`TestCaddyfileServesTheDevelopmentMailInbox` pins both halves of that
gating, because an edition-gated block is invisible until someone runs
that edition.
@geovannewashington geovannewashington self-assigned this Aug 20, 2026
@geovannewashington
geovannewashington requested a review from a team as a code owner August 20, 2026 20:53
@geovannewashington geovannewashington added go Pull requests that update Go code area/docker area/gateway labels Aug 20, 2026
@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown

Claude finished @geovannewashington's task in 1m 3s —— View job


Code Review Complete

Reviewed 2 files across code quality, security, testing, language patterns, and architecture — no new issues found. The code looks good as-is.

The new mail.<domain> vhost is correctly nested inside both {{- if .Development }} and {{- if $cfg.EnableEnterprise }}, imports the shared forwarded snippet like every other route, and mirrors the idp. block's gating exactly. TestCaddyfileServesTheDevelopmentMailInbox pins all three gating cases (present in dev+enterprise, absent in dev community, absent in non-dev enterprise), and the existing TestCaddyfileAdapts / TestEveryUpstreamReceivesTheClientAddress cover the block for free.

If you push additional changes and want a new review, tag @shellhub-io/admin and a team member can trigger it.

@otavio
otavio merged commit 2d2372c into master Aug 21, 2026
50 of 70 checks passed
@otavio
otavio deleted the feat/mailpit-dev-stack branch August 21, 2026 19:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/docker area/gateway go Pull requests that update Go code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants