├── .github ├── release-drafter.yml ├── renovate.json └── workflows │ ├── gradle.yml │ ├── release-notes.yml │ └── release.yml ├── .gitignore ├── .sdkmanrc ├── LICENSE ├── README.md ├── build.gradle ├── buildSrc └── build.gradle ├── examples └── testapp1 │ ├── .gitignore │ ├── .sdkmanrc │ ├── build.gradle │ ├── buildSrc │ └── build.gradle │ ├── gradle.properties │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── grails-app │ ├── assets │ │ ├── images │ │ │ ├── advancedgrails.svg │ │ │ ├── apple-touch-icon-retina.png │ │ │ ├── apple-touch-icon.png │ │ │ ├── documentation.svg │ │ │ ├── favicon.ico │ │ │ ├── grails-cupsonly-logo-white.svg │ │ │ ├── grails.svg │ │ │ └── slack.svg │ │ ├── javascripts │ │ │ └── application.js │ │ └── stylesheets │ │ │ ├── application.css │ │ │ ├── errors.css │ │ │ └── grails.css │ ├── conf │ │ ├── application.yml │ │ └── logback-spring.xml │ ├── controllers │ │ └── testapp1 │ │ │ ├── GreenMailController.groovy │ │ │ ├── SendMailController.groovy │ │ │ └── UrlMappings.groovy │ ├── i18n │ │ ├── messages.properties │ │ └── messages_fr.properties │ ├── init │ │ └── testapp1 │ │ │ ├── Application.groovy │ │ │ └── BootStrap.groovy │ ├── services │ │ └── testapp1 │ │ │ └── GreenMailService.groovy │ └── views │ │ ├── _testemails │ │ ├── i18ntest.gsp │ │ ├── newLineTagTest.gsp │ │ ├── newLineTest.gsp │ │ ├── tagtest.gsp │ │ ├── test.gsp │ │ └── testhtml.gsp │ │ ├── error.gsp │ │ ├── greenMail │ │ └── greenMail.gsp │ │ ├── index.gsp │ │ ├── layouts │ │ └── main.gsp │ │ └── notFound.gsp │ ├── grails-wrapper.jar │ ├── grailsw │ ├── grailsw.bat │ ├── settings.gradle │ └── src │ └── integration-test │ ├── groovy │ └── grails │ │ └── plugins │ │ └── mail │ │ ├── MailServiceSpec.groovy │ │ └── functional │ │ └── MailSendSpec.groovy │ └── resources │ ├── assets │ └── grailslogo.png │ └── logback-test.xml ├── gradle.properties ├── gradle ├── docs-config.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── docs └── index.adoc ├── main ├── groovy │ └── grails │ │ └── plugins │ │ └── mail │ │ ├── GrailsMailException.groovy │ │ ├── MailAutoConfiguration.groovy │ │ ├── MailConfigurationProperties.groovy │ │ ├── MailGrailsPlugin.groovy │ │ ├── MailMessageBuilder.groovy │ │ ├── MailMessageBuilderFactory.groovy │ │ ├── MailMessageContentRender.groovy │ │ ├── MailMessageContentRenderer.groovy │ │ ├── MailService.groovy │ │ ├── PlainTextMailTagLib.groovy │ │ └── SendMail.groovy └── resources │ └── META-INF │ └── spring │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports └── test └── groovy └── grails └── plugins └── mail └── MailMessageBuilderSpec.groovy /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: $RESOLVED_VERSION 2 | tag-template: v$RESOLVED_VERSION 3 | pull-request: 4 | title-templates: 5 | fix: '🐛 $TITLE (#$NUMBER)' 6 | feat: '🚀 $TITLE (#$NUMBER)' 7 | default: '$TITLE (#$NUMBER)' 8 | autolabeler: 9 | - label: 'bug' 10 | branch: 11 | - '/fix\/.+/' 12 | title: 13 | - '/fix/i' 14 | - label: 'improvement' 15 | branch: 16 | - '/improv\/.+/' 17 | title: 18 | - '/improv/i' 19 | - label: 'feature' 20 | branch: 21 | - '/feature\/.+/' 22 | title: 23 | - '/feat/i' 24 | - label: 'documentation' 25 | branch: 26 | - '/docs\/.+/' 27 | title: 28 | - '/docs/i' 29 | - label: 'maintenance' 30 | branch: 31 | - '/(chore|refactor|style|test|ci|perf|build)\/.+/' 32 | title: 33 | - '/(chore|refactor|style|test|ci|perf|build)/i' 34 | - label: 'chore' 35 | branch: 36 | - '/chore\/.+/' 37 | title: 38 | - '/chore/i' 39 | - label: 'refactor' 40 | branch: 41 | - '/refactor\/.+/' 42 | title: 43 | - '/refactor/i' 44 | - label: 'style' 45 | branch: 46 | - '/style\/.+/' 47 | title: 48 | - '/style/i' 49 | - label: 'test' 50 | branch: 51 | - '/test\/.+/' 52 | title: 53 | - '/test/i' 54 | - label: 'ci' 55 | branch: 56 | - '/ci\/.+/' 57 | title: 58 | - '/ci/i' 59 | - label: 'perf' 60 | branch: 61 | - '/perf\/.+/' 62 | title: 63 | - '/perf/i' 64 | - label: 'build' 65 | branch: 66 | - '/build\/.+/' 67 | title: 68 | - '/build/i' 69 | - label: 'deps' 70 | branch: 71 | - '/deps\/.+/' 72 | title: 73 | - '/deps/i' 74 | - label: 'revert' 75 | branch: 76 | - '/revert\/.+/' 77 | title: 78 | - '/revert/i' 79 | categories: 80 | - title: '🚀 Features' 81 | labels: 82 | - 'feature' 83 | - "type: enhancement" 84 | - "type: new feature" 85 | - "type: major" 86 | - "type: minor" 87 | - title: '💡 Improvements' 88 | labels: 89 | - 'improvement' 90 | - "type: improvement" 91 | 92 | - title: '🐛 Bug Fixes' 93 | labels: 94 | - 'fix' 95 | - 'bug' 96 | - "type: bug" 97 | - title: '📚 Documentation' 98 | labels: 99 | - 'docs' 100 | - title: '🔧 Maintenance' 101 | labels: 102 | - 'maintenance' 103 | - 'chore' 104 | - 'refactor' 105 | - 'style' 106 | - 'test' 107 | - 'ci' 108 | - 'perf' 109 | - 'build' 110 | - "type: ci" 111 | - "type: build" 112 | - title: '⏪ Reverts' 113 | labels: 114 | - 'revert' 115 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)' 116 | version-resolver: 117 | major: 118 | labels: 119 | - 'type: major' 120 | minor: 121 | labels: 122 | - 'type: minor' 123 | patch: 124 | labels: 125 | - 'type: patch' 126 | default: patch 127 | template: | 128 | ## What's Changed 129 | 130 | $CHANGES 131 | 132 | ## Contributors 133 | 134 | $CONTRIBUTORS -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "labels": ["type: dependency upgrade"], 6 | "packageRules": [ 7 | { 8 | "matchUpdateTypes": ["major"], 9 | "enabled": false 10 | }, 11 | { 12 | "matchPackagePatterns": ["*"], 13 | "allowedVersions": "!/SNAPSHOT$/" 14 | }, 15 | { 16 | "matchPackagePatterns": [ 17 | "^org\\.codehaus\\.groovy" 18 | ], 19 | "groupName": "groovy monorepo" 20 | }, 21 | { 22 | "matchPackageNames": [ 23 | "org.grails:grails-bom", 24 | "org.grails:grails-bootstrap", 25 | "org.grails:grails-codecs", 26 | "org.grails:grails-console", 27 | "org.grails:grails-core", 28 | "org.grails:grails-databinding", 29 | "org.grails:grails-dependencies", 30 | "org.grails:grails-docs", 31 | "org.grails:grails-encoder", 32 | "org.grails:grails-gradle-model", 33 | "org.grails:grails-logging", 34 | "org.grails:grails-plugin-codecs", 35 | "org.grails:grails-plugin-controllers", 36 | "org.grails:grails-plugin-databinding", 37 | "org.grails:grails-plugin-datasource", 38 | "org.grails:grails-plugin-domain-class", 39 | "org.grails:grails-plugin-i18n", 40 | "org.grails:grails-plugin-interceptors", 41 | "org.grails:grails-plugin-mimetypes", 42 | "org.grails:grails-plugin-rest", 43 | "org.grails:grails-plugin-services", 44 | "org.grails:grails-plugin-url-mappings", 45 | "org.grails:grails-plugin-url-validation", 46 | "org.grails:grails-shell", 47 | "org.grails:grails-spring", 48 | "org.grails:grails-test", 49 | "org.grails:grails-validation", 50 | "org.grails:grails-web", 51 | "org.grails:grails-web-boot", 52 | "org.grails:grails-web-common", 53 | "org.grails:grails-web-databinding", 54 | "org.grails:grails-web-fileupload", 55 | "org.grails:grails-web-mvc", 56 | "org.grails:grails-web-url-mappings" 57 | ], 58 | "groupName": "grails monorepo" 59 | } 60 | ] 61 | } -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: "Java CI" 2 | on: 3 | push: 4 | branches: 5 | - '[4-9]+.[0-9]+.x' 6 | pull_request: 7 | branches: 8 | - '[4-9]+.[0-9]+.x' 9 | workflow_dispatch: 10 | jobs: 11 | test_project: 12 | name: "Test Project" 13 | runs-on: ubuntu-24.04 14 | strategy: 15 | fail-fast: true 16 | matrix: 17 | java: [17, 21] 18 | steps: 19 | - name: "📥 Checkout repository" 20 | uses: actions/checkout@v4 21 | - name: "☕️ Setup JDK" 22 | uses: actions/setup-java@v4 23 | with: 24 | java-version: ${{ matrix.java }} 25 | distribution: liberica 26 | - name: "🐘 Setup Gradle" 27 | uses: gradle/actions/setup-gradle@v4 28 | with: 29 | develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} 30 | - name: "🏃 Run tests" 31 | run: ./gradlew check 32 | - name: "🏃 Run integration tests" 33 | working-directory: ./examples/testapp1 34 | run: ./gradlew integrationTest 35 | publish_snapshot: 36 | if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' 37 | name: "Build Project and Publish Snapshot release" 38 | needs: test_project 39 | runs-on: ubuntu-24.04 40 | permissions: 41 | contents: write # updates gh-pages branch 42 | packages: write # publishes snapshot to GitHub Packages 43 | steps: 44 | - name: "📥 Checkout repository" 45 | uses: actions/checkout@v4 46 | - name: "☕️ Setup JDK" 47 | uses: actions/setup-java@v4 48 | with: 49 | java-version: 17 50 | distribution: liberica 51 | - name: "🐘 Setup Gradle" 52 | uses: gradle/actions/setup-gradle@v4 53 | with: 54 | develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} 55 | - name: "🔨 Build Project" 56 | run: ./gradlew build 57 | - name: "📤 Publish Snapshot version to Artifactory (repo.grails.org)" 58 | env: 59 | GRAILS_PUBLISH_RELEASE: 'false' 60 | MAVEN_PUBLISH_USERNAME: ${{ secrets.MAVEN_PUBLISH_USERNAME }} 61 | MAVEN_PUBLISH_PASSWORD: ${{ secrets.MAVEN_PUBLISH_PASSWORD }} 62 | MAVEN_PUBLISH_URL: 'https://repo.grails.org/artifactory/plugins3-snapshots-local' 63 | run: ./gradlew publish 64 | - name: "📖 Generate Snapshot Documentation" 65 | run: ./gradlew docs 66 | - name: "📤 Publish Snapshot Documentation to Github Pages" 67 | uses: apache/grails-github-actions/deploy-github-pages@asf 68 | env: 69 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 70 | GRADLE_PUBLISH_RELEASE: 'false' 71 | SOURCE_FOLDER: build/docs -------------------------------------------------------------------------------- /.github/workflows/release-notes.yml: -------------------------------------------------------------------------------- 1 | name: "Release Drafter" 2 | on: 3 | issues: 4 | types: [closed, reopened] 5 | push: 6 | branches: 7 | - master 8 | - '[4-9]+.[0-9]+.x' 9 | pull_request: 10 | types: [opened, reopened, synchronize] 11 | pull_request_target: 12 | types: [opened, reopened, synchronize] 13 | jobs: 14 | update_release_draft: 15 | permissions: 16 | contents: write 17 | pull-requests: write 18 | runs-on: ubuntu-24.04 19 | steps: 20 | - name: "📝 Update Release Draft" 21 | uses: release-drafter/release-drafter@v6 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: "Release" 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | release: 7 | name: "Publish Release" 8 | runs-on: ubuntu-24.04 9 | permissions: 10 | packages: read # for pre-release workflow 11 | contents: write # to commit changes related to the release and publish documentation to gh-pages 12 | issues: write # to modify milestones 13 | steps: 14 | - name: "📥 Checkout repository" 15 | uses: actions/checkout@v4 16 | - name: "☕️ Setup JDK" 17 | uses: actions/setup-java@v4 18 | with: 19 | java-version: 17 20 | distribution: liberica 21 | - name: "🐘 Setup Gradle" 22 | uses: gradle/actions/setup-gradle@v4 23 | with: 24 | develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} 25 | - name: "📝 Store the current release version" 26 | run: | 27 | echo "Release version: ${GITHUB_REF:11}" 28 | echo "RELEASE_VERSION=${GITHUB_REF:11}" >> $GITHUB_ENV 29 | - name: "⚙ Run pre-release" 30 | uses: apache/grails-github-actions/pre-release@asf 31 | - name: "🔐 Generate key file for artifact signing" 32 | env: 33 | SECRING_FILE: ${{ secrets.SECRING_FILE }} 34 | run: echo $SECRING_FILE | base64 -d > ${{ github.workspace }}/secring.gpg 35 | - name: "📤 Publish artifacts to Sonatype" 36 | env: 37 | GRAILS_PUBLISH_RELEASE: 'true' 38 | NEXUS_PUBLISH_USERNAME: ${{ secrets.NEXUS_PUBLISH_USERNAME }} 39 | NEXUS_PUBLISH_PASSWORD: ${{ secrets.NEXUS_PUBLISH_PASSWORD }} 40 | NEXUS_PUBLISH_URL: ${{ secrets.NEXUS_PUBLISH_RELEASE_URL }} 41 | NEXUS_PUBLISH_STAGING_PROFILE_ID: ${{ secrets.NEXUS_PUBLISH_STAGING_PROFILE_ID }} 42 | SIGNING_KEY: ${{ secrets.SIGNING_KEY }} 43 | SIGNING_PASSPHRASE: ${{ secrets.SIGNING_PASSPHRASE }} 44 | run: > 45 | ./gradlew 46 | -Psigning.secretKeyRingFile=${{ github.workspace }}/secring.gpg 47 | publishToSonatype 48 | closeAndReleaseSonatypeStagingRepository 49 | - name: "📖 Generate Documentation" 50 | run: ./gradlew docs 51 | - name: "📤 Publish Documentation to Github Pages" 52 | uses: apache/grails-github-actions/deploy-github-pages@asf 53 | env: 54 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 55 | GRADLE_PUBLISH_RELEASE: 'true' 56 | SOURCE_FOLDER: build/docs 57 | VERSION: ${{ env.RELEASE_VERSION }} 58 | - name: "⚙️ Run post-release" 59 | uses: apache/grails-github-actions/post-release@asf 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .classpath 3 | .project 4 | .gradle/ 5 | build/ 6 | .settings/ 7 | target-eclipse/ 8 | 9 | # Intellij 10 | .idea/ 11 | *.iml 12 | *.iws 13 | 14 | *.log 15 | test/reports 16 | target 17 | .DS_Store 18 | plugin.xml 19 | *.zip 20 | web-app 21 | 22 | -------------------------------------------------------------------------------- /.sdkmanrc: -------------------------------------------------------------------------------- 1 | java=17.0.14-librca -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /* 2 | * Apache License 3 | * Version 2.0, January 2004 4 | * http://www.apache.org/licenses/ 5 | * 6 | * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | * 8 | * 1. Definitions. 9 | * 10 | * "License" shall mean the terms and conditions for use, reproduction, 11 | * and distribution as defined by Sections 1 through 9 of this document. 12 | * 13 | * "Licensor" shall mean the copyright owner or entity authorized by 14 | * the copyright owner that is granting the License. 15 | * 16 | * "Legal Entity" shall mean the union of the acting entity and all 17 | * other entities that control, are controlled by, or are under common 18 | * control with that entity. For the purposes of this definition, 19 | * "control" means (i) the power, direct or indirect, to cause the 20 | * direction or management of such entity, whether by contract or 21 | * otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | * outstanding shares, or (iii) beneficial ownership of such entity. 23 | * 24 | * "You" (or "Your") shall mean an individual or Legal Entity 25 | * exercising permissions granted by this License. 26 | * 27 | * "Source" form shall mean the preferred form for making modifications, 28 | * including but not limited to software source code, documentation 29 | * source, and configuration files. 30 | * 31 | * "Object" form shall mean any form resulting from mechanical 32 | * transformation or translation of a Source form, including but 33 | * not limited to compiled object code, generated documentation, 34 | * and conversions to other media types. 35 | * 36 | * "Work" shall mean the work of authorship, whether in Source or 37 | * Object form, made available under the License, as indicated by a 38 | * copyright notice that is included in or attached to the work 39 | * (an example is provided in the Appendix below). 40 | * 41 | * "Derivative Works" shall mean any work, whether in Source or Object 42 | * form, that is based on (or derived from) the Work and for which the 43 | * editorial revisions, annotations, elaborations, or other modifications 44 | * represent, as a whole, an original work of authorship. For the purposes 45 | * of this License, Derivative Works shall not include works that remain 46 | * separable from, or merely link (or bind by name) to the interfaces of, 47 | * the Work and Derivative Works thereof. 48 | * 49 | * "Contribution" shall mean any work of authorship, including 50 | * the original version of the Work and any modifications or additions 51 | * to that Work or Derivative Works thereof, that is intentionally 52 | * submitted to Licensor for inclusion in the Work by the copyright owner 53 | * or by an individual or Legal Entity authorized to submit on behalf of 54 | * the copyright owner. For the purposes of this definition, "submitted" 55 | * means any form of electronic, verbal, or written communication sent 56 | * to the Licensor or its representatives, including but not limited to 57 | * communication on electronic mailing lists, source code control systems, 58 | * and issue tracking systems that are managed by, or on behalf of, the 59 | * Licensor for the purpose of discussing and improving the Work, but 60 | * excluding communication that is conspicuously marked or otherwise 61 | * designated in writing by the copyright owner as "Not a Contribution." 62 | * 63 | * "Contributor" shall mean Licensor and any individual or Legal Entity 64 | * on behalf of whom a Contribution has been received by Licensor and 65 | * subsequently incorporated within the Work. 66 | * 67 | * 2. Grant of Copyright License. Subject to the terms and conditions of 68 | * this License, each Contributor hereby grants to You a perpetual, 69 | * worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | * copyright license to reproduce, prepare Derivative Works of, 71 | * publicly display, publicly perform, sublicense, and distribute the 72 | * Work and such Derivative Works in Source or Object form. 73 | * 74 | * 3. Grant of Patent License. Subject to the terms and conditions of 75 | * this License, each Contributor hereby grants to You a perpetual, 76 | * worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | * (except as stated in this section) patent license to make, have made, 78 | * use, offer to sell, sell, import, and otherwise transfer the Work, 79 | * where such license applies only to those patent claims licensable 80 | * by such Contributor that are necessarily infringed by their 81 | * Contribution(s) alone or by combination of their Contribution(s) 82 | * with the Work to which such Contribution(s) was submitted. If You 83 | * institute patent litigation against any entity (including a 84 | * cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | * or a Contribution incorporated within the Work constitutes direct 86 | * or contributory patent infringement, then any patent licenses 87 | * granted to You under this License for that Work shall terminate 88 | * as of the date such litigation is filed. 89 | * 90 | * 4. Redistribution. You may reproduce and distribute copies of the 91 | * Work or Derivative Works thereof in any medium, with or without 92 | * modifications, and in Source or Object form, provided that You 93 | * meet the following conditions: 94 | * 95 | * (a) You must give any other recipients of the Work or 96 | * Derivative Works a copy of this License; and 97 | * 98 | * (b) You must cause any modified files to carry prominent notices 99 | * stating that You changed the files; and 100 | * 101 | * (c) You must retain, in the Source form of any Derivative Works 102 | * that You distribute, all copyright, patent, trademark, and 103 | * attribution notices from the Source form of the Work, 104 | * excluding those notices that do not pertain to any part of 105 | * the Derivative Works; and 106 | * 107 | * (d) If the Work includes a "NOTICE" text file as part of its 108 | * distribution, then any Derivative Works that You distribute must 109 | * include a readable copy of the attribution notices contained 110 | * within such NOTICE file, excluding those notices that do not 111 | * pertain to any part of the Derivative Works, in at least one 112 | * of the following places: within a NOTICE text file distributed 113 | * as part of the Derivative Works; within the Source form or 114 | * documentation, if provided along with the Derivative Works; or, 115 | * within a display generated by the Derivative Works, if and 116 | * wherever such third-party notices normally appear. The contents 117 | * of the NOTICE file are for informational purposes only and 118 | * do not modify the License. You may add Your own attribution 119 | * notices within Derivative Works that You distribute, alongside 120 | * or as an addendum to the NOTICE text from the Work, provided 121 | * that such additional attribution notices cannot be construed 122 | * as modifying the License. 123 | * 124 | * You may add Your own copyright statement to Your modifications and 125 | * may provide additional or different license terms and conditions 126 | * for use, reproduction, or distribution of Your modifications, or 127 | * for any such Derivative Works as a whole, provided Your use, 128 | * reproduction, and distribution of the Work otherwise complies with 129 | * the conditions stated in this License. 130 | * 131 | * 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | * any Contribution intentionally submitted for inclusion in the Work 133 | * by You to the Licensor shall be under the terms and conditions of 134 | * this License, without any additional terms or conditions. 135 | * Notwithstanding the above, nothing herein shall supersede or modify 136 | * the terms of any separate license agreement you may have executed 137 | * with Licensor regarding such Contributions. 138 | * 139 | * 6. Trademarks. This License does not grant permission to use the trade 140 | * names, trademarks, service marks, or product names of the Licensor, 141 | * except as required for reasonable and customary use in describing the 142 | * origin of the Work and reproducing the content of the NOTICE file. 143 | * 144 | * 7. Disclaimer of Warranty. Unless required by applicable law or 145 | * agreed to in writing, Licensor provides the Work (and each 146 | * Contributor provides its Contributions) on an "AS IS" BASIS, 147 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | * implied, including, without limitation, any warranties or conditions 149 | * of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | * PARTICULAR PURPOSE. You are solely responsible for determining the 151 | * appropriateness of using or redistributing the Work and assume any 152 | * risks associated with Your exercise of permissions under this License. 153 | * 154 | * 8. Limitation of Liability. In no event and under no legal theory, 155 | * whether in tort (including negligence), contract, or otherwise, 156 | * unless required by applicable law (such as deliberate and grossly 157 | * negligent acts) or agreed to in writing, shall any Contributor be 158 | * liable to You for damages, including any direct, indirect, special, 159 | * incidental, or consequential damages of any character arising as a 160 | * result of this License or out of the use or inability to use the 161 | * Work (including but not limited to damages for loss of goodwill, 162 | * work stoppage, computer failure or malfunction, or any and all 163 | * other commercial damages or losses), even if such Contributor 164 | * has been advised of the possibility of such damages. 165 | * 166 | * 9. Accepting Warranty or Additional Liability. While redistributing 167 | * the Work or Derivative Works thereof, You may choose to offer, 168 | * and charge a fee for, acceptance of support, warranty, indemnity, 169 | * or other liability obligations and/or rights consistent with this 170 | * License. However, in accepting such obligations, You may act only 171 | * on Your own behalf and on Your sole responsibility, not on behalf 172 | * of any other Contributor, and only if You agree to indemnify, 173 | * defend, and hold each Contributor harmless for any liability 174 | * incurred by, or claims asserted against, such Contributor by reason 175 | * of your accepting any such warranty or additional liability. 176 | * 177 | * END OF TERMS AND CONDITIONS 178 | * 179 | * APPENDIX: How to apply the Apache License to your work. 180 | * 181 | * To apply the Apache License to your work, attach the following 182 | * boilerplate notice, with the fields enclosed by brackets "[]" 183 | * replaced with your own identifying information. (Don't include 184 | * the brackets!) The text should be enclosed in the appropriate 185 | * comment syntax for the file format. We also recommend that a 186 | * file or class name and description of purpose be included on the 187 | * same "printed page" as the copyright notice for easier 188 | * identification within third-party archives. 189 | * 190 | * Copyright 2009 SpringSource 191 | * 192 | * Licensed under the Apache License, Version 2.0 (the "License"); 193 | * you may not use this file except in compliance with the License. 194 | * You may obtain a copy of the License at 195 | * 196 | * http://www.apache.org/licenses/LICENSE-2.0 197 | * 198 | * Unless required by applicable law or agreed to in writing, software 199 | * distributed under the License is distributed on an "AS IS" BASIS, 200 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | * See the License for the specific language governing permissions and 202 | * limitations under the License. 203 | */ 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grails Mail Plugin 2 | 3 | [![Maven Central](https://img.shields.io/maven-central/v/org.grails.plugins/grails-mail.svg?label=Maven%20Central)](https://central.sonatype.com/artifact/org.grails.plugins/grails-mail) 4 | [![Java CI](https://github.com/grails-plugins/grails-mail/actions/workflows/gradle.yml/badge.svg?event=push)](https://github.com/grails-plugins/grails-mail/actions/workflows/gradle.yml) 5 | 6 | The Grails Mail Plugin provides a convenient Domain-Specific Language (DSL) for _sending_ email, supporting features such as plain text, HTML, attachments, inline resources and i18n (internationalization), among others. 7 | 8 | Email can be sent using the `mailService.sendMail` method. Here's an example: 9 | ```groovy 10 | mailService.sendMail { 11 | to 'fred@gmail.com', 'ginger@gmail.com' 12 | from 'john@gmail.com' 13 | cc 'marge@gmail.com', 'ed@gmail.com' 14 | bcc 'joe@gmail.com' 15 | subject 'Hello John' 16 | text 'this is some text' 17 | } 18 | ``` 19 | 20 | ## Documentation 21 | 22 | - [Latest Release](https://grails-plugins.github.io/grails-mail/latest/) 23 | - [Development Snapshot](https://grails-plugins.github.io/grails-mail/snapshot/) 24 | 25 | ## Versions 26 | 27 | | Branch | Grails Version | 28 | |--------|----------------| 29 | | 1.x | 2 | 30 | | 2.x | 3 | 31 | | 3.x | 4-5 | 32 | | 4.x | 6 | 33 | | 5.x | 7 | 34 | 35 | ## Issues 36 | 37 | Issues can be raised via [GitHub Issues](https://github.com/grails-plugins/grails-mail/issues). 38 | 39 | ## Contributing 40 | 41 | Pull requests are the preferred way to submit contributions. Before submitting, please create an issue using the [issue tracker](https://github.com/grails-plugins/grails-mail/issues), outlining the problem your contribution addresses. 42 | 43 | For documentation contributions, creating an issue is not required. 44 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | version = projectVersion 2 | group = 'org.grails.plugins' 3 | 4 | apply plugin: 'groovy' 5 | apply plugin: 'java-library' 6 | apply plugin: 'org.apache.grails.gradle.grails-plugin' 7 | apply plugin: 'org.apache.grails.gradle.grails-publish' 8 | 9 | repositories { 10 | mavenCentral() 11 | maven { url = 'https://repo.grails.org/grails/core' } 12 | maven { url = 'https://repository.apache.org/content/repositories/snapshots' } 13 | } 14 | 15 | dependencies { 16 | 17 | implementation(platform("org.apache.grails:grails-bom:$grailsVersion")) 18 | 19 | api 'org.apache.grails:grails-gsp', { 20 | // api: GroovyPageTemplate, GroovyPagesTemplateEngine 21 | } 22 | api 'org.apache.grails.web:grails-web-common', { 23 | // api: GroovyPagesUriService 24 | // impl: GrailsWebRequest, WrappedResponseHolder 25 | } 26 | api 'org.springframework:spring-beans', { 27 | // api: DisposableBean, InitializingBean 28 | // impl: @Autowired(runtime), @Qualifier(runtime) 29 | } 30 | api 'org.springframework:spring-context', { 31 | // api: ApplicationContext, JndiObjectFactoryBean 32 | // impl: @Bean(runtime) 33 | } 34 | api 'org.springframework:spring-context-support', { 35 | // api: JavaMailSender, MailMessage, MailSender, SimpleMailMessage 36 | // impl: JavaMailSenderImpl, MimeMailMessage, MimeMessageHelper 37 | } 38 | api 'org.springframework:spring-core', { 39 | // api: InputStreamSource 40 | // impl: Assert, ByteArrayResource, FileSystemResource, PropertiesPropertySource, StringUtils 41 | } 42 | 43 | implementation 'org.apache.grails.views:grails-web-taglib', { 44 | // for taglib support 45 | } 46 | implementation 'org.apache.grails.web:grails-web-url-mappings', { 47 | // impl: LinkGenerator 48 | } 49 | implementation 'org.apache.groovy:groovy-templates', { 50 | // impl: Template 51 | } 52 | implementation 'org.eclipse.angus:jakarta.mail', { 53 | // impl: SMTPMessage 54 | } 55 | implementation 'org.springframework:spring-web', { 56 | // impl: RequestContextHolder 57 | } 58 | implementation 'org.springframework:spring-webmvc', { 59 | // impl: DispatcherServlet, FixedLocaleResolver, RequestContextUtils 60 | } 61 | implementation 'org.springframework.boot:spring-boot', { 62 | // impl: Bindable, Binder, @ConfigurationProperties, ConfigurationPropertySources, 63 | // @EnableConfigurationProperties(runtime) 64 | } 65 | implementation 'org.springframework.boot:spring-boot-autoconfigure', { 66 | // impl: @AutoConfiguration(runtime), @ConditionalOnMissingBean(runtime), @ConditionalOnProperty(runtime) 67 | } 68 | 69 | compileOnlyApi 'jakarta.mail:jakarta.mail-api', { 70 | // api: Message, Session 71 | // impl: MimeMessage, MimeUtility 72 | } 73 | 74 | compileOnly 'jakarta.inject:jakarta.inject-api', { // Used by Spring DI 75 | // impl: @Inject(runtime) 76 | } 77 | compileOnly 'jakarta.servlet:jakarta.servlet-api', { // Provided by the servlet container 78 | // impl: Cookie, HttpServletResponse, HttpServletRequest 79 | } 80 | compileOnly 'org.apache.grails:grails-core', { // Provided as this is a Grails plugin 81 | // api: Config, GrailsApplication, GrailsPluginManager, Plugin 82 | // impl: @Artefact(runtime), @Enhances(runtime) 83 | } 84 | compileOnly 'org.apache.groovy:groovy' // Provided as this is a Grails plugin 85 | 86 | testImplementation 'jakarta.servlet:jakarta.servlet-api', { 87 | // impl: ServletContext 88 | } 89 | testImplementation 'org.apache.grails.testing:grails-testing-support-core', { 90 | // impl: GrailsUnitTest 91 | } 92 | testImplementation 'org.spockframework:spock-core' 93 | 94 | testRuntimeOnly 'org.junit.platform:junit-platform-launcher' 95 | } 96 | 97 | grailsPublish { 98 | githubSlug = 'grails-plugins/grails-mail' 99 | license { 100 | name = 'Apache-2.0' 101 | } 102 | title = 'Grails Mail Plugin' 103 | desc = 'Provides Mail support to a running Grails application' 104 | developers = [ 105 | candrews: 'Craig Andrews', 106 | ldaley: 'Luke Daley', 107 | pledbrook: 'Peter Ledbrook', 108 | jeffscottbrown: 'Jeff Brown', 109 | graemerocher: 'Graeme Rocher', 110 | marcpalmer: 'Marc Palmer', 111 | sbglasius: 'Søren Berg Glasius', 112 | matrei: 'Mattias Reichel' 113 | ] 114 | } 115 | 116 | tasks.withType(Test).configureEach { 117 | useJUnitPlatform() 118 | testLogging { 119 | events 'passed', 'skipped', 'failed' 120 | } 121 | } 122 | 123 | compileJava.options.release = javaVersion.toInteger() 124 | 125 | apply from: layout.projectDirectory.file('gradle/docs-config.gradle') 126 | -------------------------------------------------------------------------------- /buildSrc/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'groovy-gradle-plugin' 3 | } 4 | 5 | file('../gradle.properties').withInputStream { 6 | def gradleProperties = new Properties() 7 | gradleProperties.load(it) 8 | gradleProperties.each { k, v -> ext.set(k, v) } 9 | 10 | } 11 | 12 | repositories { 13 | maven { url = 'https://repo.grails.org/grails/core' } 14 | maven { url = 'https://repository.apache.org/content/repositories/snapshots' } 15 | } 16 | 17 | dependencies { 18 | implementation platform("org.apache.grails:grails-bom:$grailsVersion") 19 | implementation "org.asciidoctor.jvm.convert:org.asciidoctor.jvm.convert.gradle.plugin:$asciidoctorGradlePluginVersion" 20 | implementation 'org.apache.grails:grails-gradle-plugins' 21 | } -------------------------------------------------------------------------------- /examples/testapp1/.gitignore: -------------------------------------------------------------------------------- 1 | ### Gradle ### 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | 39 | ### Other ### 40 | Thumbs.db 41 | .DS_Store 42 | target/ 43 | -------------------------------------------------------------------------------- /examples/testapp1/.sdkmanrc: -------------------------------------------------------------------------------- 1 | java=17.0.14-librca -------------------------------------------------------------------------------- /examples/testapp1/build.gradle: -------------------------------------------------------------------------------- 1 | file('../../gradle.properties').withInputStream { 2 | // Inherit versions from main project 3 | def mainProjectProps = new Properties() 4 | mainProjectProps.load(it) 5 | mainProjectProps 6 | .findAll { !findProperty(it.key.toString()) } 7 | .findAll {it.key.toString().endsWith('Version') } 8 | .each { k, v -> ext.set(k, v) } 9 | } 10 | 11 | version = '0.1' 12 | group = 'testapp1' 13 | 14 | apply plugin: 'org.apache.grails.gradle.grails-web' 15 | apply plugin: 'com.bertramlabs.asset-pipeline' 16 | 17 | repositories { 18 | maven { url = 'https://repo.grails.org/grails/core' } 19 | maven { url = 'https://repository.apache.org/content/repositories/snapshots' } 20 | } 21 | 22 | dependencies { 23 | 24 | implementation platform("org.apache.grails:grails-bom:$grailsVersion") 25 | 26 | implementation "com.icegreen:greenmail:$greenmailVersion" 27 | implementation 'org.grails.plugins:grails-mail' 28 | implementation 'org.apache.grails:grails-core' 29 | implementation 'org.apache.grails:grails-controllers' 30 | implementation 'org.apache.grails:grails-gsp' 31 | 32 | testAndDevelopmentOnly 'org.webjars.npm:bootstrap' 33 | testAndDevelopmentOnly 'org.webjars.npm:bootstrap-icons' 34 | testAndDevelopmentOnly 'org.webjars.npm:jquery' 35 | 36 | runtimeOnly 'com.bertramlabs.plugins:asset-pipeline-grails' 37 | runtimeOnly 'org.fusesource.jansi:jansi' 38 | runtimeOnly 'org.apache.grails:grails-services' 39 | runtimeOnly 'org.apache.grails:grails-i18n' 40 | runtimeOnly 'org.apache.grails:grails-url-mappings' 41 | runtimeOnly 'org.springframework.boot:spring-boot-autoconfigure' 42 | runtimeOnly 'org.springframework.boot:spring-boot-starter-logging' 43 | runtimeOnly 'org.springframework.boot:spring-boot-starter-tomcat' 44 | 45 | testRuntimeOnly 'org.junit.platform:junit-platform-launcher' 46 | 47 | integrationTestImplementation testFixtures('org.apache.grails:grails-geb') 48 | integrationTestImplementation 'org.apache.grails.testing:grails-testing-support-core' 49 | integrationTestImplementation 'org.spockframework:spock-core' 50 | 51 | } 52 | 53 | compileJava.options.release = javaVersion.toInteger() 54 | 55 | tasks.withType(Test).configureEach { 56 | useJUnitPlatform() 57 | testLogging { 58 | events 'passed', 'skipped', 'failed' 59 | } 60 | } -------------------------------------------------------------------------------- /examples/testapp1/buildSrc/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'groovy-gradle-plugin' 3 | } 4 | 5 | file('../../../gradle.properties').withInputStream { 6 | def gradleProperties = new Properties() 7 | gradleProperties.load(it) 8 | gradleProperties.each { k, v -> ext.set(k, v) } 9 | } 10 | 11 | repositories { 12 | maven { url = 'https://repo.grails.org/grails/core' } 13 | maven { url = 'https://repository.apache.org/content/repositories/snapshots' } 14 | } 15 | 16 | dependencies { 17 | implementation platform("org.apache.grails:grails-bom:$grailsVersion") 18 | implementation 'com.bertramlabs.plugins:asset-pipeline-gradle' 19 | implementation 'org.apache.grails:grails-gradle-plugins' 20 | } -------------------------------------------------------------------------------- /examples/testapp1/gradle.properties: -------------------------------------------------------------------------------- 1 | # More versions are inherited from ../../gradle.properties 2 | greenmailVersion=2.1.2 3 | 4 | org.gradle.caching=true 5 | org.gradle.daemon=true 6 | org.gradle.parallel=true 7 | -------------------------------------------------------------------------------- /examples/testapp1/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-mail/62073062a1ad9e57900f50dc3fd076cd860ed1f9/examples/testapp1/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /examples/testapp1/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /examples/testapp1/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s 90 | ' "$PWD" ) || exit 91 | 92 | # Use the maximum available, or set MAX_FD != -1 to use that value. 93 | MAX_FD=maximum 94 | 95 | warn () { 96 | echo "$*" 97 | } >&2 98 | 99 | die () { 100 | echo 101 | echo "$*" 102 | echo 103 | exit 1 104 | } >&2 105 | 106 | # OS specific support (must be 'true' or 'false'). 107 | cygwin=false 108 | msys=false 109 | darwin=false 110 | nonstop=false 111 | case "$( uname )" in #( 112 | CYGWIN* ) cygwin=true ;; #( 113 | Darwin* ) darwin=true ;; #( 114 | MSYS* | MINGW* ) msys=true ;; #( 115 | NONSTOP* ) nonstop=true ;; 116 | esac 117 | 118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 119 | 120 | 121 | # Determine the Java command to use to start the JVM. 122 | if [ -n "$JAVA_HOME" ] ; then 123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 124 | # IBM's JDK on AIX uses strange locations for the executables 125 | JAVACMD=$JAVA_HOME/jre/sh/java 126 | else 127 | JAVACMD=$JAVA_HOME/bin/java 128 | fi 129 | if [ ! -x "$JAVACMD" ] ; then 130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 131 | 132 | Please set the JAVA_HOME variable in your environment to match the 133 | location of your Java installation." 134 | fi 135 | else 136 | JAVACMD=java 137 | if ! command -v java >/dev/null 2>&1 138 | then 139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 140 | 141 | Please set the JAVA_HOME variable in your environment to match the 142 | location of your Java installation." 143 | fi 144 | fi 145 | 146 | # Increase the maximum file descriptors if we can. 147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 148 | case $MAX_FD in #( 149 | max*) 150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 151 | # shellcheck disable=SC2039,SC3045 152 | MAX_FD=$( ulimit -H -n ) || 153 | warn "Could not query maximum file descriptor limit" 154 | esac 155 | case $MAX_FD in #( 156 | '' | soft) :;; #( 157 | *) 158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 159 | # shellcheck disable=SC2039,SC3045 160 | ulimit -n "$MAX_FD" || 161 | warn "Could not set maximum file descriptor limit to $MAX_FD" 162 | esac 163 | fi 164 | 165 | # Collect all arguments for the java command, stacking in reverse order: 166 | # * args from the command line 167 | # * the main class name 168 | # * -classpath 169 | # * -D...appname settings 170 | # * --module-path (only if needed) 171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 172 | 173 | # For Cygwin or MSYS, switch paths to Windows format before running java 174 | if "$cygwin" || "$msys" ; then 175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 177 | 178 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 179 | 180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 181 | for arg do 182 | if 183 | case $arg in #( 184 | -*) false ;; # don't mess with options #( 185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 186 | [ -e "$t" ] ;; #( 187 | *) false ;; 188 | esac 189 | then 190 | arg=$( cygpath --path --ignore --mixed "$arg" ) 191 | fi 192 | # Roll the args list around exactly as many times as the number of 193 | # args, so each arg winds up back in the position where it started, but 194 | # possibly modified. 195 | # 196 | # NB: a `for` loop captures its iteration list before it begins, so 197 | # changing the positional parameters here affects neither the number of 198 | # iterations, nor the values presented in `arg`. 199 | shift # remove old arg 200 | set -- "$@" "$arg" # push replacement arg 201 | done 202 | fi 203 | 204 | 205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 207 | 208 | # Collect all arguments for the java command: 209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 210 | # and any embedded shellness will be escaped. 211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 212 | # treated as '${Hostname}' itself on the command line. 213 | 214 | set -- \ 215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 216 | -classpath "$CLASSPATH" \ 217 | org.gradle.wrapper.GradleWrapperMain \ 218 | "$@" 219 | 220 | # Stop when "xargs" is not available. 221 | if ! command -v xargs >/dev/null 2>&1 222 | then 223 | die "xargs is not available" 224 | fi 225 | 226 | # Use "xargs" to parse quoted args. 227 | # 228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 229 | # 230 | # In Bash we could simply go: 231 | # 232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 233 | # set -- "${ARGS[@]}" "$@" 234 | # 235 | # but POSIX shell has neither arrays nor command substitution, so instead we 236 | # post-process each arg (as a line of input to sed) to backslash-escape any 237 | # character that might be a shell metacharacter, then use eval to reverse 238 | # that process (while maintaining the separation between arguments), and wrap 239 | # the whole thing up as a single "set" statement. 240 | # 241 | # This will of course break if any of these variables contains a newline or 242 | # an unmatched quote. 243 | # 244 | 245 | eval "set -- $( 246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 247 | xargs -n1 | 248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 249 | tr '\n' ' ' 250 | )" '"$@"' 251 | 252 | exec "$JAVACMD" "$@" 253 | -------------------------------------------------------------------------------- /examples/testapp1/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /examples/testapp1/grails-app/assets/images/advancedgrails.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/testapp1/grails-app/assets/images/apple-touch-icon-retina.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-mail/62073062a1ad9e57900f50dc3fd076cd860ed1f9/examples/testapp1/grails-app/assets/images/apple-touch-icon-retina.png -------------------------------------------------------------------------------- /examples/testapp1/grails-app/assets/images/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-mail/62073062a1ad9e57900f50dc3fd076cd860ed1f9/examples/testapp1/grails-app/assets/images/apple-touch-icon.png -------------------------------------------------------------------------------- /examples/testapp1/grails-app/assets/images/documentation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/testapp1/grails-app/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grails-plugins/grails-mail/62073062a1ad9e57900f50dc3fd076cd860ed1f9/examples/testapp1/grails-app/assets/images/favicon.ico -------------------------------------------------------------------------------- /examples/testapp1/grails-app/assets/images/grails-cupsonly-logo-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/testapp1/grails-app/assets/images/grails.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | grails 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/testapp1/grails-app/assets/images/slack.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | slack_orange 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/testapp1/grails-app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js. 2 | // 3 | // Any JavaScript file within this directory can be referenced here using a relative path. 4 | // 5 | // You're free to add application-wide JavaScript to this file, but it's generally better 6 | // to create separate JavaScript files as needed. 7 | // 8 | //= require webjars/jquery/3.7.1/dist/jquery.js 9 | //= require webjars/bootstrap/5.3.3/dist/js/bootstrap.bundle 10 | //= require_self 11 | 12 | if (typeof jQuery !== 'undefined') { 13 | (function($) { 14 | $('#spinner').ajaxStart(function() { 15 | $(this).fadeIn(); 16 | }).ajaxStop(function() { 17 | $(this).fadeOut(); 18 | }); 19 | })(jQuery); 20 | } -------------------------------------------------------------------------------- /examples/testapp1/grails-app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS file within this directory can be referenced here using a relative path. 6 | * 7 | * You're free to add application-wide styles to this file and they'll appear at the top of the 8 | * compiled file, but it's generally better to create a new file per style scope. 9 | * 10 | *= require webjars/bootstrap/5.3.3/dist/css/bootstrap 11 | *= require webjars/bootstrap-icons/1.11.3/font/bootstrap-icons 12 | *= require grails 13 | *= require_self 14 | */ 15 | -------------------------------------------------------------------------------- /examples/testapp1/grails-app/assets/stylesheets/errors.css: -------------------------------------------------------------------------------- 1 | .filename { 2 | font-style: italic; 3 | } 4 | 5 | .exceptionMessage { 6 | margin: 10px; 7 | border: 1px solid #000; 8 | padding: 5px; 9 | background-color: #E9E9E9; 10 | } 11 | 12 | .stack, 13 | .snippet { 14 | margin: 10px 0; 15 | } 16 | 17 | .stack, 18 | .snippet { 19 | border: 1px solid #ccc; 20 | } 21 | 22 | /* error details */ 23 | .error-details { 24 | border: 1px solid #FFAAAA; 25 | background-color:#FFF3F3; 26 | line-height: 1.5; 27 | overflow: hidden; 28 | padding: 10px 0 5px 25px; 29 | } 30 | 31 | .error-details dt { 32 | clear: left; 33 | float: left; 34 | font-weight: bold; 35 | margin-right: 5px; 36 | } 37 | 38 | .error-details dt:after { 39 | content: ":"; 40 | } 41 | 42 | .error-details dd { 43 | display: block; 44 | } 45 | 46 | /* stack trace */ 47 | .stack { 48 | padding: 5px; 49 | overflow: auto; 50 | height: 300px; 51 | } 52 | 53 | /* code snippet */ 54 | .snippet { 55 | background-color: #fff; 56 | font-family: monospace; 57 | } 58 | 59 | .snippet .line { 60 | display: block; 61 | } 62 | 63 | .snippet .lineNumber { 64 | background-color: #ddd; 65 | color: #999; 66 | display: inline-block; 67 | margin-right: 5px; 68 | padding: 0 3px; 69 | text-align: right; 70 | width: 3em; 71 | } 72 | 73 | .snippet .error { 74 | background-color: #fff3f3; 75 | font-weight: bold; 76 | } 77 | 78 | .snippet .error .lineNumber { 79 | background-color: #faa; 80 | color: #333; 81 | font-weight: bold; 82 | } 83 | 84 | .snippet .line:first-child .lineNumber { 85 | padding-top: 5px; 86 | } 87 | 88 | .snippet .line:last-child .lineNumber { 89 | padding-bottom: 5px; 90 | } -------------------------------------------------------------------------------- /examples/testapp1/grails-app/assets/stylesheets/grails.css: -------------------------------------------------------------------------------- 1 | table.scaffold tr>td:first-child, tr>th:first-child { 2 | padding-left: 1.25em; 3 | } 4 | 5 | table.scaffold tr>td:last-child, tr>th:last-child { 6 | padding-right: 1.25em; 7 | } 8 | 9 | table.scaffold th { 10 | background-image: linear-gradient( 11 | to bottom, 12 | #ffffff 0%, 13 | #f8f8f8 30%, 14 | #eaeaea 70%, 15 | #d4d4d4 100% 16 | ); 17 | border-bottom: 2px solid #b3b3b3; /* Adding a subtle shadow effect */ 18 | box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1); /* Adding a drop shadow */ 19 | } 20 | 21 | [data-bs-theme=dark] table.scaffold th { 22 | background-image: linear-gradient( 23 | to bottom, 24 | #4a4a4a 0%, 25 | #3e3e3e 30%, 26 | #2a2a2a 70%, 27 | #1e1e1e 100% 28 | ); 29 | border-bottom: 2px solid #141414; /* Adding a subtle shadow effect */ 30 | box-shadow: 0 2px 3px rgba(0, 0, 0, 0.3); /* Adding a drop shadow */ 31 | } 32 | 33 | table.scaffold thead th { 34 | white-space: nowrap; 35 | } 36 | 37 | table.scaffold th a { 38 | display: block; 39 | text-decoration: none; 40 | } 41 | 42 | table.scaffold th a:link, th a:visited { 43 | color: #666666; 44 | } 45 | 46 | table.scaffold th a:hover, th a:focus { 47 | color: #333333; 48 | } 49 | 50 | table.scaffold th.sortable a { 51 | background-position: right; 52 | background-repeat: no-repeat; 53 | padding-right: 1.1em; 54 | } 55 | 56 | table.scaffold th { 57 | position: relative; 58 | } 59 | 60 | 61 | table.scaffold th.asc a:after { 62 | content: '▲'; 63 | position: absolute; 64 | right: 10px; 65 | font-size: 0.8em; 66 | } 67 | 68 | table.scaffold th.desc a:after { 69 | content: '▼'; 70 | position: absolute; 71 | right: 10px; 72 | font-size: 0.8em; 73 | } 74 | 75 | table.scaffold th:hover { 76 | background: #f5f5f5 !important; 77 | } -------------------------------------------------------------------------------- /examples/testapp1/grails-app/conf/application.yml: -------------------------------------------------------------------------------- 1 | info: 2 | app: 3 | name: '@info.app.name@' 4 | version: '@info.app.version@' 5 | grailsVersion: '@info.app.grailsVersion@' 6 | grails: 7 | mail: 8 | host: 127.0.0.1 9 | port: 3025 10 | views: 11 | default: 12 | codec: html 13 | gsp: 14 | encoding: UTF-8 15 | htmlcodec: xml 16 | codecs: 17 | expression: html 18 | scriptlet: html 19 | taglib: none 20 | staticparts: none 21 | mime: 22 | disable: 23 | accept: 24 | header: 25 | userAgents: 26 | - Gecko 27 | - WebKit 28 | - Presto 29 | - Trident 30 | types: 31 | all: '*/*' 32 | atom: application/atom+xml 33 | css: text/css 34 | csv: text/csv 35 | form: application/x-www-form-urlencoded 36 | html: 37 | - text/html 38 | - application/xhtml+xml 39 | js: text/javascript 40 | json: 41 | - application/json 42 | - text/json 43 | multipartForm: multipart/form-data 44 | pdf: application/pdf 45 | rss: application/rss+xml 46 | text: text/plain 47 | hal: 48 | - application/hal+json 49 | - application/hal+xml 50 | xml: 51 | - text/xml 52 | - application/xml -------------------------------------------------------------------------------- /examples/testapp1/grails-app/conf/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | true 7 | 8 | ${CONSOLE_LOG_THRESHOLD} 9 | 10 | 11 | ${CONSOLE_LOG_PATTERN} 12 | ${CONSOLE_LOG_CHARSET} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /examples/testapp1/grails-app/controllers/testapp1/GreenMailController.groovy: -------------------------------------------------------------------------------- 1 | package testapp1 2 | 3 | import com.icegreen.greenmail.util.GreenMailUtil 4 | 5 | class GreenMailController { 6 | 7 | GreenMailService greenMailService 8 | 9 | def index() { 10 | def greenMail = greenMailService.greenMail 11 | if (!greenMail) { 12 | render 'GreenMail is not running' 13 | return 14 | } 15 | def messages = greenMail.receivedMessages.collect { msg -> 16 | [ 17 | from : msg.from.address, 18 | to : msg.allRecipients.join(', '), 19 | subject: msg.subject, 20 | body : GreenMailUtil.getBody(msg) 21 | ] 22 | } 23 | render(view: 'greenMail', model: [messages: messages]) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/testapp1/grails-app/controllers/testapp1/SendMailController.groovy: -------------------------------------------------------------------------------- 1 | package testapp1 2 | 3 | class SendMailController { 4 | 5 | def mailService 6 | 7 | def index() { 8 | mailService.sendMail { 9 | to params.to 10 | from params.from 11 | subject params.subject 12 | text params.text 13 | } 14 | redirect(controller: 'greenMail', action: 'index') 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/testapp1/grails-app/controllers/testapp1/UrlMappings.groovy: -------------------------------------------------------------------------------- 1 | package testapp1 2 | 3 | class UrlMappings { 4 | static mappings = { 5 | "/$controller/$action?/$id?(.$format)?"{ 6 | constraints { 7 | // apply constraints here 8 | } 9 | } 10 | 11 | "/"(view:"/index") 12 | "500"(view:'/error') 13 | "404"(view:'/notFound') 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/testapp1/grails-app/i18n/messages.properties: -------------------------------------------------------------------------------- 1 | translate=Translate this: {0} -------------------------------------------------------------------------------- /examples/testapp1/grails-app/i18n/messages_fr.properties: -------------------------------------------------------------------------------- 1 | translate=Traduis ceci: {0} -------------------------------------------------------------------------------- /examples/testapp1/grails-app/init/testapp1/Application.groovy: -------------------------------------------------------------------------------- 1 | package testapp1 2 | 3 | import grails.boot.GrailsApp 4 | import grails.boot.config.GrailsAutoConfiguration 5 | import groovy.transform.CompileStatic 6 | 7 | @CompileStatic 8 | class Application extends GrailsAutoConfiguration { 9 | static void main(String[] args) { 10 | GrailsApp.run(Application, args) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/testapp1/grails-app/init/testapp1/BootStrap.groovy: -------------------------------------------------------------------------------- 1 | package testapp1 2 | 3 | class BootStrap { 4 | 5 | GreenMailService greenMailService 6 | 7 | def init = { 8 | greenMailService.start() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/testapp1/grails-app/services/testapp1/GreenMailService.groovy: -------------------------------------------------------------------------------- 1 | package testapp1 2 | 3 | import com.icegreen.greenmail.util.GreenMail 4 | import com.icegreen.greenmail.util.ServerSetupTest 5 | import groovy.util.logging.Slf4j 6 | import jakarta.annotation.PreDestroy 7 | 8 | @Slf4j 9 | class GreenMailService { 10 | 11 | GreenMail greenMail 12 | 13 | void start() { 14 | greenMail = new GreenMail(ServerSetupTest.SMTP) 15 | greenMail.start() 16 | log.info('Greenmail started on port {}', greenMail.getSmtp().getPort()) 17 | } 18 | 19 | void reset() { 20 | if (greenMail) { 21 | greenMail.reset() 22 | log.debug('Greenmail reset') 23 | } 24 | } 25 | 26 | @PreDestroy 27 | void stop() { 28 | if (greenMail) { 29 | greenMail.stop() 30 | log.info('Greenmail stopped') 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /examples/testapp1/grails-app/views/_testemails/i18ntest.gsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/html" %> 2 | 3 | 4 | -------------------------------------------------------------------------------- /examples/testapp1/grails-app/views/_testemails/newLineTagTest.gsp: -------------------------------------------------------------------------------- 1 | ${part1}${part2} -------------------------------------------------------------------------------- /examples/testapp1/grails-app/views/_testemails/newLineTest.gsp: -------------------------------------------------------------------------------- 1 | ${part1} 2 | ${part2} -------------------------------------------------------------------------------- /examples/testapp1/grails-app/views/_testemails/tagtest.gsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/plain" %> 2 | Condition is true 3 | -------------------------------------------------------------------------------- /examples/testapp1/grails-app/views/_testemails/test.gsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/plain" %> 2 | Message is: ${msg} 3 | -------------------------------------------------------------------------------- /examples/testapp1/grails-app/views/_testemails/testhtml.gsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/html" %> 2 | Message is: ${msg} 3 | -------------------------------------------------------------------------------- /examples/testapp1/grails-app/views/error.gsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <g:if env="development">Grails Runtime Exception</g:if><g:else>Error</g:else> 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
    19 |
  • An error has occurred
  • 20 |
  • Exception: ${exception}
  • 21 |
  • Message: ${message}
  • 22 |
  • Path: ${path}
  • 23 |
