fix: make mail truly optional and clarify quick-start docs (#310)#311
Conversation
MailService now degrades gracefully when no JavaMailSender bean is available (typically because spring.mail.host is unset) — operations log a warning and return instead of failing application startup. README quick-start updates: - Step 1: list spring-boot-starter-oauth2-client as required (it is unconditionally wired into the security chain) and pin spring-retry to 2.0.12 to match what the library is built against. - Step 4: clarify that mail config is genuinely optional now, with email-dependent features degrading to a warning. - Step 9: clarify the library only ships email templates; user-facing pages must come from the demo app or be supplied by the consumer. Reported in #310 by @katharinebrinker.
Code Review: PR #311 — Make mail truly optional & clarify quick-start docsGood overall direction — these are real usability pain points for new adopters. The ✅ What's Working Well
🔴 Must Fix1. In
This property does not exist in Spring Boot or Spring Security. There is no single flag to disable OAuth2 auto-configuration via that key. In this library, the relevant toggles live under 🟡 Should Fix2. PII logged at WARN level on every skipped send // MailService.java — resolveMailSender()
log.warn("Email send to '{}' skipped: JavaMailSender is not configured...", to);This logs the recipient's email address at WARN level every time a send is attempted without a configured sender. WARN is typically captured by production log aggregators, which makes this a GDPR/PII concern. The debug-level logs (
3. Warning fires on every call, not once at startup Related to #2 — if an app runs without mail configured and thousands of users register, the log gets flooded with the same warning. A @PostConstruct
private void init() {
this.resolvedSender = mailSenderProvider.getIfAvailable();
if (resolvedSender == null) {
log.warn("JavaMailSender is not configured — email sending is disabled. Set 'spring.mail.host' to enable.");
}
}Then 🔵 Minor / Nice-to-Have4. Tests don't verify the warning was logged The two new tests verify the no-op behavior correctly, but neither asserts that a warning was actually emitted. This is non-critical (the no-op is what matters for behavior), but if the 5. Pinning 6. When the sender is null, the method returns before any Summary
Fix #1 before merge; #2+#3 are strongly recommended given this library's GDPR positioning. The rest can be follow-ups. |
There was a problem hiding this comment.
Pull request overview
This PR smooths out quick-start integration by documenting required dependencies and making outbound email sending degrade gracefully when mail isn’t configured, aligning runtime behavior with the README.
Changes:
- Refactor
MailServiceto lazily resolveJavaMailSenderviaObjectProviderand no-op (with a warning) when absent. - Update
MailServiceTestto use the new constructor and add coverage for the “no sender configured” path. - Clarify README quick-start dependencies (OAuth2 client + pinned
spring-retry) and correct guidance around user-facing templates.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/main/java/com/digitalsanctuary/spring/user/mail/MailService.java | Make mail sender optional via ObjectProvider; skip sends when unavailable. |
| src/test/java/com/digitalsanctuary/spring/user/mail/MailServiceTest.java | Update wiring for new constructor; add tests for missing JavaMailSender. |
| README.md | Document OAuth2 and spring-retry version; clarify optional mail behavior and template expectations. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private JavaMailSender resolveMailSender(String to) { | ||
| JavaMailSender sender = mailSenderProvider.getIfAvailable(); | ||
| if (sender == null) { | ||
| log.warn("Email send to '{}' skipped: JavaMailSender is not configured. Set 'spring.mail.host' to enable email sending.", to); | ||
| } |
- MailService: switch to @PostConstruct to cache JavaMailSender once at startup; warning logged a single time without PII instead of per-call with recipient address (GDPR/noise concern raised by Copilot review). - MailServiceTest: call init() after construction to simulate @PostConstruct; no-sender tests use a dedicated service instance. - README: clarify spring.security.oauth2.enabled is a framework property (not a standard Spring Security key) and is opt-in only.
Summary
Addresses the four speed bumps reported in #310:
OAuth2 dependency was undocumented.
spring-boot-starter-oauth2-clientis unconditionally wired into the security chain (WebSecurityConfiginjectsDSOAuth2UserService/DSOidcUserServiceas required constructor params). README now lists it in the required dependencies.spring-retryversion resolution. Spring Boot's BOM doesn't always managespring-retry; pin to2.0.12to match what the library is built against (build.gradle:52).Mail config wasn't actually optional.
MailServicehadJavaMailSenderas a hard constructor dependency, so apps withoutspring.mail.hostset wouldn't start. Refactored to takeObjectProvider<JavaMailSender>— when the bean is absent, send operations log a warning and no-op. README Step 4 updated to describe the degraded behavior.No user-facing templates ship. The library ships only
templates/mail/(email templates). README Step 9 used to claim "default HTML templates" existed; updated to direct users to the demo app's templates dir.Changes
src/main/java/com/digitalsanctuary/spring/user/mail/MailService.java— switch toObjectProvider<JavaMailSender>; addresolveMailSender()helper that returns null + warns when absent; bail out of send methods when no sender available.src/test/java/com/digitalsanctuary/spring/user/mail/MailServiceTest.java— update fixture for new constructor; add two new tests covering the no-sender no-op path.README.md— Step 1 deps (OAuth2 + retry version + explanatory notes), Step 4 mail (truly optional now), Step 9 templates (link to demo, list the files).Test plan
./gradlew test --tests "*MailServiceTest"— all 24 tests pass (2 new)./gradlew test --tests "*UserEmailServiceTest" --tests "*RegistrationListenerTest"— pass (downstream callers unaffected)./gradlew test— only pre-existing failures remain (LogoutSuccessServiceTestMockito strict-stubbing onRefererheader, unrelated to this PR)Closes #310.