Skip to content

feat: capture demo mail in Mailpit so the Docker stacks show email verification - #87

Merged
devondragon merged 5 commits into
mainfrom
feature/86-mailpit-docker-email-flows
Aug 18, 2026
Merged

feat: capture demo mail in Mailpit so the Docker stacks show email verification#87
devondragon merged 5 commits into
mainfrom
feature/86-mailpit-docker-email-flows

Conversation

@devondragon

@devondragon devondragon commented Aug 18, 2026

Copy link
Copy Markdown
Owner

Closes #86

Both Docker stacks ran docker-mailserver with SMTP_ONLY=1: a relay with no route to real inboxes. That is why #85 had to set USER_REGISTRATION_SENDVERIFICATIONEMAIL=false, and why the demo could never show registration verification, password reset, or the two-hop flow most adopters actually ship.

This replaces it with Mailpit, which captures outbound mail and serves it as a web inbox on localhost:8025.

Changes

  • compose.yaml, docker-compose-keycloak.yml: docker-mailserveraxllent/mailpit:v1.30. The app talks plain SMTP to mailpit:1025, auth and STARTTLS off. Neither stack overrides sendVerificationEmail any more, so the base default of true applies.
  • Removed with it: mailserver.env, the ./config/ bind mount, the maildata/mailstate/maillogs volumes, NET_ADMIN/SYS_PTRACE, and published ports 25/587. SELINUX_LABEL went with the bind mount, so docker compose no longer warns that it is unset.
  • Docs: README quick start, docs/CONFIGURATION.md mail section, docs/AUTHENTICATION.md registration and Keycloak walkthroughs, docs/DEVELOPMENT.md stack descriptions, keycloak/README.md (ports table plus a form-registration walkthrough). Also corrected a stale compose.yaml:86-90 citation to 72-76.
  • messages.properties: page.title.registration-ending-verificationregistration-pending-verification. The template always asked for the latter, so the pending page rendered ??page.title.registration-pending-verification_en?? as its title. It survived because no documented run path reached that page until now.

One deviation from the ticket

The ticket proposed a GET /livez healthcheck. The implementation uses ["CMD", "/mailpit", "readyz"] instead. Either would have worked, and readyz is preferred for a different reason than I first gave: it is exactly what the image bakes in as its own default HEALTHCHECK, so the compose block just makes the upstream default explicit.

Correction. An earlier version of this description said the image is built from scratch with no curl/wget/nc and that there is no /livez. Both claims were wrong. axllent/mailpit:v1.30 is Alpine-based with busybox wget and nc (only curl is absent), and v1.30.7 serves both /livez and /readyz over HTTP on 8025 — the ticket's proposed probe would have been fine. mailpit livez is not a CLI subcommand, which is the only sense in which the original claim held. The compose comments carried the same error and are corrected in cc0949b.

Verification

Driven in a real browser against both stacks, not just asserted:

Criterion Result
compose.yaml: register → read mail at :8025 → click verify link → log in Pass
compose.yaml: password reset via the emailed link Pass; new password logs in
Keycloak stack: both flows Pass
Keycloak OIDC login (demo/demo) still works Pass
./gradlew test BUILD SUCCESSFUL, 309 tests, 0 failures, 0 errors

Mailpit's readyz probe gated myapp-main correctly in both stacks, and the emailed links point at http://localhost:8080 (from user.security.appUrl), so they work from the host browser.

Playwright is unaffected as the ticket predicted: it runs local,playwright-test outside Docker, and that profile disables both the verification and password-reset emails.

Framework-side limitation found while verifying

The resend-verification page does not work, and it is not fixable here. UserAPI.resendRegistrationToken in ds-spring-user-framework:5.3.0 binds the full registration UserDto, whose firstName, lastName, password, and matchingPassword are @NotBlank. The email-only payload resend-verification.js posts is rejected with HTTP 400 and no mail is sent:

Validation failed for argument [0] in ... UserAPI.resendRegistrationToken(UserDto, HttpServletRequest)
with 4 errors: [... 'matchingPassword': rejected value [null] ...] [... 'password' ...]
[... 'lastName' ...] [... 'firstName' ...]

The ticket listed resend among the flows the Docker demo could not show. It still cannot, for a different reason. Documented as a known limitation in docs/CONFIGURATION.md and docs/AUTHENTICATION.md; tracked upstream.

…rification

Both stacks ran docker-mailserver with SMTP_ONLY=1, a relay with no route to
real inboxes, so #85 had to set USER_REGISTRATION_SENDVERIFICATIONEMAIL=false
and the demo could never show registration verification or password reset.

Replace it with axllent/mailpit, which captures outbound mail and serves it as
a web inbox on 8025. The app now talks plain SMTP to mailpit:1025 with auth and
STARTTLS off, and neither stack overrides sendVerificationEmail, so the base
default of true applies and the two-hop registration flow works end to end.