24 |
25 |
26 | 27 |
    28 |
  • An error has occurred
  • 29 |
30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/testapp1/grails-app/views/greenMail/greenMail.gsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | Greenmail Inbox 4 | 17 | 18 | 19 |

📬 Greenmail Inbox

20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
FromToSubjectBody
${msg.from}${msg.to}${msg.subject}${msg.body}
37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /examples/testapp1/grails-app/views/index.gsp: -------------------------------------------------------------------------------- 1 | <%@ page import="grails.util.Environment; org.springframework.core.SpringVersion; org.springframework.boot.SpringBootVersion" 2 | %> 3 | 4 | 5 | 6 | Welcome to Grails 7 | 8 | 9 | 10 | 32 | 41 | 49 | 50 | 51 | 56 | 57 |
58 |
59 |
60 |

Welcome to Grails

61 | 62 |

63 | Congratulations, you have successfully started your first Grails application! At the moment 64 | this is the default page, feel free to modify it to either redirect to a controller or display 65 | whatever content you may choose. Below is a list of controllers that are currently deployed in 66 | this application, click on each to execute its default action: 67 |

68 | 69 | 79 |
80 |
81 |
82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /examples/testapp1/grails-app/views/layouts/main.gsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <g:layoutTitle default="Grails"/> 8 | 9 | 10 |