Skip to content

MX 241: harden SMTP fallback and notification delivery reliability#150

Merged
IOhacker merged 1 commit intoopenMF:developfrom
DeathGun44:MX-241-harden-notification-smtp-fallback
Apr 15, 2026
Merged

MX 241: harden SMTP fallback and notification delivery reliability#150
IOhacker merged 1 commit intoopenMF:developfrom
DeathGun44:MX-241-harden-notification-smtp-fallback

Conversation

@DeathGun44
Copy link
Copy Markdown
Contributor

@DeathGun44 DeathGun44 commented Apr 15, 2026

Summary by CodeRabbit

  • New Features

    • New unit and integration tests covering SMTP fallback, cooldown, and notification flows.
  • Improvements

    • SMTP handling: robust fallback to configured properties, stricter validation, and reduced noisy debug output.
    • Notification templates: smarter name fallbacks and conditional timestamp rendering.
    • SMS provider selection is configurable and deterministic.
    • Notification logs omit sensitive fields and suppress repeated SMTP warnings after the first occurrence.
    • Cooldown cache expired-entry replacement optimized.
  • Bug Fixes

    • Prefer first IP from X-Forwarded-For for client IP detection.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 15, 2026

Warning

Rate limit exceeded

@DeathGun44 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 47 minutes and 46 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 47 minutes and 46 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 206dd283-cc62-4c36-982f-20125f46253f

📥 Commits

Reviewing files that changed from the base of the PR and between 9aedb92 and bdf969d.

📒 Files selected for processing (18)
  • src/main/java/org/apache/fineract/infrastructure/core/service/SelfServicePluginEmailService.java
  • src/main/java/org/apache/fineract/infrastructure/core/service/SmtpConfigurationUnavailableException.java
  • src/main/java/org/apache/fineract/selfservice/notification/NotificationCooldownCache.java
  • src/main/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationEvent.java
  • src/main/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationService.java
  • src/main/java/org/apache/fineract/selfservice/notification/starter/SelfServiceNotificationConfig.java
  • src/main/java/org/apache/fineract/selfservice/registration/service/SelfServiceForgotPasswordWritePlatformServiceImpl.java
  • src/main/java/org/apache/fineract/selfservice/registration/service/SelfServiceRegistrationWritePlatformServiceImpl.java
  • src/main/java/org/apache/fineract/selfservice/security/api/SelfAuthenticationApiResource.java
  • src/main/resources/notification/templates/html/login-failure.html
  • src/main/resources/notification/templates/html/login-success.html
  • src/main/resources/notification/templates/text/login-failure.txt
  • src/main/resources/notification/templates/text/login-success.txt
  • src/test/java/org/apache/fineract/infrastructure/core/service/SelfServicePluginEmailServiceTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/NotificationCooldownCacheTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationSmtpIntegrationTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/SelfServiceSmtpFallbackIntegrationTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationServiceTest.java
📝 Walkthrough

Walkthrough

Centralizes SMTP credential resolution with DB-first and Spring-property fallback, adds SmtpConfigurationUnavailableException, adjusts notification event null-safety and cooldown logic, refactors notification sending and SMS provider selection, updates templates for null-safe defaults, and adds unit and integration tests covering fallback and logging behavior.

Changes

Cohort / File(s) Summary
SMTP Configuration & Email Service
src/main/java/org/apache/fineract/infrastructure/core/service/SelfServicePluginEmailService.java, src/main/java/org/apache/fineract/infrastructure/core/service/SmtpConfigurationUnavailableException.java
Injects Spring Environment, adds resolveSmtpCredentials() and centralized configureMailSender(), implements DB-first then fineract.selfservice.smtp.* fallback with one-time WARN (AtomicBoolean), validates/normalizes port (default 587), conditionally enables STARTTLS, removes hardcoded SSL/mail.debug properties, and adds runtime exception when required config missing.
Notification Service & Event Handling
src/main/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationService.java, src/main/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationEvent.java, src/main/java/org/apache/fineract/selfservice/notification/NotificationCooldownCache.java
Replaced Lombok ctor with explicit ctor; added @Transactional on handleNotification; introduced helper email/SMS send methods; deterministic SMS provider selection via env property and WARN on missing provider; added SMTP-config-specific handling that releases cooldown and logs once; event constructor enforces non-null type/userId and a non-PII toString(); cooldown now overwrites stale entries directly.
Notification Configuration
src/main/java/org/apache/fineract/selfservice/notification/starter/SelfServiceNotificationConfig.java
Makes cooldown max-size configurable via fineract.selfservice.notification.cooldown-max-size (default 10_000); refactors NotificationExecutorProperties to Lombok getters/setters and adds @Min validation on pool/queue fields.
API & Security
src/main/java/org/apache/fineract/selfservice/security/api/SelfAuthenticationApiResource.java
Removed unused local variable; extractClientIp() now prefers first IP from X-Forwarded-For header (trimmed) and falls back to getRemoteAddr().
Templates
src/main/resources/notification/templates/.../login-success.html, .../login-failure.html, .../login-success.txt, .../login-failure.txt
Make greetings null-safe (firstName defaults), conditionally render eventTimestamp only if present, and expand login-failure guidance messaging.
Unit Tests
src/test/java/org/apache/fineract/infrastructure/core/service/SelfServicePluginEmailServiceTest.java, src/test/java/org/apache/fineract/selfservice/notification/NotificationCooldownCacheTest.java, src/test/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationServiceTest.java
Adds tests for SMTP resolution paths (DB success, DB failure with Spring fallback, missing/incomplete config), cooldown TTL/eviction, notification flows (email/SMS, cooldown, feature flags), and log-once assertions using Logback ListAppender.
Integration Tests
src/test/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationSmtpIntegrationTest.java, src/test/java/org/apache/fineract/selfservice/notification/SelfServiceSmtpFallbackIntegrationTest.java
Adds Testcontainers and Spring integration tests validating enrollment/login flows when SMTP DB table is missing, repeated logins under SMTP unavailability, async completion listener for notification handling, and log-once suppression behavior.

