feat: capture demo mail in Mailpit so the Docker stacks show email verification - #87
Merged
Merged
Conversation
…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.
Contributor
There was a problem hiding this comment.
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-mailserverwithaxllent/mailpitin both Docker stacks, updating app SMTP settings and healthchecks. - Restores/relies on the default
user.registration.sendVerificationEmail=trueso 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 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 |
Owner
Author
There was a problem hiding this comment.
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.
- 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #86
Both Docker stacks ran
docker-mailserverwithSMTP_ONLY=1: a relay with no route to real inboxes. That is why #85 had to setUSER_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-mailserver→axllent/mailpit:v1.30. The app talks plain SMTP tomailpit:1025, auth and STARTTLS off. Neither stack overridessendVerificationEmailany more, so the base default oftrueapplies.mailserver.env, the./config/bind mount, themaildata/mailstate/maillogsvolumes,NET_ADMIN/SYS_PTRACE, and published ports 25/587.SELINUX_LABELwent with the bind mount, sodocker composeno longer warns that it is unset.docs/CONFIGURATION.mdmail section,docs/AUTHENTICATION.mdregistration and Keycloak walkthroughs,docs/DEVELOPMENT.mdstack descriptions,keycloak/README.md(ports table plus a form-registration walkthrough). Also corrected a stalecompose.yaml:86-90citation to72-76.messages.properties:page.title.registration-ending-verification→registration-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 /livezhealthcheck. The implementation uses["CMD", "/mailpit", "readyz"]instead. Either would have worked, andreadyzis preferred for a different reason than I first gave: it is exactly what the image bakes in as its own defaultHEALTHCHECK, so the compose block just makes the upstream default explicit.Verification
Driven in a real browser against both stacks, not just asserted:
compose.yaml: register → read mail at :8025 → click verify link → log incompose.yaml: password reset via the emailed linkdemo/demo) still works./gradlew testMailpit's
readyzprobe gatedmyapp-maincorrectly in both stacks, and the emailed links point athttp://localhost:8080(fromuser.security.appUrl), so they work from the host browser.Playwright is unaffected as the ticket predicted: it runs
local,playwright-testoutside 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.resendRegistrationTokeninds-spring-user-framework:5.3.0binds the full registrationUserDto, whosefirstName,lastName,password, andmatchingPasswordare@NotBlank. The email-only payloadresend-verification.jsposts is rejected with HTTP 400 and no mail is sent: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.mdanddocs/AUTHENTICATION.md; tracked upstream.