The healthcheck is `mailpit readyz` rather than an HTTP probe: the image is
built from scratch and has no curl, wget, or nc.

Drops mailserver.env, the ./config/ bind mount, the maildata/mailstate/maillogs
volumes, the NET_ADMIN/SYS_PTRACE capabilities, and the published ports 25/587.
SELINUX_LABEL went with the bind mount, so compose no longer warns about it.

Closes #86
messages.properties spelled the key page.title.registration-ending-verification
while registration-pending-verification.html asks for
page.title.registration-pending-verification, so the page rendered
??page.title.registration-pending-verification_en?? as its title.

The page is only reachable when verification email is on, which is why the typo
survived: no documented run path reached it until the Docker stacks switched to
Mailpit.
Rewrites the Docker quick start, the CONFIGURATION mail section, the
AUTHENTICATION registration and Keycloak walkthroughs, the DEVELOPMENT stack
descriptions, and keycloak/README.md around reading mail at localhost:8025.

Drops the SELINUX_LABEL environment row, which no longer exists, and corrects a
stale compose.yaml:86-90 healthcheck citation to 72-76.

Notes a framework-side limitation found while verifying this: the
resend-verification page fails against ds-spring-user-framework 5.3.0, whose
UserAPI.resendRegistrationToken binds the full registration UserDto with
@notblank on firstName, lastName, password, and matchingPassword, so the
email-only payload resend-verification.js posts is rejected with HTTP 400.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Docker demo stacks to use Mailpit (SMTP capture + web inbox) so the demo can exercise real email-driven flows like registration verification and password reset, and aligns documentation/messages accordingly.

Changes:

  • Replaces docker-mailserver with axllent/mailpit in both Docker stacks, updating app SMTP settings and healthchecks.
  • Restores/relies on the default user.registration.sendVerificationEmail=true so the Docker path can show verification end-to-end.
  • Fixes an i18n key mismatch for the pending-verification page title and updates docs to reference the Mailpit inbox flow.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/main/resources/messages/messages.properties Fixes the pending-verification page title key to match the template’s expected message key.
README.md Updates Docker quick start to instruct reading verification/reset emails via Mailpit on localhost:8025.
mailserver.env Removes the old docker-mailserver env file (no longer needed with Mailpit).
keycloak/README.md Documents Mailpit inbox URL and adds a form-registration walkthrough for the Keycloak stack.
docs/DEVELOPMENT.md Updates stack descriptions and references to reflect Mailpit usage and updated compose citations.
docs/CONFIGURATION.md Updates the “Mail” section to describe Mailpit behavior in Docker stacks and notes the resend limitation.
docs/AUTHENTICATION.md Updates registration and password-reset walkthroughs to reference Mailpit and documents resend limitation.
docker-compose-keycloak.yml Swaps mail service to Mailpit, updates ports/healthcheck, and points app SMTP to mailpit:1025.
compose.yaml Swaps mail service to Mailpit, updates ports/healthcheck, and points app SMTP to mailpit:1025.
CHANGELOG.md Adds an entry describing the Mailpit migration and the i18n key fix.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread CHANGELOG.md Outdated
Comment on lines +15 to +19
- Both Docker stacks (`compose.yaml`, `docker-compose-keycloak.yml`) now run
[Mailpit](https://mailpit.axllent.org/) instead of `docker-mailserver`. Mailpit captures outbound
mail and serves it as a web inbox on http://localhost:8025, so the demo exercises the real
registration-verification, resend-verification, and password-reset flows: register, read the message
in the browser, click the link, log in

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already fixed in cc0949b, which landed after this review ran. The entry now lists only registration-verification and password-reset, and points at the known-limitation note for resend.

Comment thread compose.yaml
Comment thread docker-compose-keycloak.yml
- compose.yaml, docker-compose-keycloak.yml: the "built from scratch: no
  curl, no wget, no nc" comment was wrong. Verified against
  axllent/mailpit:v1.30: it's Alpine-based with busybox providing wget
  and nc; only curl is absent. `mailpit readyz` is still the right
  choice — it's the image's own baked-in default HEALTHCHECK.
- CHANGELOG.md: dropped resend-verification from the list of flows the
  demo "exercises for real" — it's documented elsewhere in this same
  change (docs/CONFIGURATION.md, docs/AUTHENTICATION.md) as broken
  against ds-spring-user-framework 5.3.0, so the two entries
  contradicted each other.
The stacks pinned axllent/mailpit:v1.30, a floating minor tag, while the
flows were verified against v1.30.7. A patch bump could change the web UI
or the readyz probe and silently break the documented demo path.

Matches how keycloak:25.0.6 is pinned; raised in review on #87.
@devondragon
devondragon merged commit 473e73d into main Aug 18, 2026
14 checks passed
@devondragon
devondragon deleted the feature/86-mailpit-docker-email-flows branch August 18, 2026 03:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use Mailpit in the Docker stacks so the demo can show email verification and password reset

2 participants