Sequence Diagram(s)

sequenceDiagram
    participant NSvc as SelfServiceNotificationService
    participant SSvc as SelfServicePluginEmailService
    participant DB as Database
    participant Env as Spring\ Environment
    participant Mail as JavaMailSender

    NSvc->>SSvc: sendFormattedEmail(event, template, ctx)
    SSvc->>SSvc: resolveSmtpCredentials()
    rect rgba(100,149,237,0.5)
        SSvc->>DB: externalServicesReadPlatformService.getSMTPCredentials()
        alt DB returns credentials
            DB-->>SSvc: SMTPCredentialsData
        else DB throws DataAccessException
            DB--x SSvc: Exception
            SSvc->>Env: Read fineract.selfservice.smtp.* properties
            Env-->>SSvc: env properties
            SSvc->>SSvc: validate host & from-email
            alt required fields present
                SSvc-->>SSvc: build SMTPCredentialsData
            else missing required fields
                SSvc-->>NSvc: throw SmtpConfigurationUnavailableException
            end
        end
    end
    alt SMTP configured
        SSvc->>SSvc: configureMailSender(SMTPCredentialsData)
        SSvc->>Mail: send(message)
        Mail-->>SSvc: send result
        SSvc-->>NSvc: return/send status
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • IOhacker
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'MX 241: harden SMTP fallback and notification delivery reliability' clearly and specifically describes the main objective of the changeset, which involves hardening SMTP configuration fallback mechanisms and improving notification delivery reliability through various code improvements across multiple files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (6)
src/main/java/org/apache/fineract/selfservice/security/api/SelfAuthenticationApiResource.java (1)

251-253: Guard X-Forwarded-For usage behind a trusted-proxy boundary.

At Line 252-Line 253, the header is accepted unconditionally. A direct client can spoof it, which can corrupt notification/audit IP data. Only use forwarded headers when the request passed through trusted proxy infrastructure.

As per coding guidelines, "Validate and sanitize all incoming data (use Bean Validation with @Valid and constraints)."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/apache/fineract/selfservice/security/api/SelfAuthenticationApiResource.java`
around lines 251 - 253, The code unconditionally trusts the X-Forwarded-For
header in SelfAuthenticationApiResource; restrict use to requests that came
through configured trusted proxies (e.g., check remote address against a
trusted-proxies config or a helper like isTrustedProxy(remoteAddr)) and only
then parse X-Forwarded-For. When using the header, split and trim the first
entry, validate/sanitize it using a proper IP parse (e.g., InetAddress
resolution) and catch parse exceptions; if validation fails or the request is
not from a trusted proxy, fall back to httpRequest.getRemoteAddr(). Ensure any
config-driven trusted-proxies list is consulted and validated before trusting
the header.
src/main/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationService.java (1)

99-99: Use import statement for @Transactional annotation.

Other annotations in this class (@Slf4j, @Service, @Async, @EventListener) use imports. The fully qualified @org.springframework.transaction.annotation.Transactional is inconsistent.

Proposed fix

Add to imports:

import org.springframework.transaction.annotation.Transactional;

Then change line 99:

-@org.springframework.transaction.annotation.Transactional
+@Transactional
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationService.java`
at line 99, Replace the fully-qualified annotation usage with an import: add
"import org.springframework.transaction.annotation.Transactional;" to the
imports and change the annotation on SelfServiceNotificationService from
"@org.springframework.transaction.annotation.Transactional" to "@Transactional"
to match the style of other annotations like `@Slf4j`, `@Service`, `@Async`, and
`@EventListener`.
src/main/java/org/apache/fineract/infrastructure/core/service/SelfServicePluginEmailService.java (2)

64-68: Add final modifier to the env parameter for consistency.

