Skip to content

Author-nudge emails: investigate IMS legacy behavior + design CAP replacement #545

Description

@jung-thomas

Background

When porting MTA resources from IMS Java to the new CAP tutorials-srv, tutorials-mail was carried over from com.sap.developers.ims. Investigation today (2026-06-22) during a DEV deploy revealed:

  1. IMS Java has the email code — EmailSenderServiceImpl, TutorialContributorsNotificationServiceImpl, daily cron at 09:00 UTC with 4 escalating notices to authors of tutorials not reviewed for 6+ months.
  2. The mail service is NOT bound to imsprod in manifest-prod.yml. The Spring code looks up JNDI mail/Session, but no mail service is bound in the production CF manifest, so the lookup fails.
  3. The feature flag isNotificationSendingAllowed is true in seed/test data — production DB state unverified.
  4. Exception path: EmailNotSentException is caught + logged but never re-thrown — silent failure in cron logs.
  5. The new tutorials-srv (CAP Node.js) has zero mail consumers — the tutorials-mail MTA resource was carried over but never wired. Removed in the 2026-06-22 deploy since the subaccount has no mail service offering anyway.

Task

  1. Verify the legacy IMS behavior: cf logs imsprod --recent | grep -iE "(email|mail|notif)" to confirm whether the cron is actually firing exceptions, or whether the feature flag in PROD DB is false so the cron exits cleanly.
  2. Decide the replacement design for the new CAP tutorials-srv:
    • Option A: SAP Cloud Mail Service (managed BTP service, if entitled to this subaccount).
    • Option B: SES / Mailgun / SendGrid via destination service.
    • Option C: GitHub workflow_dispatch — file an issue on the tutorial's source repo instead of emailing. Authors get a GitHub notification they already follow. Zero new infrastructure.
    • Option D: Skip entirely — rely on the dashboard's outdated-tutorial filter for authors to self-discover.
  3. Migrate the 4 email templates from IMS Java to whatever CAP-side templating we land on.
  4. Re-instate the daily cron under whichever option we pick (srv/jobs/scheduler.js is the host).

Acceptance criteria

  • A design doc at docs/superpowers/specs/YYYY-MM-DD-author-nudge-emails-design.md covering the 4 options plus recommendation.
  • Confirmation of whether IMS PROD is silently failing or genuinely disabled.
  • If we ship a replacement: scheduled job, templates, and tested send to a known test address before flipping the feature flag.

Out of scope

  • Marketing emails, account-activity notifications, or anything beyond the author-nudge use case.
  • Implementing the design — separate ticket once Option chosen.

References

  • Removed MTA resource: tutorials-mail (.deploy/mta.yaml pre-2026-06-22)
  • IMS code path: application/src/main/java/com/sap/developers/ims/service/email/
  • IMS cron: TutorialContributorsNotificationScheduler.java cron 0 0 9 * * ?
  • Feature flag: isNotificationSendingAllowed in ImsConfig entity
  • Recipient list: emailListForOutdated config

