Imported from gpc/greenmail (
AGENTS.md). Install upstream withnpx skills add gpc/greenmail. Copyright stays with the author.
AGENTS.md - greenmail
Project Overview
The GreenMail plugin runs a mock GreenMail SMTP
server inside a Grails application during development and test, so mail the application sends is
captured instead of delivered. It also ships a controller and GSP view that list captured messages at
/greenmail.
- Language: Groovy 5.0.8 on Java 21
- Framework: Grails 8.x
- Build System: Gradle 9.6.0 (with wrapper)
- Published artifact:
io.github.gpc:greenmail - Current Version: 8.0.0-SNAPSHOT
- License: Apache 2.0
Skill Files (Best Practices)
Detailed best practices are documented in .agents/skills/ (.claude is a symlink to .agents):
| Skill File | Purpose |
|---|---|
repository-structure |
Canonical directory layout and architectural rules |
gradle-best-practices |
Convention plugins, lazy APIs, build structure rules |
plugin-project |
Plugin project scope: source code + unit tests only |
example-apps |
Example app patterns: integration & functional tests |
Read these skill files before making structural changes to the repository.
Critical Rules
- NEVER add code to the root
build.gradleto configure subprojects. Nosubprojects {},allprojects {}, orconfigure()blocks. All shared configuration goes through convention plugins inbuild-logic/. build-logic/,.agents/,.github/workflows/,.github/scripts/,CONTRIBUTING.mdanddocs/src/docs/index.tmplare synced fromgrails-plugin-template. Edits there are overwritten by sync PRs. Put repository-specific guidance in this file instead.- The plugin project contains ONLY plugin code and unit tests. No integration tests, no functional tests.
examples/app1hosts all integration and functional tests. It depends on the plugin viaimplementation project(':greenmail')and tests it as a real consumer would.- Always use lazy Gradle APIs (
tasks.register(),tasks.named(),configureEach,provider {}).
Repository Structure
greenmail/
├── plugin/ # Grails plugin (artifact: greenmail)
│ ├── grails-app/ # Controller, URL mappings, GSP view, plugin conf
│ └── src/ # Plugin source code and unit tests
├── examples/app1/ # Example Grails app hosting the integration tests
├── docs/ # Asciidoctor documentation
├── build-logic/ # Gradle convention plugins (composite build)
├── code-coverage/ # Aggregated JaCoCo report
├── .github/workflows/ # CI, release, release-notes, contributor and version updates
├── build.gradle # Root build file (docs + root-publish ONLY)
├── settings.gradle # Multi-project settings
├── gradle.properties # Version properties
└── project.yml # Single source of project metadata (POM, docs, version index)
Build and Test Commands
# Full build (compile + unit tests + example-app integration tests)
./gradlew build
# Unit tests (plugin module)
./gradlew :greenmail:test
# Integration tests (example app)
./gradlew :app1:integrationTest
# Run the example app, then browse http://localhost:8080/greenmail
./gradlew :app1:bootRun
# Documentation -> build/docs
./gradlew docs
# Code style checks only
./gradlew codeStyle
# Skips
./gradlew build -PskipTests
./gradlew build -PskipCodeStyle
SDK Requirements
Tool versions are pinned in .sdkmanrc and are build-critical: config.compile reads the Java
major version from it and fails if it is missing. Run sdk env install before building — building on a
newer JDK makes the Groovy compiler emit bytecode that JaCoCo cannot analyse.
- Java:
21.0.7-librca - Gradle:
9.6.0 - Groovy:
5.0.8
Architecture
GreenmailGrailsPluginregisters agreenMailbean when the plugin is enabled, starts the server indoWithApplicationContext()and stops it inonShutdown(). It readsgrails.plugin.greenmail.disabledandgrails.plugin.greenmail.ports.smtp(default 3025, from GreenMail'sServerSetupTest.SMTP.port).GreenMailsubclassescom.icegreen.greenmail.util.GreenMailwith Groovy-friendly accessors. It is the plugin's public API, socom.icegreen:greenmailis anapidependency.MimeMessageExtensionis a Groovy extension module addingto/tos/cc/ccs/bcc/bccstojakarta.mail.internet.MimeMessage, registered viaMETA-INF/services/org.codehaus.groovy.runtime.ExtensionModule.GreenmailController+GreenmailUrlMappings+views/greenmail/list.gspserve the message list.withFormathandles thehtmlandjsformats, so JSON is served from.js— a.jsonextension falls back to the HTML block. See the docs caveat.
Core Classes
| Class | Location | Purpose |
|---|---|---|
GreenmailGrailsPlugin |
plugin/src/main/groovy/grails/plugin/greenmail/ |
Plugin descriptor and lifecycle |
GreenMail |
plugin/src/main/groovy/grails/plugin/greenmail/ |
Public API over GreenMail |
MimeMessageExtension |
plugin/src/main/groovy/org/grails/plugin/greenmail/ |
MimeMessage recipient properties |
GreenmailController |
plugin/grails-app/controllers/com/piragua/greenmail/ |
/greenmail list, show, clear |
GreenmailUrlMappings |
plugin/grails-app/controllers/greenmail/ |
/greenmail/... URL mappings |
Repository-Specific Notes
- The plugin ships a GSP, so
plugin/build.gradleappliesorg.apache.grails.gradle.grails-gspon top ofconfig.grails-plugin. Neitherconfig.grails-pluginnorconfig.grails-web-pluginapplies it. Removing it breaks the/greenmailview in consuming applications (issue #42). The plugin jar must containgsp_greenmail_greenmaillist_gsp.classandgsp/views.properties. config.grails-web-pluginis deliberately not used — the GSP has inline CSS and no<asset:>tags, so the asset pipeline is not needed.plugin/grails-app/conf/application.ymlsets the SMTP port to 1025. That applies only when running the plugin standalone; a plugin'sapplication.ymlis not loaded into consuming applications, where the default remains 3025. The two are not in conflict.GreenmailControllerlives in the legacycom.piragua.greenmailpackage, which differs from thegreenmaildefault package. Renaming it is a deliberate breaking change, not cleanup.
CI/CD
- CI (
.github/workflows/ci.yml): builds and tests on push/PR; publishes snapshots and docs on push to release branches. - Release (
.github/workflows/release.yml): staged release to Maven Central on GitHub release publish, then docs to GitHub Pages and the gh-pages version index. Requires therelease,docsandcloseGitHub environments. project.ymldrives POM metadata, the docs index and release notes. Itscontributorsandversionsblocks are maintained automatically by the update-contributors and update-versions workflows — do not hand-edit them after the initial seed.
Code Conventions
- Groovy source files follow standard Grails conventions: artefacts in
grails-app/, everything else insrc/main/groovy/. - Use
deffor local variables where the type is inferable from the right-hand side. Explicit types only when the type cannot be inferred or@CompileStaticrequires it. - CodeNarc runs at zero tolerance for all three priorities. Notably: single quotes for
non-interpolated strings (
UnnecessaryGString), no space before a map-entry colon (SpaceAroundMapEntryColon), no wildcard imports, and a blank line after a class's opening brace. - Checkstyle only scans Java sources, so it is
NO-SOURCEin this Groovy-only repository.