The first constructor parameter has final but env does not. Per coding guidelines, constructor-injected dependencies should be marked final.

Proposed fix
 `@Autowired`
 public SelfServicePluginEmailService(final ExternalServicesPropertiesReadPlatformService externalServicesReadPlatformService,
-                                     org.springframework.core.env.Environment env) {
+                                     final org.springframework.core.env.Environment env) {
     this.externalServicesReadPlatformService = externalServicesReadPlatformService;
     this.env = env;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/apache/fineract/infrastructure/core/service/SelfServicePluginEmailService.java`
around lines 64 - 68, Constructor parameter consistency: mark the env parameter
as final in the SelfServicePluginEmailService constructor to match the first
parameter and coding guidelines; update the constructor signature (public
SelfServicePluginEmailService(final
ExternalServicesPropertiesReadPlatformService
externalServicesReadPlatformService, final
org.springframework.core.env.Environment env)) so env is declared final and no
other changes are needed.

224-229: Simplify TLS configuration to use STARTTLS only.

Combining mail.smtp.starttls.enable=true with explicit SSLSocketFactory is not aligned with modern JavaMail best practices. Official documentation indicates SSLSocketFactory is rarely needed. Use STARTTLS properties alone:

Proposed fix
 if (smtpCredentialsData.isUseTLS()) {
     props.put("mail.smtp.starttls.enable", "true");
-    props.put("mail.smtp.socketFactory.port", Integer.toString(port));
-    props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); // NOSONAR
-    props.put("mail.smtp.socketFactory.fallback", "true");
+    props.put("mail.smtp.starttls.required", "true");
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/apache/fineract/infrastructure/core/service/SelfServicePluginEmailService.java`
around lines 224 - 229, In the smtpCredentialsData.isUseTLS() block inside
SelfServicePluginEmailService, remove the explicit SSL socket factory properties
("mail.smtp.socketFactory.port", "mail.smtp.socketFactory.class",
"mail.smtp.socketFactory.fallback") and leave only the STARTTLS setting (e.g.,
set "mail.smtp.starttls.enable" to "true"; optionally set
"mail.smtp.starttls.required" if you want to enforce it) so the code uses
STARTTLS per JavaMail best practices instead of forcing an SSLSocketFactory.
src/test/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationServiceTest.java (1)

79-83: Consider using imports instead of fully qualified class names.

Fully qualified class names for ThreadLocalContextUtil, FineractPlatformTenant, BusinessDateType, and LocalDate make the code verbose. Adding explicit imports would improve readability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationServiceTest.java`
around lines 79 - 83, Replace the verbose fully-qualified usages with imports:
add import statements for ThreadLocalContextUtil, FineractPlatformTenant,
BusinessDateType and LocalDate at the top of the test file and then update the
occurrences (the ThreadLocalContextUtil.setTenant call, new
FineractPlatformTenant(...), BusinessDateType.BUSINESS_DATE, and
java.time.LocalDate.now()) to use the short class names; this will remove the
long package prefixes and improve readability while keeping behavior unchanged.
src/test/java/org/apache/fineract/selfservice/notification/SelfServiceSmtpFallbackIntegrationTest.java (1)

271-286: Completion listener relies on sleep-based ordering.

The 100ms sleep to "ensure the primary handler runs first" is timing-dependent and may be flaky under high load. Consider using a more deterministic synchronization mechanism.

However, this is acceptable for integration tests where exact ordering is less critical than completion detection.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/org/apache/fineract/selfservice/notification/SelfServiceSmtpFallbackIntegrationTest.java`
around lines 271 - 286, The onNotification method currently uses
Thread.sleep(100) to try to order handlers which is flaky; instead create a
deterministic synchronization primitive (e.g., a CountDownLatch or
CompletableFuture) named something like primaryHandlerDone and have the primary
notification handler signal primaryHandlerDone when it finishes; then in this
fallback/onNotification listener wait on primaryHandlerDone (with a timeout)
before calling latch.countDown(). Also update awaitCompletion if needed to
account for the new signal, and reference the `@Async`("notificationExecutor")
listener method onNotification, the existing latch, and the
SelfServiceNotificationEvent to locate where to add/wait on the
primaryHandlerDone signal.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationService.java`:
- Around line 189-197: The SMS sending block saves messages as PENDING but never
marks them SENT on success; after calling
smsScheduledJobService.sendTriggeredMessage(...) successfully, set
smsMessage.setStatusType(SmsMessageStatusType.SENT.getValue()) and persist via
smsMessageRepository.save(smsMessage); apply the same fix in
SelfServiceRegistrationWritePlatformServiceImpl and
SelfServiceForgotPasswordWritePlatformServiceImpl, and ensure any asynchronous
gateway/webhook handlers can still override the SENT state if they provide later
delivery/failure updates.

In
`@src/main/java/org/apache/fineract/selfservice/notification/starter/SelfServiceNotificationConfig.java`:
- Around line 194-206: The NotificationExecutorProperties class annotated with
`@ConfigurationProperties` contains `@jakarta.validation.constraints.Min`
annotations but lacks `@Validated` so constraints won't be enforced; add the
Spring validation annotation
(org.springframework.validation.annotation.Validated) to the
NotificationExecutorProperties class declaration (keeping the existing
`@ConfigurationProperties`, `@lombok.Getter` and `@lombok.Setter`) so Spring Boot will
apply the Min constraints during binding, and add the corresponding import.

In
`@src/main/java/org/apache/fineract/selfservice/security/api/SelfAuthenticationApiResource.java`:
- Around line 251-255: The extracted X-Forwarded-For token can be empty (e.g. ",
10.0.0.1"); update the logic in SelfAuthenticationApiResource where
xForwardedFor is processed: after getting xForwardedFor from httpRequest, split
on "," and take the first token, trim it, then check
StringUtils.isNotBlank(firstToken) before returning it; if the trimmed
firstToken is blank, fall back to httpRequest.getRemoteAddr(). Ensure you
reference the xForwardedFor variable and httpRequest.getRemoteAddr() in the fix.

In
`@src/test/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationSmtpIntegrationTest.java`:
- Around line 200-207: The current assertion in
SelfServiceNotificationSmtpIntegrationTest that checks health.getStatusCode() <
600 is too permissive; replace it with a stricter check that the health response
is a successful HTTP status (e.g., verify status is 200 or in the 2xx range).
Locate the variable health and the call to
get("/fineract-provider/actuator/health") and change the assertion to assert
that health.getStatusCode() is between 200 and 299 (or equals 200) and keep the
existing message, so the test fails on 5xx server errors rather than passing
them.

---

Nitpick comments:
In
`@src/main/java/org/apache/fineract/infrastructure/core/service/SelfServicePluginEmailService.java`:
- Around line 64-68: Constructor parameter consistency: mark the env parameter
as final in the SelfServicePluginEmailService constructor to match the first
parameter and coding guidelines; update the constructor signature (public
SelfServicePluginEmailService(final
ExternalServicesPropertiesReadPlatformService
externalServicesReadPlatformService, final
org.springframework.core.env.Environment env)) so env is declared final and no
other changes are needed.
- Around line 224-229: In the smtpCredentialsData.isUseTLS() block inside
SelfServicePluginEmailService, remove the explicit SSL socket factory properties
("mail.smtp.socketFactory.port", "mail.smtp.socketFactory.class",
"mail.smtp.socketFactory.fallback") and leave only the STARTTLS setting (e.g.,
set "mail.smtp.starttls.enable" to "true"; optionally set
"mail.smtp.starttls.required" if you want to enforce it) so the code uses
STARTTLS per JavaMail best practices instead of forcing an SSLSocketFactory.

In
`@src/main/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationService.java`:
- Line 99: Replace the fully-qualified annotation usage with an import: add
"import org.springframework.transaction.annotation.Transactional;" to the
imports and change the annotation on SelfServiceNotificationService from
"@org.springframework.transaction.annotation.Transactional" to "@Transactional"
to match the style of other annotations like `@Slf4j`, `@Service`, `@Async`, and
`@EventListener`.

In
`@src/main/java/org/apache/fineract/selfservice/security/api/SelfAuthenticationApiResource.java`:
- Around line 251-253: The code unconditionally trusts the X-Forwarded-For
header in SelfAuthenticationApiResource; restrict use to requests that came
through configured trusted proxies (e.g., check remote address against a
trusted-proxies config or a helper like isTrustedProxy(remoteAddr)) and only
then parse X-Forwarded-For. When using the header, split and trim the first
entry, validate/sanitize it using a proper IP parse (e.g., InetAddress
resolution) and catch parse exceptions; if validation fails or the request is
not from a trusted proxy, fall back to httpRequest.getRemoteAddr(). Ensure any
config-driven trusted-proxies list is consulted and validated before trusting
the header.

In
`@src/test/java/org/apache/fineract/selfservice/notification/SelfServiceSmtpFallbackIntegrationTest.java`:
- Around line 271-286: The onNotification method currently uses
Thread.sleep(100) to try to order handlers which is flaky; instead create a
deterministic synchronization primitive (e.g., a CountDownLatch or
CompletableFuture) named something like primaryHandlerDone and have the primary
notification handler signal primaryHandlerDone when it finishes; then in this
fallback/onNotification listener wait on primaryHandlerDone (with a timeout)
before calling latch.countDown(). Also update awaitCompletion if needed to
account for the new signal, and reference the `@Async`("notificationExecutor")
listener method onNotification, the existing latch, and the
SelfServiceNotificationEvent to locate where to add/wait on the
primaryHandlerDone signal.

In
`@src/test/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationServiceTest.java`:
- Around line 79-83: Replace the verbose fully-qualified usages with imports:
add import statements for ThreadLocalContextUtil, FineractPlatformTenant,
BusinessDateType and LocalDate at the top of the test file and then update the
occurrences (the ThreadLocalContextUtil.setTenant call, new
FineractPlatformTenant(...), BusinessDateType.BUSINESS_DATE, and
java.time.LocalDate.now()) to use the short class names; this will remove the
long package prefixes and improve readability while keeping behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dd1d6e3c-32a2-4a04-8401-2978acc14627

📥 Commits

Reviewing files that changed from the base of the PR and between 4b06a44 and c55cb5b.

📒 Files selected for processing (16)
  • src/main/java/org/apache/fineract/infrastructure/core/service/SelfServicePluginEmailService.java
  • src/main/java/org/apache/fineract/infrastructure/core/service/SmtpConfigurationUnavailableException.java
  • src/main/java/org/apache/fineract/selfservice/notification/NotificationCooldownCache.java
  • src/main/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationEvent.java
  • src/main/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationService.java
  • src/main/java/org/apache/fineract/selfservice/notification/starter/SelfServiceNotificationConfig.java
  • src/main/java/org/apache/fineract/selfservice/security/api/SelfAuthenticationApiResource.java
  • src/main/resources/notification/templates/html/login-failure.html
  • src/main/resources/notification/templates/html/login-success.html
  • src/main/resources/notification/templates/text/login-failure.txt
  • src/main/resources/notification/templates/text/login-success.txt
  • src/test/java/org/apache/fineract/infrastructure/core/service/SelfServicePluginEmailServiceTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/NotificationCooldownCacheTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationSmtpIntegrationTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/SelfServiceSmtpFallbackIntegrationTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationServiceTest.java
💤 Files with no reviewable changes (1)
  • src/main/java/org/apache/fineract/selfservice/notification/NotificationCooldownCache.java

@DeathGun44 DeathGun44 force-pushed the MX-241-harden-notification-smtp-fallback branch from c55cb5b to 8d61dbc Compare April 15, 2026 07:19
@DeathGun44
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
src/main/java/org/apache/fineract/infrastructure/core/service/SelfServicePluginEmailService.java (1)

63-68: Prefer Lombok constructor injection here.

This manual DI constructor adds boilerplate and is still undocumented even though it is public. @RequiredArgsConstructor would align with the project standard and remove the need to maintain this constructor by hand.

As per coding guidelines, "Use RequiredArgsConstructor for dependency injection" and "Public methods and classes MUST have Javadoc."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/apache/fineract/infrastructure/core/service/SelfServicePluginEmailService.java`
around lines 63 - 68, Replace the handwritten public constructor in
SelfServicePluginEmailService with Lombok-based constructor injection: mark the
class with `@RequiredArgsConstructor`, make the injected fields
(externalServicesReadPlatformService and env) final, remove the explicit
constructor, and add a Javadoc comment on the class (and any remaining public
methods) per guidelines; ensure lombok.RequiredArgsConstructor is imported and
the class-level Javadoc describes the service responsibility.
src/main/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationService.java (1)

70-83: Prefer @RequiredArgsConstructor for this bean.

The explicit DI constructor adds boilerplate and is public without Javadoc. Lombok constructor injection would match the project standard and keep this service easier to maintain.

As per coding guidelines, "Use RequiredArgsConstructor for dependency injection" and "Public methods and classes MUST have Javadoc."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationService.java`
around lines 70 - 83, Replace the explicit public constructor
SelfServiceNotificationService(...) with Lombok constructor injection: mark the
class SelfServiceNotificationService with `@RequiredArgsConstructor`, make all
injected fields final (notificationTemplateEngine, notificationMessageSource,
emailService, smsMessageRepository, smsScheduledJobService, smsProviderService,
notificationCooldownCache, env), and remove the manual constructor; also add the
required Javadoc for the public class (and any public methods) to comply with
project guidelines.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/java/org/apache/fineract/infrastructure/core/service/SelfServicePluginEmailService.java`:
- Around line 155-166: resolveSmtpCredentials() must not propagate
SmtpConfigurationUnavailableException because other callers of
sendToUserAccount()/sendDefinedEmail() expect email failures to degrade
gracefully; change resolveSmtpCredentials() so it catches
SmtpConfigurationUnavailableException (in addition to DataAccessException), log
exactly as the current DataAccessException path using smtpFallbackLogged and
then return buildCredentialsFromEnvironment(exception) instead of letting the
SmtpConfigurationUnavailableException escape; this keeps existing callers
(registration, forgot-password, user-creation) from rolling back while
SelfServiceNotificationService can still handle the specific exception if
desired.
- Around line 201-210: The port parsing currently only catches non-numeric
input; after parsing portStr via smtpCredentialsData.getPort() into port,
validate that port is within the valid TCP/UDP range (1–65535); if parsed value
is out of range (<=0 or >65535) treat it as invalid, log a warning similar to
the NumberFormatException case (including portStr), and fall back to the default
587 before calling mailSender.setPort(port). Ensure you still catch
NumberFormatException from Integer.parseInt and handle both cases consistently.

In
`@src/main/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationService.java`:
- Around line 145-149: The cooldown entry acquired in handleNotification is not
released when delivery is skipped, so update sendEmailNotification (and the
analogous early-return paths indicated around lines 159-166 and 182-184) to call
the cooldown-release/invalidator before every early return: locate the method
that acquires the cooldown in handleNotification and use its corresponding
release method (e.g., releaseCooldown(event) or cooldownCache.invalidate(key))
immediately prior to logging and returning from sendEmailNotification and the
other skip branches (missing email/sms or missing provider config) so the cache
entry is removed if no delivery attempt is made.
- Around line 150-155: The code builds an HTML template ("html/...") via
notificationTemplateEngine.process but sends it with
emailService.sendDefinedEmail which uses SimpleMailMessage (plain text), causing
raw HTML to be received; update SelfServiceNotificationService to send the
rendered HTML using the email service's MIME/HTML-capable API (e.g., call
emailService.sendHtmlEmail or a sendMimeMessage variant) or set the EmailDetail
to indicate HTML content if the service supports it, replacing
sendDefinedEmail(...) with the HTML/MIME send method so the HTML body is
delivered as formatted mail.