Activity

  1. jung-thomas commented on Jun 23, 2026

    @jung-thomas
    ContributorAuthor

    From Riley about legacy:

    It would only come from the Production system (if I understand the architecture correctly). Dmytro mentioned a Destination that makes the connection. I do see nag messages from as recently as June 21 (I get cc-ed on some of these by IMS, depending on severity).

    So we need to research further

  2. jung-thomas commented on Jun 23, 2026

    @jung-thomas
    ContributorAuthor

    Update — source-code verification (2026-06-23)

    Followed up on the BTP-destination-hookup hypothesis. The source code does not support it. Walking the IMS Java code end-to-end:

    The 3 destinations in the IMS subaccount are HTTP API destinations, not mail

    Destination Consumer Purpose
    SCI / SCI_prod SciClientImpl.java:17 via @Value("${sci.destination-name}") → application.yaml:152 SAP Cloud Identity user lookup
    NGDS NGDSSenderServiceImpl.java:70 + NGDSTokenManagerImpl.java:39 via @Value("${ngds.destination-name}") → application.yaml:238 Next Gen Developer Services accomplishment messaging

    Both consumers use SAP Cloud SDK's DestinationAccessor + HttpClientAccessor (see DestinationServiceImpl.java) — that class only fetches HttpDestination and runs GET/DELETE. It cannot service an SMTP transport. Spring Boot mail uses JavaMailSender wired through MailSenderAutoConfiguration, not through the destination service.

    The email.destination-name: mail/Session config value is dead config

    application/src/main/resources/application.yaml:272:

    email:
      destination-name: mail/Session
      default-from: developers@sap.com

    Full-tree grep:

    grep -rn '${email.destination-name}\|email.destination-name\|"mail/Session"\|mail/Session' application/src/main/
    → application/src/main/resources/application.yaml:273:  destination-name: mail/Session
    

    Zero consumers. No @Value("${email.destination-name}") exists anywhere in the Java source. The key name was carried over from old NEO-era config — mail/Session is the JNDI name of a Tomcat container-managed javax.mail.Session (a NEO Java buildpack pattern), not a BTP destination. On Cloud Foundry that JNDI name resolves to nothing.

    JavaMailSender has no production bean — only a test mock

    The only @Bean JavaMailSender in the entire IMS repo is application/src/test/java/com/sap/developers/ims/configuration/MailConfiguration.java, which Mockito.mock(...)s it. Production has:

    • No MailConfig.java / no @Bean JavaMailSender anywhere under src/main/
    • No spring.mail.* config in application.yaml (or any sibling profile file)
    • manifest-prod.yml binds only ims-hana-prod-container — no mail service, no destination-service binding

    That leaves Spring Boot's MailSenderAutoConfiguration, which only fires when spring.mail.host (or one of its siblings) is set. A grep for spring.mail across application/, web/, and approuter/ returns nothing.

    MailTestController admits it's a debug probe

    MailTestController.java is annotated with TODO: Remove this controller once application debugging is completed. — it was added precisely to test whether mail wiring worked. Hitting /test/mail on imsprod would throw NoSuchBeanDefinitionException (or fail at startup) because there is no real JavaMailSender bean to inject.

    Conclusion

    IMS author-nudge email has been silently broken since the NEO→Cloud Foundry migration. The tutorials-mail MTA resource removed on 2026-06-22 was never going to start working on the new platform either — the subaccount has no mail service offering, and the destination service can't carry SMTP credentials.

    Definitive runtime check (pending)

    A migration is running on imsprod right now. Once it completes and we can cf target it again, this command will seal the picture:

    cf env imsprod | grep -iE "mail|smtp|spring_mail"

    If that returns nothing, the cron has been throwing EmailNotSentException (caught + swallowed at the cron boundary, never re-thrown — see the original ticket body) on every fire since CF cutover.

    Next steps

    Holding off on the design doc until the cf env imsprod check confirms the static-code reading. After that, draft docs/superpowers/specs/2026-06-23-author-nudge-emails-design.md covering options A–D from the ticket.

  3. jung-thomas commented on Jun 23, 2026

    @jung-thomas
    ContributorAuthor

    Correction — runtime evidence reverses my prior reading (2026-06-23)

    Just did cf env imsprod after the migration freed the target. My previous comment was wrong — mail IS wired up in production, just not the way the IMS Java source code suggested.

    What's actually wired (and how I missed it)

    The IMS team set Spring Boot mail config directly on the app as User-Provided env vars (via cf set-env):

    SPRING_MAIL_HOST:                                 smtpauth.mail.net.sap
    SPRING_MAIL_PORT:                                 587
    SPRING_MAIL_USERNAME:                             developer-rims-notifications
    SPRING_MAIL_PASSWORD:                             <REDACTED>
    SPRING_MAIL_PROPERTIES_MAIL_SMTP_AUTH:            true
    SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_ENABLE: true
    SPRING_MAIL_PROPERTIES_MAIL_TRANSPORT_PROTOCOL:   smtp
    SPRING_MAIL_PROPERTIES_MAIL_SMTP_CONNECTIONTIMEOUT: 5000
    SPRING_MAIL_PROPERTIES_MAIL_SMTP_TIMEOUT:           5000
    SPRING_MAIL_PROPERTIES_MAIL_SMTP_WRITETIMEOUT:      5000
    

    This triggers Spring Boot's MailSenderAutoConfiguration (via relaxed binding from upper-snake-case env vars onto the spring.mail.* config keys), which materializes a real JavaMailSender bean — no @Bean definition needed in source, no service binding needed, no BTP destination needed.

    Why I missed it in the static read: these values aren't in application.yaml, aren't in any manifest-*.yml, and don't show up as a service binding. They're operator-set env on the running app, invisible to the repo. The screenshot of the 3 BTP destinations (NGDS, SCI, SCI_prod) was a red herring — mail in this stack is plain SMTP over cf set-env, not destination-mediated.

    What this means for the original ticket

    The first hypothesis in the issue body — "mail/Session JNDI lookup fails, cron silently errors" — is also wrong. There IS a working mail transport. So the relevant question for #545 shifts to: does the cron actually fire, and if so does it send? Possibilities:

    1. Working as designed: isNotificationSendingAllowed='true' in PROD DB, cron fires daily at 09:00 UTC, emails are flowing to authors, and we've just been unaware of it.
    2. Flag-disabled: isNotificationSendingAllowed='false' in PROD DB, cron exits cleanly, no emails sent regardless of SMTP working.
    3. Half-broken: cron fires, SMTP send succeeds, but recipient list is stale / emailListForOutdated empty / TutorialContributors table empty.

    cf logs imsprod --recent only covers the live tail (~minutes), not back to today's 09:00 UTC cron fire. To distinguish (1)/(2)/(3) I need either:

    • A direct query against IMSDBUSER.ims_config for the isNotificationSendingAllowed row and the emailListForOutdated row (creds are in the env — DB_USERNAME / DB_PASSWORD point at the legacy IMSDBUSER schema)
    • Live cf logs imsprod tail tomorrow at 09:00 UTC to catch the next fire
    • Or pulling logs from imsprod-logs (Application Logging service) if retention covers today

    Updated implications for the design

    Option C/D from the original ticket (skip email entirely / GitHub-issue-based nudges) are still viable for the new CAP tutorials-srv, but the original premise — "IMS email has been silently broken anyway" — does NOT hold. If we want behavioral continuity at PROD cutover, we either:

    • Carry the SMTP credentials forward (rotate first — see security finding below) and wire srv/lib/mail-client.js to the same smtpauth.mail.net.sap host, OR
    • Make a deliberate decision to stop sending these emails and document the change

    Security finding (separate issue, not part of #545)

    The cf env imsprod block also exposes plaintext credentials with no obfuscation:

    • SPRING_MAIL_PASSWORD (SMTP password for developer-rims-notifications)
    • DB_PASSWORD (plaintext password for the legacy IMSDBUSER HANA schema user — direct DB access bypasses HDI)
    • 3 SAP P-user passwords embedded in SPRING_APPLICATION_JSON under app.tech-users (used for the tech-users-mapping impersonation pattern at the AEM boundary)

    Any SpaceDeveloper on Developer Destination_IMS / PROD can read these. Rotation is overdue regardless of #545 outcome, and the new tutorials-srv must not inherit this pattern — the new platform's secrets work (#465 credstore) handles this category. Will file a separate tracking issue.

    Next on this ticket

    Pending confirmation of isNotificationSendingAllowed value in PROD HANA — that determines whether (1), (2), or (3) above holds — and then the design doc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions