Skip to content

Add Grails 8 email guide sample app. - #1

Open
sanjana2505006 wants to merge 6 commits into
grails-guides:grails8from
sanjana2505006:grails8
Open

Add Grails 8 email guide sample app.#1
sanjana2505006 wants to merge 6 commits into
grails-guides:grails8from
sanjana2505006:grails8

Conversation

@sanjana2505006

@sanjana2505006 sanjana2505006 commented Jul 1, 2026

Copy link
Copy Markdown

Summary

Ports the grails-email sample app to Grails 8 (grails8 branch).

  • Rebuilds initial/ as a Grails 8 REST API starter (JDK 21, Apache Grails BOM)
  • Rebuilds complete/ with:
    • EmailService boundary + SMTP (grails-mail 5.x), SendGrid, and AWS SES implementations
    • Mail configuration externalized in application.yml (email.provider, SMTP / SendGrid / SES settings)
    • Provider wiring in resources.groovy (skipped in test so integration tests can mock the sender)
    • POST /mail/send controller and EmailCmd command object
    • Spock Spring integration spec that mocks EmailService and asserts message fields — no live email in CI
  • Adds .github/workflows/grails8.yml and updates README

Companion website PR (prose + guides.yml v8 block): apache/grails-static-website

@jamesfredley jamesfredley 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.

Thank you, @sanjana2505006, for the Grails 8 sample-app port. The blockers center on the three EmailService implementations behaving inconsistently on failure, allowing unsuccessful provider sends to appear successful.

Additional requested changes without eligible inline anchors:

  • complete/src/main/groovy/example/AwsSesMailService.groovy: rename AwsSesMailService to AwsSesEmailService to match the EmailService boundary naming.
  • complete/grails-app/controllers/example/MailController.groovy: return validation details instead of a bare 422, for example respond cmd.errors, view: '/application/errors', status: UNPROCESSABLE_ENTITY.

Body-only repository items:

  • Delete the duplicate, unused Gradle wrapper directories at complete/wrapper/ and initial/wrapper/. The application wrappers use gradle/wrapper/.
  • Restore recursive Gradle/build exclusions in the root .gitignore and restore IDE and OS exclusions such as .idea, *.iml, and .DS_Store in nested app .gitignore files.

Please also coordinate these behavior and documentation updates with companion website PR apache/grails-static-website#524.

Comment thread complete/src/main/groovy/example/SendGridEmailService.groovy Outdated
Comment thread complete/src/main/groovy/example/SendGridEmailService.groovy
Comment thread complete/src/main/groovy/example/AwsSesMailService.groovy Outdated
Comment thread complete/grails-app/conf/spring/resources.groovy Outdated
Comment thread complete/grails-app/views/error.gson Outdated
Comment thread complete/src/test/resources/application-test.yml Outdated
Comment thread complete/build.gradle
Comment thread initial/build.gradle
)

then:
resp.statusCode == HttpStatus.OK

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.

This spec covers only the happy path. Add invalid-request cases, such as a missing recipient or subject, that assert 422 and the JSON validation-error schema. This protects the API contract and the errors view.

Comment thread complete/src/integration-test/groovy/example/MailControllerSpec.groovy Outdated
Errors errorsObject = (Errors)this.errors
def allErrors = errorsObject.allErrors
int errorCount = allErrors.size()
def resourcePath = g.link(resource:request.uri, absolute:false)

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.

The live invalid-request response for POST /mail/send currently reports path: /send and a self link ending in /send. g.link(resource: request.uri) treats the URI as a resource name. Please preserve the actual request URI and use g.link(uri: ..., absolute: true) for the self link, then assert the exact path and href in both invalid-request tests.

respond cmd.errors, view: '/application/errors', status: UNPROCESSABLE_ENTITY
return
}
log.info '{}', cmd.toString()

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.

EmailCmd has @ToString, so this INFO log writes recipient, cc, bcc, subject, and full HTML or text bodies. The integration run showed the message content in application logs. Please remove the full command logging or log only non-sensitive metadata.

@jamesfredley

Copy link
Copy Markdown
Contributor

Latest-head re-review found one additional correctness blocker that GitHub could not anchor at its unchanged helper lines:

  • complete/src/main/groovy/example/AwsSesEmailService.groovy:37 returns immediately after building the HTML body. When both htmlBody and textBody are supplied, SES drops the plain-text alternative while SMTP and SendGrid preserve both. Please build one Body.Builder, set both non-empty alternatives, and return after both checks.

I also added inline comments for the reproduced integration-test failure, the incorrect validation path/self link, and full email-content logging. The original addressed threads were resolved; the invalid-request-test thread remains open because the prescribed suite is still red.

Comment thread complete/src/main/groovy/example/AwsSesEmailService.groovy Outdated
@sanjana2505006

Copy link
Copy Markdown
Author

@jamesfredley addressed the feedback
please have a look and let me know if there's any scope for improvements

Errors errorsObject = (Errors)this.errors
def allErrors = errorsObject.allErrors
int errorCount = allErrors.size()
def resourcePath = request.uri

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.

Reproduced on current head b07505d: ./gradlew test integrationTest still fails both invalid-request cases, and the GitHub complete job is red. request.uri remains /send after the error-view dispatch, so the JSON path and self link still omit /mail. Please derive both values from the original servlet request URI, such as request.requestURI, verify the live payload is /mail/send, and rerun the full command.

return
}
log.info '{}', cmd.toString()
log.info 'Sending mail to {} with subject {}', cmd.recipient, cmd.subject

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.

This is better than logging the full command, but recipient addresses are personal data and subjects can contain sensitive content, so they are not non-sensitive metadata. Please log only a generic send event, correlation ID, or provider message ID at INFO.

Comment thread complete/build.gradle
implementation "org.springframework.boot:spring-boot-tomcat"
console "org.apache.grails:grails-console"
//tag::mailPlugin[]
implementation "org.grails.plugins:grails-mail:$mailPluginVersion"

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.

Fresh ./gradlew bootRun does not start the application. The context fails because grails-mail requires a org.grails.gsp.GroovyPagesTemplateEngine bean, but this REST app has no Grails GSP dependency; Gradle misleadingly exits successful after APPLICATION FAILED TO START. Please add the required org.apache.grails:grails-gsp runtime support (and GSP Gradle plugin if templates will be compiled), verify port 8080 actually binds, and add a startup smoke check so CI catches this surface.

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.

2 participants