---

Nitpick comments:
In
`@src/main/java/org/apache/fineract/infrastructure/core/service/SelfServicePluginEmailService.java`:
- Around line 63-68: Replace the handwritten public constructor in
SelfServicePluginEmailService with Lombok-based constructor injection: mark the
class with `@RequiredArgsConstructor`, make the injected fields
(externalServicesReadPlatformService and env) final, remove the explicit
constructor, and add a Javadoc comment on the class (and any remaining public
methods) per guidelines; ensure lombok.RequiredArgsConstructor is imported and
the class-level Javadoc describes the service responsibility.

In
`@src/main/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationService.java`:
- Around line 70-83: Replace the explicit public constructor
SelfServiceNotificationService(...) with Lombok constructor injection: mark the
class SelfServiceNotificationService with `@RequiredArgsConstructor`, make all
injected fields final (notificationTemplateEngine, notificationMessageSource,
emailService, smsMessageRepository, smsScheduledJobService, smsProviderService,
notificationCooldownCache, env), and remove the manual constructor; also add the
required Javadoc for the public class (and any public methods) to comply with
project guidelines.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5219ff87-cce1-4bf1-bb1e-cb6711cb4908

📥 Commits

Reviewing files that changed from the base of the PR and between c55cb5b and 8d61dbc.

