├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── examples ├── nestjs-example │ ├── .env.example │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── nest-cli.json │ ├── package.json │ ├── src │ │ ├── app.controller.spec.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── config │ │ │ └── mailer.config.ts │ │ ├── main.ts │ │ └── templates │ │ │ └── welcome.tsx │ ├── test │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json └── nestjs-with-swc-example │ ├── .env.example │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── gulpfile.js │ ├── nest-cli.json │ ├── package.json │ ├── src │ ├── app.controller.spec.ts │ ├── app.controller.ts │ ├── app.module.ts │ ├── app.service.ts │ ├── config │ │ └── mailer.config.ts │ ├── main.ts │ └── templates │ │ └── welcome.tsx │ ├── test │ ├── app.e2e-spec.ts │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── package.json ├── src ├── index.ts └── react.adapter.tsx ├── tsconfig.json └── tsup.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | package-lock.json 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # TypeScript v1 declaration files 46 | typings/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | 76 | # parcel-bundler cache (https://parceljs.org/) 77 | .cache 78 | 79 | # Next.js build output 80 | .next 81 | 82 | # Nuxt.js build / generate output 83 | .nuxt 84 | dist 85 | 86 | # Gatsby files 87 | .cache/ 88 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 89 | # https://nextjs.org/blog/next-9-1#public-directory-support 90 | # public 91 | 92 | # vuepress build output 93 | .vuepress/dist 94 | 95 | # Serverless directories 96 | .serverless/ 97 | 98 | # FuseBox cache 99 | .fusebox/ 100 | 101 | # DynamoDB Local files 102 | .dynamodb/ 103 | 104 | # TernJS port file 105 | .tern-port 106 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # source 2 | src 3 | examples 4 | package-lock.json 5 | tsconfig.json 6 | tsup.config.ts 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Webtre Technologies 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Nest Logo 4 | 5 |

6 | 7 |

8 | 📨 Build and send emails in Nest framework using React.js 9 |

10 | 11 |

12 | NPM Version 13 | Package License 14 | NPM Downloads 15 |

16 | 17 | ## Features 18 | 19 | - 🦾 Write your email templates in [React](https://github.com/facebook/react/) and [TypeScript](https://www.typescriptlang.org/) 20 | 21 | - 📬 No more template not found / sending blank emails. 22 | 23 | - 🔰 No more missing context / variables from template. 24 | 25 | - 🧪 Write testable templates intended for email clients. 26 | 27 | - 💌 Built on top of [`react-email`](https://github.com/resendlabs/react-email) — the next generation of writing emails. 28 | 29 | ## Installation 30 | 31 | > This library is an adapter for the [`@nestjs-modules/mailer`](https://github.com/nest-modules/mailer) module, so we'll install the dependencies alongside by running the command below. 32 | 33 | ```sh 34 | npm i @webtre/nestjs-mailer-react-adapter @nestjs-modules/mailer nodemailer 35 | ``` 36 | 37 | ### Getting Started 38 | 39 | To add support for `React` to your project, modify `tsconfig.json` 40 | 41 | ```javascript 42 | { 43 | "compilerOptions": { 44 | // add this line 45 | "jsx": "react-jsx" 46 | } 47 | } 48 | ``` 49 | 50 | ### Configuration 51 | 52 | ```javascript 53 | // src/app.module.ts 54 | import { Module } from "@nestjs/common"; 55 | import { MailerModule } from "@nestjs-modules/mailer"; 56 | import { ReactAdapter } from "@webtre/nestjs-mailer-react-adapter"; 57 | 58 | @Module({ 59 | imports: [ 60 | MailerModule.forRoot({ 61 | transport: { 62 | host: "smtp.domain.com", 63 | port: 2525, 64 | secure: false, 65 | auth: { 66 | user: "user@domain.com", 67 | pass: "password", 68 | }, 69 | }, 70 | defaults: { 71 | from: '"Webtre Technologies" ', 72 | }, 73 | template: { 74 | dir: __dirname + "/templates", 75 | // Use the adapter 76 | adapter: new ReactAdapter(), 77 | 78 | // Or with optional config 79 | adapter: new ReactAdapter({ 80 | pretty: false, 81 | plainText: true, 82 | htmlToTextOptions: { 83 | wordwrap: 130, 84 | limits: { 85 | ellipsis: "...", 86 | }, 87 | }, 88 | }), 89 | }, 90 | }), 91 | ], 92 | }) 93 | export class AppModule {} 94 | ``` 95 | 96 | To see more options that can be passed to the `htmlToTextOptions` object, [click here](https://github.com/html-to-text/node-html-to-text/tree/master/packages/html-to-text#options). 97 | 98 | ### Service Provider 99 | 100 | ```javascript 101 | import { Injectable } from '@nestjs/common'; 102 | import { MailerService } from '@nestjs-modules/mailer'; 103 | 104 | @Injectable() 105 | export class ExampleService { 106 | constructor(private readonly mailerService: MailerService) {} 107 | 108 | async public example(): Promise { 109 | await this.mailerService 110 | .sendMail({ 111 | to: 'john@domain.com', 112 | subject: 'Testing react template', 113 | template: 'welcome', // The compiled extension is appended automatically. 114 | context: { // Data to be passed as props to your template. 115 | code: '123456', 116 | name: 'John Doe', 117 | }, 118 | }); 119 | } 120 | } 121 | ``` 122 | 123 | ### React Template 124 | 125 | ```javascript 126 | // src/templates/welcome.tsx 127 | interface Props { 128 | code: string; 129 | name: string; 130 | } 131 | 132 | export default function Welcome({ name, code }: Props) { 133 | return ( 134 |
135 | Hi {name}, thanks for signing up. Your code is {code} 136 |
137 | ); 138 | } 139 | ``` 140 | 141 | ## Examples 142 | 143 | You could also check the [examples folder](./examples) in this repo for a working usage example and how to set it up with [SWC](https://docs.nestjs.com/recipes/swc) using [gulp](https://gulpjs.com/). 144 | 145 | ## Credits 146 | 147 | - [`react-email`](https://github.com/resendlabs/react-email) — build and send emails using React 148 | - [`@nestjs-modules/mailer`](https://github.com/nest-modules/mailer) — a mailer module for Nest framework (node.js) 149 | 150 | ## License 151 | 152 | [MIT](./LICENSE) License © 2022 [Webtre Technologies](https://github.com/webtretech) 153 | -------------------------------------------------------------------------------- /examples/nestjs-example/.env.example: -------------------------------------------------------------------------------- 1 | MAIL_SERVICE=smtp 2 | MAIL_HOST= 3 | MAIL_PORT=2525 4 | MAIL_USERNAME= 5 | MAIL_PASSWORD= 6 | MAIL_FROM_ADDRESS=hello@domain.com 7 | MAIL_FROM_NAME="Webtre Technologies" 8 | -------------------------------------------------------------------------------- /examples/nestjs-example/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir: __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /examples/nestjs-example/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff: 7 | .idea/**/workspace.xml 8 | .idea/**/tasks.xml 9 | .idea/dictionaries 10 | 11 | # Sensitive or high-churn files: 12 | .idea/**/dataSources/ 13 | .idea/**/dataSources.ids 14 | .idea/**/dataSources.xml 15 | .idea/**/dataSources.local.xml 16 | .idea/**/sqlDataSources.xml 17 | .idea/**/dynamic.xml 18 | .idea/**/uiDesigner.xml 19 | 20 | # Gradle: 21 | .idea/**/gradle.xml 22 | .idea/**/libraries 23 | 24 | # CMake 25 | cmake-build-debug/ 26 | 27 | # Mongo Explorer plugin: 28 | .idea/**/mongoSettings.xml 29 | 30 | ## File-based project format: 31 | *.iws 32 | 33 | ## Plugin-specific files: 34 | 35 | # IntelliJ 36 | out/ 37 | 38 | # mpeltonen/sbt-idea plugin 39 | .idea_modules/ 40 | 41 | # JIRA plugin 42 | atlassian-ide-plugin.xml 43 | 44 | # Cursive Clojure plugin 45 | .idea/replstate.xml 46 | 47 | # Crashlytics plugin (for Android Studio and IntelliJ) 48 | com_crashlytics_export_strings.xml 49 | crashlytics.properties 50 | crashlytics-build.properties 51 | fabric.properties 52 | ### VisualStudio template 53 | ## Ignore Visual Studio temporary files, build results, and 54 | ## files generated by popular Visual Studio add-ons. 55 | ## 56 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 57 | 58 | # User-specific files 59 | *.suo 60 | *.user 61 | *.userosscache 62 | *.sln.docstates 63 | 64 | # User-specific files (MonoDevelop/Xamarin Studio) 65 | *.userprefs 66 | 67 | # Build results 68 | [Dd]ebug/ 69 | [Dd]ebugPublic/ 70 | [Rr]elease/ 71 | [Rr]eleases/ 72 | x64/ 73 | x86/ 74 | bld/ 75 | [Bb]in/ 76 | [Oo]bj/ 77 | [Ll]og/ 78 | 79 | # Visual Studio 2015 cache/options directory 80 | .vs/ 81 | # Uncomment if you have tasks that create the project's static files in wwwroot 82 | #wwwroot/ 83 | 84 | # MSTest test Results 85 | [Tt]est[Rr]esult*/ 86 | [Bb]uild[Ll]og.* 87 | 88 | # NUNIT 89 | *.VisualState.xml 90 | TestResult.xml 91 | 92 | # Build Results of an ATL Project 93 | [Dd]ebugPS/ 94 | [Rr]eleasePS/ 95 | dlldata.c 96 | 97 | # Benchmark Results 98 | BenchmarkDotNet.Artifacts/ 99 | 100 | # .NET Core 101 | project.lock.json 102 | project.fragment.lock.json 103 | artifacts/ 104 | **/Properties/launchSettings.json 105 | 106 | *_i.c 107 | *_p.c 108 | *_i.h 109 | *.ilk 110 | *.meta 111 | *.obj 112 | *.pch 113 | *.pdb 114 | *.pgc 115 | *.pgd 116 | *.rsp 117 | *.sbr 118 | *.tlb 119 | *.tli 120 | *.tlh 121 | *.tmp 122 | *.tmp_proj 123 | *.log 124 | *.vspscc 125 | *.vssscc 126 | .builds 127 | *.pidb 128 | *.svclog 129 | *.scc 130 | 131 | # Chutzpah Test files 132 | _Chutzpah* 133 | 134 | # Visual C++ cache files 135 | ipch/ 136 | *.aps 137 | *.ncb 138 | *.opendb 139 | *.opensdf 140 | *.sdf 141 | *.cachefile 142 | *.VC.db 143 | *.VC.VC.opendb 144 | 145 | # Visual Studio profiler 146 | *.psess 147 | *.vsp 148 | *.vspx 149 | *.sap 150 | 151 | # Visual Studio Trace Files 152 | *.e2e 153 | 154 | # TFS 2012 Local Workspace 155 | $tf/ 156 | 157 | # Guidance Automation Toolkit 158 | *.gpState 159 | 160 | # ReSharper is a .NET coding add-in 161 | _ReSharper*/ 162 | *.[Rr]e[Ss]harper 163 | *.DotSettings.user 164 | 165 | # JustCode is a .NET coding add-in 166 | .JustCode 167 | 168 | # TeamCity is a build add-in 169 | _TeamCity* 170 | 171 | # DotCover is a Code Coverage Tool 172 | *.dotCover 173 | 174 | # AxoCover is a Code Coverage Tool 175 | .axoCover/* 176 | !.axoCover/settings.json 177 | 178 | # Visual Studio code coverage results 179 | *.coverage 180 | *.coveragexml 181 | 182 | # NCrunch 183 | _NCrunch_* 184 | .*crunch*.local.xml 185 | nCrunchTemp_* 186 | 187 | # MightyMoose 188 | *.mm.* 189 | AutoTest.Net/ 190 | 191 | # Web workbench (sass) 192 | .sass-cache/ 193 | 194 | # Installshield output folder 195 | [Ee]xpress/ 196 | 197 | # DocProject is a documentation generator add-in 198 | DocProject/buildhelp/ 199 | DocProject/Help/*.HxT 200 | DocProject/Help/*.HxC 201 | DocProject/Help/*.hhc 202 | DocProject/Help/*.hhk 203 | DocProject/Help/*.hhp 204 | DocProject/Help/Html2 205 | DocProject/Help/html 206 | 207 | # Click-Once directory 208 | publish/ 209 | 210 | # Publish Web Output 211 | *.[Pp]ublish.xml 212 | *.azurePubxml 213 | # Note: Comment the next line if you want to checkin your web deploy settings, 214 | # but database connection strings (with potential passwords) will be unencrypted 215 | *.pubxml 216 | *.publishproj 217 | 218 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 219 | # checkin your Azure Web App publish settings, but sensitive information contained 220 | # in these scripts will be unencrypted 221 | PublishScripts/ 222 | 223 | # NuGet Packages 224 | *.nupkg 225 | # The packages folder can be ignored because of Package Restore 226 | **/[Pp]ackages/* 227 | # except build/, which is used as an MSBuild target. 228 | !**/[Pp]ackages/build/ 229 | # Uncomment if necessary however generally it will be regenerated when needed 230 | #!**/[Pp]ackages/repositories.config 231 | # NuGet v3's project.json files produces more ignorable files 232 | *.nuget.props 233 | *.nuget.targets 234 | 235 | # Microsoft Azure Build Output 236 | csx/ 237 | *.build.csdef 238 | 239 | # Microsoft Azure Emulator 240 | ecf/ 241 | rcf/ 242 | 243 | # Windows Store app package directories and files 244 | AppPackages/ 245 | BundleArtifacts/ 246 | Package.StoreAssociation.xml 247 | _pkginfo.txt 248 | *.appx 249 | 250 | # Visual Studio cache files 251 | # files ending in .cache can be ignored 252 | *.[Cc]ache 253 | # but keep track of directories ending in .cache 254 | !*.[Cc]ache/ 255 | 256 | # Others 257 | ClientBin/ 258 | ~$* 259 | *~ 260 | *.dbmdl 261 | *.dbproj.schemaview 262 | *.jfm 263 | *.pfx 264 | *.publishsettings 265 | orleans.codegen.cs 266 | 267 | # Since there are multiple workflows, uncomment next line to ignore bower_components 268 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 269 | #bower_components/ 270 | 271 | # RIA/Silverlight projects 272 | Generated_Code/ 273 | 274 | # Backup & report files from converting an old project file 275 | # to a newer Visual Studio version. Backup files are not needed, 276 | # because we have git ;-) 277 | _UpgradeReport_Files/ 278 | Backup*/ 279 | UpgradeLog*.XML 280 | UpgradeLog*.htm 281 | 282 | # SQL Server files 283 | *.mdf 284 | *.ldf 285 | *.ndf 286 | 287 | # Business Intelligence projects 288 | *.rdl.data 289 | *.bim.layout 290 | *.bim_*.settings 291 | 292 | # Microsoft Fakes 293 | FakesAssemblies/ 294 | 295 | # GhostDoc plugin setting file 296 | *.GhostDoc.xml 297 | 298 | # Node.js Tools for Visual Studio 299 | .ntvs_analysis.dat 300 | node_modules/ 301 | 302 | # Typescript v1 declaration files 303 | typings/ 304 | 305 | # Visual Studio 6 build log 306 | *.plg 307 | 308 | # Visual Studio 6 workspace options file 309 | *.opt 310 | 311 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 312 | *.vbw 313 | 314 | # Visual Studio LightSwitch build output 315 | **/*.HTMLClient/GeneratedArtifacts 316 | **/*.DesktopClient/GeneratedArtifacts 317 | **/*.DesktopClient/ModelManifest.xml 318 | **/*.Server/GeneratedArtifacts 319 | **/*.Server/ModelManifest.xml 320 | _Pvt_Extensions 321 | 322 | # Paket dependency manager 323 | .paket/paket.exe 324 | paket-files/ 325 | 326 | # FAKE - F# Make 327 | .fake/ 328 | 329 | # JetBrains Rider 330 | .idea/ 331 | *.sln.iml 332 | 333 | # IDE - VSCode 334 | .vscode/* 335 | !.vscode/settings.json 336 | !.vscode/tasks.json 337 | !.vscode/launch.json 338 | !.vscode/extensions.json 339 | 340 | # CodeRush 341 | .cr/ 342 | 343 | # Python Tools for Visual Studio (PTVS) 344 | __pycache__/ 345 | *.pyc 346 | 347 | # Cake - Uncomment if you are using it 348 | # tools/** 349 | # !tools/packages.config 350 | 351 | # Tabs Studio 352 | *.tss 353 | 354 | # Telerik's JustMock configuration file 355 | *.jmconfig 356 | 357 | # BizTalk build output 358 | *.btp.cs 359 | *.btm.cs 360 | *.odx.cs 361 | *.xsd.cs 362 | 363 | # OpenCover UI analysis results 364 | OpenCover/ 365 | coverage/ 366 | 367 | ### macOS template 368 | # General 369 | .DS_Store 370 | .AppleDouble 371 | .LSOverride 372 | 373 | # Icon must end with two \r 374 | Icon 375 | 376 | # Thumbnails 377 | ._* 378 | 379 | # Files that might appear in the root of a volume 380 | .DocumentRevisions-V100 381 | .fseventsd 382 | .Spotlight-V100 383 | .TemporaryItems 384 | .Trashes 385 | .VolumeIcon.icns 386 | .com.apple.timemachine.donotpresent 387 | 388 | # Directories potentially created on remote AFP share 389 | .AppleDB 390 | .AppleDesktop 391 | Network Trash Folder 392 | Temporary Items 393 | .apdisk 394 | 395 | ======= 396 | # Local 397 | .env 398 | dist 399 | .webpack 400 | .serverless/**/*.zip 401 | -------------------------------------------------------------------------------- /examples/nestjs-example/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } 5 | -------------------------------------------------------------------------------- /examples/nestjs-example/README.md: -------------------------------------------------------------------------------- 1 | ## Project setup 2 | 3 | ```bash 4 | $ npm install 5 | ``` 6 | 7 | ## Compile and run the project 8 | 9 | ```bash 10 | # development 11 | $ npm run start 12 | 13 | # watch mode 14 | $ npm run start:dev 15 | 16 | # production mode 17 | $ npm run start:prod 18 | ``` 19 | 20 | ## Run tests 21 | 22 | ```bash 23 | # unit tests 24 | $ npm run test 25 | 26 | # e2e tests 27 | $ npm run test:e2e 28 | 29 | # test coverage 30 | $ npm run test:cov 31 | ``` 32 | -------------------------------------------------------------------------------- /examples/nestjs-example/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/nestjs-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@webtre/nestjs-mailer-react-adapter-example", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "This is a demo using @webtre/nestjs-mailer-react-adapter", 6 | "license": "MIT", 7 | "scripts": { 8 | "build": "nest build", 9 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 10 | "start": "nest start", 11 | "start:dev": "nest start --watch", 12 | "start:debug": "nest start --debug --watch", 13 | "start:prod": "node dist/main", 14 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 15 | "test": "jest", 16 | "test:watch": "jest --watch", 17 | "test:cov": "jest --coverage", 18 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/jest/bin/jest --runInBand", 19 | "test:e2e": "jest --config ./test/jest-e2e.json" 20 | }, 21 | "dependencies": { 22 | "@nestjs-modules/mailer": "^2.0.2", 23 | "@nestjs/common": "^11.0.11", 24 | "@nestjs/config": "^4.0.1", 25 | "@nestjs/core": "^11.0.11", 26 | "@nestjs/platform-express": "^11.0.11", 27 | "@webtre/nestjs-mailer-react-adapter": "^0.2.4", 28 | "nodemailer": "^6.10.0", 29 | "reflect-metadata": "^0.2.2", 30 | "rxjs": "^7.8.2" 31 | }, 32 | "devDependencies": { 33 | "@nestjs/cli": "^11.0.5", 34 | "@nestjs/schematics": "^11.0.2", 35 | "@nestjs/testing": "^11.0.11", 36 | "@swc/cli": "^0.6.0", 37 | "@swc/core": "^1.11.8", 38 | "@types/express": "^5.0.0", 39 | "@types/jest": "^29.5.14", 40 | "@types/node": "^22.13.10", 41 | "@types/supertest": "^6.0.2", 42 | "@typescript-eslint/eslint-plugin": "^6.21.0", 43 | "@typescript-eslint/parser": "^6.21.0", 44 | "eslint": "^8.56.0", 45 | "eslint-config-prettier": "^9.1.0", 46 | "eslint-plugin-prettier": "^5.1.3", 47 | "jest": "^29.7.0", 48 | "prettier": "^3.5.3", 49 | "source-map-support": "^0.5.21", 50 | "supertest": "^7.0.0", 51 | "ts-jest": "^29.2.6", 52 | "ts-loader": "^9.5.2", 53 | "ts-node": "^10.9.2", 54 | "tsconfig-paths": "^4.2.0", 55 | "typescript": "^5.3.3" 56 | }, 57 | "jest": { 58 | "moduleFileExtensions": [ 59 | "js", 60 | "json", 61 | "ts" 62 | ], 63 | "rootDir": "src", 64 | "testRegex": ".*\\.spec\\.ts$", 65 | "transform": { 66 | "^.+\\.(t|j)s$": "ts-jest" 67 | }, 68 | "collectCoverageFrom": [ 69 | "**/*.(t|j)s" 70 | ], 71 | "coverageDirectory": "../coverage", 72 | "testEnvironment": "node" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /examples/nestjs-example/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let app: TestingModule; 7 | 8 | beforeAll(async () => { 9 | app = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | }); 14 | 15 | describe('getHello', () => { 16 | it('should return "Hello World!"', () => { 17 | const appController = app.get(AppController); 18 | expect(appController.getHello()).toBe('Hello World!'); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /examples/nestjs-example/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | welcome(): Promise { 10 | return this.appService.sendWelcomeMail(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/nestjs-example/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { MailerModule } from '@nestjs-modules/mailer'; 2 | import { Module } from '@nestjs/common'; 3 | import { ConfigModule, ConfigType } from '@nestjs/config'; 4 | import { ReactAdapter } from '@webtre/nestjs-mailer-react-adapter'; 5 | import { AppController } from './app.controller'; 6 | import { AppService } from './app.service'; 7 | import mailerConfig from './config/mailer.config'; 8 | 9 | @Module({ 10 | imports: [ 11 | ConfigModule.forRoot({ 12 | isGlobal: true, 13 | load: [mailerConfig], 14 | envFilePath: __dirname + '/../.env', 15 | }), 16 | MailerModule.forRootAsync({ 17 | inject: [mailerConfig.KEY], 18 | useFactory: (mailerConf: ConfigType) => ({ 19 | ...mailerConf, 20 | template: { 21 | adapter: new ReactAdapter(), 22 | dir: __dirname + '/templates', 23 | }, 24 | }), 25 | }), 26 | ], 27 | controllers: [AppController], 28 | providers: [AppService], 29 | }) 30 | export class AppModule {} 31 | -------------------------------------------------------------------------------- /examples/nestjs-example/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { MailerService } from '@nestjs-modules/mailer'; 2 | import { Injectable } from '@nestjs/common'; 3 | 4 | @Injectable() 5 | export class AppService { 6 | constructor(private readonly mailerService: MailerService) {} 7 | 8 | public async sendWelcomeMail(): Promise { 9 | await this.mailerService.sendMail({ 10 | to: 'john@domain.com', 11 | subject: 'Testing react template', 12 | template: 'welcome', // The compiled extension is appended automatically. 13 | context: { 14 | // Data to be passed as props to your template. 15 | code: '123456', 16 | name: 'John Doe', 17 | }, 18 | }); 19 | 20 | return 'Mail sent!'; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/nestjs-example/src/config/mailer.config.ts: -------------------------------------------------------------------------------- 1 | import { MailerOptions } from '@nestjs-modules/mailer'; 2 | import { registerAs } from '@nestjs/config'; 3 | 4 | export default registerAs('mailer', (): MailerOptions => { 5 | return { 6 | transport: { 7 | service: process.env.MAIL_SERVICE, 8 | host: process.env.MAIL_HOST, 9 | port: parseInt(process.env.MAIL_PORT, 10), 10 | auth: { 11 | user: process.env.MAIL_USERNAME, 12 | pass: process.env.MAIL_PASSWORD, 13 | }, 14 | }, 15 | defaults: { 16 | from: { 17 | name: process.env.MAIL_FROM_NAME, 18 | address: process.env.MAIL_FROM_ADDRESS, 19 | }, 20 | }, 21 | }; 22 | }); 23 | -------------------------------------------------------------------------------- /examples/nestjs-example/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | 4 | async function bootstrap() { 5 | const app = await NestFactory.create(AppModule); 6 | await app.listen(3000); 7 | } 8 | bootstrap(); 9 | -------------------------------------------------------------------------------- /examples/nestjs-example/src/templates/welcome.tsx: -------------------------------------------------------------------------------- 1 | interface Props { 2 | code: string; 3 | name: string; 4 | } 5 | 6 | export default function Welcome({ name, code }: Props) { 7 | return ( 8 |
9 | Hi {name}, thanks for signing up. Your code is {code} 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /examples/nestjs-example/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { Test } from '@nestjs/testing'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from '../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeAll(async () => { 10 | const moduleFixture = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | afterAll(async () => { 19 | await app.close(); 20 | }); 21 | 22 | it('/ (GET)', () => { 23 | return request(app.getHttpServer()) 24 | .get('/') 25 | .expect(200) 26 | .expect('Hello World!'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /examples/nestjs-example/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": "\\.e2e-spec\\.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/nestjs-example/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /examples/nestjs-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2021", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "jsx": "react-jsx" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/nestjs-with-swc-example/.env.example: -------------------------------------------------------------------------------- 1 | MAIL_SERVICE=smtp 2 | MAIL_HOST= 3 | MAIL_PORT=2525 4 | MAIL_USERNAME= 5 | MAIL_PASSWORD= 6 | MAIL_FROM_ADDRESS=hello@domain.com 7 | MAIL_FROM_NAME="Webtre Technologies" 8 | -------------------------------------------------------------------------------- /examples/nestjs-with-swc-example/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir: __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /examples/nestjs-with-swc-example/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff: 7 | .idea/**/workspace.xml 8 | .idea/**/tasks.xml 9 | .idea/dictionaries 10 | 11 | # Sensitive or high-churn files: 12 | .idea/**/dataSources/ 13 | .idea/**/dataSources.ids 14 | .idea/**/dataSources.xml 15 | .idea/**/dataSources.local.xml 16 | .idea/**/sqlDataSources.xml 17 | .idea/**/dynamic.xml 18 | .idea/**/uiDesigner.xml 19 | 20 | # Gradle: 21 | .idea/**/gradle.xml 22 | .idea/**/libraries 23 | 24 | # CMake 25 | cmake-build-debug/ 26 | 27 | # Mongo Explorer plugin: 28 | .idea/**/mongoSettings.xml 29 | 30 | ## File-based project format: 31 | *.iws 32 | 33 | ## Plugin-specific files: 34 | 35 | # IntelliJ 36 | out/ 37 | 38 | # mpeltonen/sbt-idea plugin 39 | .idea_modules/ 40 | 41 | # JIRA plugin 42 | atlassian-ide-plugin.xml 43 | 44 | # Cursive Clojure plugin 45 | .idea/replstate.xml 46 | 47 | # Crashlytics plugin (for Android Studio and IntelliJ) 48 | com_crashlytics_export_strings.xml 49 | crashlytics.properties 50 | crashlytics-build.properties 51 | fabric.properties 52 | ### VisualStudio template 53 | ## Ignore Visual Studio temporary files, build results, and 54 | ## files generated by popular Visual Studio add-ons. 55 | ## 56 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 57 | 58 | # User-specific files 59 | *.suo 60 | *.user 61 | *.userosscache 62 | *.sln.docstates 63 | 64 | # User-specific files (MonoDevelop/Xamarin Studio) 65 | *.userprefs 66 | 67 | # Build results 68 | [Dd]ebug/ 69 | [Dd]ebugPublic/ 70 | [Rr]elease/ 71 | [Rr]eleases/ 72 | x64/ 73 | x86/ 74 | bld/ 75 | [Bb]in/ 76 | [Oo]bj/ 77 | [Ll]og/ 78 | 79 | # Visual Studio 2015 cache/options directory 80 | .vs/ 81 | # Uncomment if you have tasks that create the project's static files in wwwroot 82 | #wwwroot/ 83 | 84 | # MSTest test Results 85 | [Tt]est[Rr]esult*/ 86 | [Bb]uild[Ll]og.* 87 | 88 | # NUNIT 89 | *.VisualState.xml 90 | TestResult.xml 91 | 92 | # Build Results of an ATL Project 93 | [Dd]ebugPS/ 94 | [Rr]eleasePS/ 95 | dlldata.c 96 | 97 | # Benchmark Results 98 | BenchmarkDotNet.Artifacts/ 99 | 100 | # .NET Core 101 | project.lock.json 102 | project.fragment.lock.json 103 | artifacts/ 104 | **/Properties/launchSettings.json 105 | 106 | *_i.c 107 | *_p.c 108 | *_i.h 109 | *.ilk 110 | *.meta 111 | *.obj 112 | *.pch 113 | *.pdb 114 | *.pgc 115 | *.pgd 116 | *.rsp 117 | *.sbr 118 | *.tlb 119 | *.tli 120 | *.tlh 121 | *.tmp 122 | *.tmp_proj 123 | *.log 124 | *.vspscc 125 | *.vssscc 126 | .builds 127 | *.pidb 128 | *.svclog 129 | *.scc 130 | 131 | # Chutzpah Test files 132 | _Chutzpah* 133 | 134 | # Visual C++ cache files 135 | ipch/ 136 | *.aps 137 | *.ncb 138 | *.opendb 139 | *.opensdf 140 | *.sdf 141 | *.cachefile 142 | *.VC.db 143 | *.VC.VC.opendb 144 | 145 | # Visual Studio profiler 146 | *.psess 147 | *.vsp 148 | *.vspx 149 | *.sap 150 | 151 | # Visual Studio Trace Files 152 | *.e2e 153 | 154 | # TFS 2012 Local Workspace 155 | $tf/ 156 | 157 | # Guidance Automation Toolkit 158 | *.gpState 159 | 160 | # ReSharper is a .NET coding add-in 161 | _ReSharper*/ 162 | *.[Rr]e[Ss]harper 163 | *.DotSettings.user 164 | 165 | # JustCode is a .NET coding add-in 166 | .JustCode 167 | 168 | # TeamCity is a build add-in 169 | _TeamCity* 170 | 171 | # DotCover is a Code Coverage Tool 172 | *.dotCover 173 | 174 | # AxoCover is a Code Coverage Tool 175 | .axoCover/* 176 | !.axoCover/settings.json 177 | 178 | # Visual Studio code coverage results 179 | *.coverage 180 | *.coveragexml 181 | 182 | # NCrunch 183 | _NCrunch_* 184 | .*crunch*.local.xml 185 | nCrunchTemp_* 186 | 187 | # MightyMoose 188 | *.mm.* 189 | AutoTest.Net/ 190 | 191 | # Web workbench (sass) 192 | .sass-cache/ 193 | 194 | # Installshield output folder 195 | [Ee]xpress/ 196 | 197 | # DocProject is a documentation generator add-in 198 | DocProject/buildhelp/ 199 | DocProject/Help/*.HxT 200 | DocProject/Help/*.HxC 201 | DocProject/Help/*.hhc 202 | DocProject/Help/*.hhk 203 | DocProject/Help/*.hhp 204 | DocProject/Help/Html2 205 | DocProject/Help/html 206 | 207 | # Click-Once directory 208 | publish/ 209 | 210 | # Publish Web Output 211 | *.[Pp]ublish.xml 212 | *.azurePubxml 213 | # Note: Comment the next line if you want to checkin your web deploy settings, 214 | # but database connection strings (with potential passwords) will be unencrypted 215 | *.pubxml 216 | *.publishproj 217 | 218 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 219 | # checkin your Azure Web App publish settings, but sensitive information contained 220 | # in these scripts will be unencrypted 221 | PublishScripts/ 222 | 223 | # NuGet Packages 224 | *.nupkg 225 | # The packages folder can be ignored because of Package Restore 226 | **/[Pp]ackages/* 227 | # except build/, which is used as an MSBuild target. 228 | !**/[Pp]ackages/build/ 229 | # Uncomment if necessary however generally it will be regenerated when needed 230 | #!**/[Pp]ackages/repositories.config 231 | # NuGet v3's project.json files produces more ignorable files 232 | *.nuget.props 233 | *.nuget.targets 234 | 235 | # Microsoft Azure Build Output 236 | csx/ 237 | *.build.csdef 238 | 239 | # Microsoft Azure Emulator 240 | ecf/ 241 | rcf/ 242 | 243 | # Windows Store app package directories and files 244 | AppPackages/ 245 | BundleArtifacts/ 246 | Package.StoreAssociation.xml 247 | _pkginfo.txt 248 | *.appx 249 | 250 | # Visual Studio cache files 251 | # files ending in .cache can be ignored 252 | *.[Cc]ache 253 | # but keep track of directories ending in .cache 254 | !*.[Cc]ache/ 255 | 256 | # Others 257 | ClientBin/ 258 | ~$* 259 | *~ 260 | *.dbmdl 261 | *.dbproj.schemaview 262 | *.jfm 263 | *.pfx 264 | *.publishsettings 265 | orleans.codegen.cs 266 | 267 | # Since there are multiple workflows, uncomment next line to ignore bower_components 268 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 269 | #bower_components/ 270 | 271 | # RIA/Silverlight projects 272 | Generated_Code/ 273 | 274 | # Backup & report files from converting an old project file 275 | # to a newer Visual Studio version. Backup files are not needed, 276 | # because we have git ;-) 277 | _UpgradeReport_Files/ 278 | Backup*/ 279 | UpgradeLog*.XML 280 | UpgradeLog*.htm 281 | 282 | # SQL Server files 283 | *.mdf 284 | *.ldf 285 | *.ndf 286 | 287 | # Business Intelligence projects 288 | *.rdl.data 289 | *.bim.layout 290 | *.bim_*.settings 291 | 292 | # Microsoft Fakes 293 | FakesAssemblies/ 294 | 295 | # GhostDoc plugin setting file 296 | *.GhostDoc.xml 297 | 298 | # Node.js Tools for Visual Studio 299 | .ntvs_analysis.dat 300 | node_modules/ 301 | 302 | # Typescript v1 declaration files 303 | typings/ 304 | 305 | # Visual Studio 6 build log 306 | *.plg 307 | 308 | # Visual Studio 6 workspace options file 309 | *.opt 310 | 311 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 312 | *.vbw 313 | 314 | # Visual Studio LightSwitch build output 315 | **/*.HTMLClient/GeneratedArtifacts 316 | **/*.DesktopClient/GeneratedArtifacts 317 | **/*.DesktopClient/ModelManifest.xml 318 | **/*.Server/GeneratedArtifacts 319 | **/*.Server/ModelManifest.xml 320 | _Pvt_Extensions 321 | 322 | # Paket dependency manager 323 | .paket/paket.exe 324 | paket-files/ 325 | 326 | # FAKE - F# Make 327 | .fake/ 328 | 329 | # JetBrains Rider 330 | .idea/ 331 | *.sln.iml 332 | 333 | # IDE - VSCode 334 | .vscode/* 335 | !.vscode/settings.json 336 | !.vscode/tasks.json 337 | !.vscode/launch.json 338 | !.vscode/extensions.json 339 | 340 | # CodeRush 341 | .cr/ 342 | 343 | # Python Tools for Visual Studio (PTVS) 344 | __pycache__/ 345 | *.pyc 346 | 347 | # Cake - Uncomment if you are using it 348 | # tools/** 349 | # !tools/packages.config 350 | 351 | # Tabs Studio 352 | *.tss 353 | 354 | # Telerik's JustMock configuration file 355 | *.jmconfig 356 | 357 | # BizTalk build output 358 | *.btp.cs 359 | *.btm.cs 360 | *.odx.cs 361 | *.xsd.cs 362 | 363 | # OpenCover UI analysis results 364 | OpenCover/ 365 | coverage/ 366 | 367 | ### macOS template 368 | # General 369 | .DS_Store 370 | .AppleDouble 371 | .LSOverride 372 | 373 | # Icon must end with two \r 374 | Icon 375 | 376 | # Thumbnails 377 | ._* 378 | 379 | # Files that might appear in the root of a volume 380 | .DocumentRevisions-V100 381 | .fseventsd 382 | .Spotlight-V100 383 | .TemporaryItems 384 | .Trashes 385 | .VolumeIcon.icns 386 | .com.apple.timemachine.donotpresent 387 | 388 | # Directories potentially created on remote AFP share 389 | .AppleDB 390 | .AppleDesktop 391 | Network Trash Folder 392 | Temporary Items 393 | .apdisk 394 | 395 | ======= 396 | # Local 397 | .env 398 | dist 399 | .webpack 400 | .serverless/**/*.zip 401 | -------------------------------------------------------------------------------- /examples/nestjs-with-swc-example/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } 5 | -------------------------------------------------------------------------------- /examples/nestjs-with-swc-example/README.md: -------------------------------------------------------------------------------- 1 | ## SWC Project setup 2 | 3 | ```bash 4 | $ npm install 5 | ``` 6 | 7 | ## Compile and run the project 8 | 9 | ```bash 10 | # build mail templates 11 | $ npm run build:mail 12 | 13 | # development 14 | $ npm run start 15 | 16 | # watch mode 17 | $ npm run start:dev 18 | 19 | # production mode 20 | $ npm run start:prod 21 | ``` 22 | 23 | ## Run tests 24 | 25 | ```bash 26 | # unit tests 27 | $ npm run test 28 | 29 | # e2e tests 30 | $ npm run test:e2e 31 | 32 | # test coverage 33 | $ npm run test:cov 34 | ``` 35 | -------------------------------------------------------------------------------- /examples/nestjs-with-swc-example/gulpfile.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | 3 | const gulp = require('gulp'); 4 | const ts = require('gulp-typescript'); 5 | 6 | const paths = { 7 | src: 'src/templates/**/*.tsx', 8 | dest: 'dist/templates/', 9 | }; 10 | 11 | exports.default = function (cb) { 12 | gulp 13 | .src(paths.src, { sourcemaps: true }) 14 | .pipe(ts({ noImplicitAny: true, jsx: 'react-jsx' })) 15 | .pipe(gulp.dest(paths.dest)); 16 | 17 | cb(); 18 | }; 19 | -------------------------------------------------------------------------------- /examples/nestjs-with-swc-example/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "builder": "swc", 7 | "typeCheck": true, 8 | "deleteOutDir": false 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/nestjs-with-swc-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@webtre/nestjs-mailer-react-adapter-swc-example", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "This is a demo using @webtre/nestjs-mailer-react-adapter", 6 | "license": "MIT", 7 | "scripts": { 8 | "build:mail": "gulp", 9 | "build": "nest build", 10 | "postbuild": "npm run build:mail", 11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 12 | "start": "nest start", 13 | "start:dev": "nest start --watch", 14 | "start:debug": "nest start --debug --watch", 15 | "start:prod": "node dist/main", 16 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 17 | "test": "jest", 18 | "test:watch": "jest --watch", 19 | "test:cov": "jest --coverage", 20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/jest/bin/jest --runInBand", 21 | "test:e2e": "jest --config ./test/jest-e2e.json" 22 | }, 23 | "dependencies": { 24 | "@nestjs-modules/mailer": "^2.0.2", 25 | "@nestjs/common": "^11.0.11", 26 | "@nestjs/config": "^4.0.1", 27 | "@nestjs/core": "^11.0.11", 28 | "@nestjs/platform-express": "^11.0.11", 29 | "@webtre/nestjs-mailer-react-adapter": "^0.2.4", 30 | "nodemailer": "^6.10.0", 31 | "reflect-metadata": "^0.2.2", 32 | "rxjs": "^7.8.2" 33 | }, 34 | "devDependencies": { 35 | "@nestjs/cli": "^11.0.5", 36 | "@nestjs/schematics": "^11.0.2", 37 | "@nestjs/testing": "^11.0.11", 38 | "@swc/cli": "^0.6.0", 39 | "@swc/core": "^1.11.8", 40 | "@types/express": "^5.0.0", 41 | "@types/jest": "^29.5.14", 42 | "@types/node": "^22.13.10", 43 | "@types/supertest": "^6.0.2", 44 | "@typescript-eslint/eslint-plugin": "^6.21.0", 45 | "@typescript-eslint/parser": "^6.21.0", 46 | "eslint": "^8.56.0", 47 | "eslint-config-prettier": "^9.1.0", 48 | "eslint-plugin-prettier": "^5.1.3", 49 | "gulp": "^5.0.0", 50 | "gulp-typescript": "^6.0.0-alpha.1", 51 | "jest": "^29.7.0", 52 | "prettier": "^3.5.3", 53 | "source-map-support": "^0.5.21", 54 | "supertest": "^7.0.0", 55 | "ts-jest": "^29.2.6", 56 | "ts-loader": "^9.5.2", 57 | "ts-node": "^10.9.2", 58 | "tsconfig-paths": "^4.2.0", 59 | "typescript": "^5.3.3" 60 | }, 61 | "jest": { 62 | "moduleFileExtensions": [ 63 | "js", 64 | "json", 65 | "ts" 66 | ], 67 | "rootDir": "src", 68 | "testRegex": ".*\\.spec\\.ts$", 69 | "transform": { 70 | "^.+\\.(t|j)s$": "ts-jest" 71 | }, 72 | "collectCoverageFrom": [ 73 | "**/*.(t|j)s" 74 | ], 75 | "coverageDirectory": "../coverage", 76 | "testEnvironment": "node" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /examples/nestjs-with-swc-example/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let app: TestingModule; 7 | 8 | beforeAll(async () => { 9 | app = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | }); 14 | 15 | describe('getHello', () => { 16 | it('should return "Hello World!"', () => { 17 | const appController = app.get(AppController); 18 | expect(appController.getHello()).toBe('Hello World!'); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /examples/nestjs-with-swc-example/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | welcome(): Promise { 10 | return this.appService.sendWelcomeMail(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/nestjs-with-swc-example/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { MailerModule } from '@nestjs-modules/mailer'; 2 | import { Module } from '@nestjs/common'; 3 | import { ConfigModule, ConfigType } from '@nestjs/config'; 4 | import { ReactAdapter } from '@webtre/nestjs-mailer-react-adapter'; 5 | import { AppController } from './app.controller'; 6 | import { AppService } from './app.service'; 7 | import mailerConfig from './config/mailer.config'; 8 | 9 | @Module({ 10 | imports: [ 11 | ConfigModule.forRoot({ 12 | isGlobal: true, 13 | load: [mailerConfig], 14 | envFilePath: __dirname + '/../.env', 15 | }), 16 | MailerModule.forRootAsync({ 17 | inject: [mailerConfig.KEY], 18 | useFactory: (mailerConf: ConfigType) => ({ 19 | ...mailerConf, 20 | template: { 21 | adapter: new ReactAdapter(), 22 | dir: __dirname + '/templates', 23 | }, 24 | }), 25 | }), 26 | ], 27 | controllers: [AppController], 28 | providers: [AppService], 29 | }) 30 | export class AppModule {} 31 | -------------------------------------------------------------------------------- /examples/nestjs-with-swc-example/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { MailerService } from '@nestjs-modules/mailer'; 2 | import { Injectable } from '@nestjs/common'; 3 | 4 | @Injectable() 5 | export class AppService { 6 | constructor(private readonly mailerService: MailerService) {} 7 | 8 | public async sendWelcomeMail(): Promise { 9 | await this.mailerService.sendMail({ 10 | to: 'john@domain.com', 11 | subject: 'Testing react template', 12 | template: 'welcome', // The compiled extension is appended automatically. 13 | context: { 14 | // Data to be passed as props to your template. 15 | code: '123456', 16 | name: 'John Doe', 17 | }, 18 | }); 19 | 20 | return 'Mail sent!'; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/nestjs-with-swc-example/src/config/mailer.config.ts: -------------------------------------------------------------------------------- 1 | import { MailerOptions } from '@nestjs-modules/mailer'; 2 | import { registerAs } from '@nestjs/config'; 3 | 4 | export default registerAs('mailer', (): MailerOptions => { 5 | return { 6 | transport: { 7 | service: process.env.MAIL_SERVICE, 8 | host: process.env.MAIL_HOST, 9 | port: parseInt(process.env.MAIL_PORT, 10), 10 | auth: { 11 | user: process.env.MAIL_USERNAME, 12 | pass: process.env.MAIL_PASSWORD, 13 | }, 14 | }, 15 | defaults: { 16 | from: { 17 | name: process.env.MAIL_FROM_NAME, 18 | address: process.env.MAIL_FROM_ADDRESS, 19 | }, 20 | }, 21 | }; 22 | }); 23 | -------------------------------------------------------------------------------- /examples/nestjs-with-swc-example/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | 4 | async function bootstrap() { 5 | const app = await NestFactory.create(AppModule); 6 | await app.listen(3000); 7 | } 8 | bootstrap(); 9 | -------------------------------------------------------------------------------- /examples/nestjs-with-swc-example/src/templates/welcome.tsx: -------------------------------------------------------------------------------- 1 | interface Props { 2 | code: string; 3 | name: string; 4 | } 5 | 6 | export default function Welcome({ name, code }: Props) { 7 | return ( 8 |
9 | Hi {name}, thanks for signing up. Your code is {code} 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /examples/nestjs-with-swc-example/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { Test } from '@nestjs/testing'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from '../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeAll(async () => { 10 | const moduleFixture = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | afterAll(async () => { 19 | await app.close(); 20 | }); 21 | 22 | it('/ (GET)', () => { 23 | return request(app.getHttpServer()) 24 | .get('/') 25 | .expect(200) 26 | .expect('Hello World!'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /examples/nestjs-with-swc-example/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": "\\.e2e-spec\\.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/nestjs-with-swc-example/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /examples/nestjs-with-swc-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2021", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "jsx": "react-jsx" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@webtre/nestjs-mailer-react-adapter", 3 | "version": "0.2.5", 4 | "description": "Build and send emails in Nest framework using React.js", 5 | "main": "./dist/index.js", 6 | "module": "./dist/index.mjs", 7 | "types": "./dist/index.d.ts", 8 | "files": [ 9 | "dist/**" 10 | ], 11 | "exports": { 12 | ".": { 13 | "import": { 14 | "types": "./dist/index.d.mts", 15 | "default": "./dist/index.mjs" 16 | }, 17 | "require": { 18 | "types": "./dist/index.d.ts", 19 | "default": "./dist/index.js" 20 | } 21 | } 22 | }, 23 | "engines": { 24 | "node": "^18.19.1 || ^20.11.1 || >=22.0.0" 25 | }, 26 | "scripts": { 27 | "build": "tsup", 28 | "dev": "tsup --watch" 29 | }, 30 | "publishConfig": { 31 | "access": "public" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/webtretech/nestjs-mailer-react-adapter.git" 36 | }, 37 | "author": "Lawrence Onah ", 38 | "license": "MIT", 39 | "bugs": { 40 | "url": "https://github.com/webtretech/nestjs-mailer-react-adapter/issues" 41 | }, 42 | "homepage": "https://github.com/webtretech/nestjs-mailer-react-adapter#readme", 43 | "dependencies": { 44 | "@react-email/render": "1.0.2", 45 | "locter": "^2.1.6" 46 | }, 47 | "peerDependencies": { 48 | "@nestjs-modules/mailer": "^2.0.0" 49 | }, 50 | "devDependencies": { 51 | "@nestjs-modules/mailer": "^2.0.2", 52 | "@types/node": "^22.13.10", 53 | "@types/react": "^19.0.10", 54 | "tsup": "^8.4.0", 55 | "typescript": "^5.8.2" 56 | }, 57 | "keywords": [ 58 | "nestjs", 59 | "mailer", 60 | "module", 61 | "email", 62 | "nodemailer", 63 | "nodejs", 64 | "mail", 65 | "nest", 66 | "reactjs", 67 | "nestjs-mailer", 68 | "node", 69 | "react", 70 | "mailer-module", 71 | "adapter" 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./react.adapter"; 2 | -------------------------------------------------------------------------------- /src/react.adapter.tsx: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | import { pathToFileURL } from "node:url"; 3 | 4 | import { MailerOptions, TemplateAdapter } from "@nestjs-modules/mailer"; 5 | import { Options, render } from "@react-email/render"; 6 | import { getModuleExport, load } from "locter"; 7 | 8 | export class ReactAdapter implements TemplateAdapter { 9 | private config: Options = { 10 | pretty: false, 11 | plainText: false, 12 | }; 13 | 14 | constructor(config?: Options) { 15 | Object.assign(this.config, config); 16 | } 17 | 18 | public compile(mail: any, callback: any, options: MailerOptions): void { 19 | const { context, template } = mail.data; 20 | const templateExt = path.extname(template) || ".js"; 21 | const templateName = path.basename(template, templateExt); 22 | const templateDir = path.isAbsolute(template) 23 | ? path.dirname(template) 24 | : path.join(options.template.dir, path.dirname(template)); 25 | const templatePath = path.join(templateDir, templateName + templateExt); 26 | const templatePathFileURL = pathToFileURL(templatePath).href; 27 | 28 | load(templatePathFileURL) 29 | .then((tmplModule) => { 30 | const moduleDefault = getModuleExport( 31 | tmplModule, 32 | (key) => key === "default" 33 | ); 34 | const Comp = moduleDefault.value; 35 | 36 | return render(, this.config); 37 | }) 38 | .then((html) => { 39 | mail.data.html = html; 40 | return callback(); 41 | }) 42 | .catch(callback); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "esModuleInterop": true, 5 | "declaration": true, 6 | "removeComments": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "noImplicitAny": true, 11 | "moduleResolution": "node", 12 | "sourceMap": true, 13 | "outDir": "dist", 14 | "rootDir": "./src", 15 | "skipLibCheck": true, 16 | "jsx": "react-jsx" 17 | }, 18 | "include": ["src/**/*"] 19 | } 20 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | export default defineConfig({ 4 | entry: ["src/index.ts"], 5 | format: ["cjs", "esm"], 6 | external: ["react"], 7 | sourcemap: false, 8 | clean: true, 9 | dts: true, 10 | }); 11 | --------------------------------------------------------------------------------