📒 Files selected for processing (16)
  • src/main/java/org/apache/fineract/infrastructure/core/service/SelfServicePluginEmailService.java
  • src/main/java/org/apache/fineract/infrastructure/core/service/SmtpConfigurationUnavailableException.java
  • src/main/java/org/apache/fineract/selfservice/notification/NotificationCooldownCache.java
  • src/main/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationEvent.java
  • src/main/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationService.java
  • src/main/java/org/apache/fineract/selfservice/notification/starter/SelfServiceNotificationConfig.java
  • src/main/java/org/apache/fineract/selfservice/security/api/SelfAuthenticationApiResource.java
  • src/main/resources/notification/templates/html/login-failure.html
  • src/main/resources/notification/templates/html/login-success.html
  • src/main/resources/notification/templates/text/login-failure.txt
  • src/main/resources/notification/templates/text/login-success.txt
  • src/test/java/org/apache/fineract/infrastructure/core/service/SelfServicePluginEmailServiceTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/NotificationCooldownCacheTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationSmtpIntegrationTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/SelfServiceSmtpFallbackIntegrationTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationServiceTest.java
💤 Files with no reviewable changes (1)
  • src/main/java/org/apache/fineract/selfservice/notification/NotificationCooldownCache.java
✅ Files skipped from review due to trivial changes (5)
  • src/main/resources/notification/templates/text/login-failure.txt
  • src/main/resources/notification/templates/html/login-failure.html
  • src/main/java/org/apache/fineract/infrastructure/core/service/SmtpConfigurationUnavailableException.java
  • src/test/java/org/apache/fineract/infrastructure/core/service/SelfServicePluginEmailServiceTest.java
  • src/main/java/org/apache/fineract/selfservice/notification/starter/SelfServiceNotificationConfig.java
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/main/resources/notification/templates/html/login-success.html
  • src/main/resources/notification/templates/text/login-success.txt
  • src/test/java/org/apache/fineract/selfservice/notification/NotificationCooldownCacheTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationSmtpIntegrationTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationServiceTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/SelfServiceSmtpFallbackIntegrationTest.java

@DeathGun44 DeathGun44 force-pushed the MX-241-harden-notification-smtp-fallback branch from 8d61dbc to 9aedb92 Compare April 15, 2026 08:50
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/main/java/org/apache/fineract/infrastructure/core/service/SelfServicePluginEmailService.java (1)

233-245: Consider conditional SMTP authentication based on credentials presence.

mail.smtp.auth is unconditionally set to "true", but when username is empty (which is allowed by buildCredentialsFromEnvironment), some SMTP servers may reject the connection or behave unexpectedly. Consider enabling authentication only when credentials are provided.

Proposed fix
         Properties props = mailSender.getJavaMailProperties();
         props.put("mail.transport.protocol", "smtp");
-        props.put("mail.smtp.auth", "true");
+        
+        String username = smtpCredentialsData.getUsername();
+        if (username != null && !username.isEmpty()) {
+            props.put("mail.smtp.auth", "true");
+        } else {
+            props.put("mail.smtp.auth", "false");
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/apache/fineract/infrastructure/core/service/SelfServicePluginEmailService.java`
around lines 233 - 245, The code currently unconditionally sets "mail.smtp.auth"
to "true" in SelfServicePluginEmailService when configuring JavaMail properties;
change this to enable SMTP auth only when credentials are present by checking
smtpCredentialsData.getUsername() (or whatever accessor returns the configured
username from buildCredentialsFromEnvironment) and only
put("mail.smtp.auth","true") when username is non-empty/non-null, otherwise omit
or set to "false"; keep the existing TLS handling
(smtpCredentialsData.isUseTLS()) and mail.debug logic unchanged.
src/main/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationService.java (1)

144-145: Potentially unreachable catch block.

The catch block for SmtpConfigurationUnavailableException at line 144-145 appears unreachable. sendFormattedEmail wraps this exception in PlatformEmailSendException before throwing (see SelfServicePluginEmailService lines 106-110), so the wrapped exception path at lines 136-143 handles this case.

Consider removing the redundant catch block
         } catch (org.apache.fineract.infrastructure.core.service.PlatformEmailSendException emailEx) {
             if (emailEx.getCause() instanceof SmtpConfigurationUnavailableException) {
                 handleSmtpConfigError(event, (SmtpConfigurationUnavailableException) emailEx.getCause());
             } else {
                 String cacheKey = event.getType().name() + ":" + event.getUserId();
                 notificationCooldownCache.release(cacheKey);
                 log.error("Failed to handle notification for event type {}", event.getType(), emailEx);
             }
-        } catch (SmtpConfigurationUnavailableException configEx) {
-            handleSmtpConfigError(event, configEx);
         } catch (Exception e) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationService.java`
around lines 144 - 145, The catch block for
SmtpConfigurationUnavailableException in SelfServiceNotificationService is
redundant because sendFormattedEmail (implemented in
SelfServicePluginEmailService) wraps that exception inside
PlatformEmailSendException; either remove the specific
SmtpConfigurationUnavailableException catch or change the exception handling to
catch PlatformEmailSendException first and inspect/unwrap its cause to call
handleSmtpConfigError when the cause is a SmtpConfigurationUnavailableException;
update the code paths around sendFormattedEmail, PlatformEmailSendException,
handleSmtpConfigError and handleEmailSendError accordingly so only the
appropriate handler runs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/test/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationServiceTest.java`:
- Around line 259-260: The mock currently throws
SmtpConfigurationUnavailableException directly from the
emailService.sendFormattedEmail stub in SelfServiceNotificationServiceTest,
causing a mismatch with expected behavior; change the mocked behavior to throw a
PlatformEmailSendException that wraps the SmtpConfigurationUnavailableException
(i.e., construct a new PlatformEmailSendException with the
SmtpConfigurationUnavailableException as its cause) when stubbing
emailService.sendFormattedEmail so the test aligns with how the production code
surfaces email errors.
- Around line 243-250: The test currently mocks emailService.sendFormattedEmail
to throw SmtpConfigurationUnavailableException directly, but in production
SelfServicePluginEmailService wraps that in PlatformEmailSendException; update
the mock in SelfServiceNotificationServiceTest so that doThrow(...) throws a
PlatformEmailSendException whose cause is a new
SmtpConfigurationUnavailableException("SMTP not configured") when
emailService.sendFormattedEmail(any(EmailDetail.class)) is invoked, then run the
same service.handleNotification(emailEvent()) and
verify(cooldownCache).release("LOGIN_SUCCESS:1") as before.

---

Nitpick comments:
In
`@src/main/java/org/apache/fineract/infrastructure/core/service/SelfServicePluginEmailService.java`:
- Around line 233-245: The code currently unconditionally sets "mail.smtp.auth"
to "true" in SelfServicePluginEmailService when configuring JavaMail properties;
change this to enable SMTP auth only when credentials are present by checking
smtpCredentialsData.getUsername() (or whatever accessor returns the configured
username from buildCredentialsFromEnvironment) and only
put("mail.smtp.auth","true") when username is non-empty/non-null, otherwise omit
or set to "false"; keep the existing TLS handling
(smtpCredentialsData.isUseTLS()) and mail.debug logic unchanged.

In
`@src/main/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationService.java`:
- Around line 144-145: The catch block for SmtpConfigurationUnavailableException
in SelfServiceNotificationService is redundant because sendFormattedEmail
(implemented in SelfServicePluginEmailService) wraps that exception inside
PlatformEmailSendException; either remove the specific
SmtpConfigurationUnavailableException catch or change the exception handling to
catch PlatformEmailSendException first and inspect/unwrap its cause to call
handleSmtpConfigError when the cause is a SmtpConfigurationUnavailableException;
update the code paths around sendFormattedEmail, PlatformEmailSendException,
handleSmtpConfigError and handleEmailSendError accordingly so only the
appropriate handler runs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5472d5a3-69e1-4b40-b1a1-84095b5e84c3

📥 Commits

Reviewing files that changed from the base of the PR and between 8d61dbc and 9aedb92.

📒 Files selected for processing (16)
  • src/main/java/org/apache/fineract/infrastructure/core/service/SelfServicePluginEmailService.java
  • src/main/java/org/apache/fineract/infrastructure/core/service/SmtpConfigurationUnavailableException.java
  • src/main/java/org/apache/fineract/selfservice/notification/NotificationCooldownCache.java
  • src/main/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationEvent.java
  • src/main/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationService.java
  • src/main/java/org/apache/fineract/selfservice/notification/starter/SelfServiceNotificationConfig.java
  • src/main/java/org/apache/fineract/selfservice/security/api/SelfAuthenticationApiResource.java
  • src/main/resources/notification/templates/html/login-failure.html
  • src/main/resources/notification/templates/html/login-success.html
  • src/main/resources/notification/templates/text/login-failure.txt
  • src/main/resources/notification/templates/text/login-success.txt
  • src/test/java/org/apache/fineract/infrastructure/core/service/SelfServicePluginEmailServiceTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/NotificationCooldownCacheTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationSmtpIntegrationTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/SelfServiceSmtpFallbackIntegrationTest.java
  • src/test/java/org/apache/fineract/selfservice/notification/service/SelfServiceNotificationServiceTest.java
💤 Files with no reviewable changes (1)
  • src/main/java/org/apache/fineract/selfservice/notification/NotificationCooldownCache.java
✅ Files skipped from review due to trivial changes (6)
  • src/main/resources/notification/templates/text/login-failure.txt
  • src/main/resources/notification/templates/text/login-success.txt
  • src/main/resources/notification/templates/html/login-failure.html
  • src/main/java/org/apache/fineract/infrastructure/core/service/SmtpConfigurationUnavailableException.java
  • src/main/java/org/apache/fineract/selfservice/notification/SelfServiceNotificationEvent.java
  • src/test/java/org/apache/fineract/selfservice/notification/SelfServiceSmtpFallbackIntegrationTest.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/resources/notification/templates/html/login-success.html
  • src/main/java/org/apache/fineract/selfservice/security/api/SelfAuthenticationApiResource.java
  • src/main/java/org/apache/fineract/selfservice/notification/starter/SelfServiceNotificationConfig.java

@DeathGun44 DeathGun44 force-pushed the MX-241-harden-notification-smtp-fallback branch from 9aedb92 to 71b8ae8 Compare April 15, 2026 08:56
…iability

- Add graceful DataAccessException handling in SelfServicePluginEmailService
  with DB → Spring properties fallback chain for SMTP credentials
- Introduce SmtpConfigurationUnavailableException for clear error signaling
- Release cooldown on config failure so retries work after fix is applied
- Implement log-once WARN → DEBUG suppression for repeated SMTP errors
- Respect useTLS flag instead of hardcoding TLS properties
- Add try-catch for invalid SMTP port with fallback to default 587
- Parse X-Forwarded-For header for accurate client IP in notifications
- Remove unused loggedInUser variable in SelfAuthenticationApiResource
- Add constructor Javadoc + Objects.requireNonNull validation on event
- Add safe non-PII toString() to SelfServiceNotificationEvent
- Fix null-safe firstName/eventTimestamp in all notification templates
- Add unauthorized login guidance to login-failure templates
- Add <head>/charset to login-failure.html email template
- Make cooldown cache max-size configurable via Spring property
- Replace manual getters/setters with Lombok in NotificationExecutorProperties
- Add @min validation constraints on executor pool properties
- Fix atomic replace in NotificationCooldownCache (remove+put → put)
- Add unit tests: EmailService, CooldownCache, NotificationService (33 tests)
- Add Spring-context integration test for SMTP fallback pipeline
- Add Testcontainers integration test for login with missing SMTP table

Resolves: MX-241
@DeathGun44 DeathGun44 force-pushed the MX-241-harden-notification-smtp-fallback branch from 71b8ae8 to bdf969d Compare April 15, 2026 09:01
@IOhacker IOhacker merged commit 46d4c7b into openMF:develop Apr 15, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants