{"_id":"@bymax-one/nest-notification","_rev":"15-6900b592468bf3b7edfcac76ae6a0844","name":"@bymax-one/nest-notification","dist-tags":{"latest":"1.4.0"},"versions":{"1.0.0":{"name":"@bymax-one/nest-notification","version":"1.0.0","keywords":["nestjs","notification","email","otp","sms","push","resend","sendgrid","ses","twilio","fcm","multi-tenant","redis","transactional","saas"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-notification@1.0.0","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-notification#readme","bugs":{"url":"https://github.com/bymaxone/nest-notification/issues"},"dist":{"shasum":"d3a953d21819bfaa69cf0a7ee5e9878b2a0fadd0","tarball":"https://registry.npmjs.org/@bymax-one/nest-notification/-/nest-notification-1.0.0.tgz","fileCount":16,"integrity":"sha512-UCd1yiFw1UCjzJKDLgFuyI/R+vnHa9KVb7HzQEDU/RUolJp3MbIAgErNEUP3INIcZ+07b0Dml/9u9GJPe2/Pcw==","signatures":[{"sig":"MEUCIBIczR28LU6q8a1MTgw6HgRMirqRRlI4HD1lw7l2JSf3AiEA9TCR+k6E91DAAM4cIKLkIF4cJ9REoSg6qgow/yDt2as=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":442172},"main":"./dist/server/index.cjs","type":"module","_from":"file:bymax-one-nest-notification-1.0.0.tgz","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"scripts":{"lint":"eslint src scripts test","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","smoke":"node scripts/dogfood-smoke-test.mjs","release":"pnpm publish --provenance","lint:fix":"eslint src scripts test --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts --runInBand","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:types":"tsc --noEmit -p tsconfig.typetest.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack .","check:no-prisma":"grep -rq '@prisma/client' src; s=$?; [ $s -eq 1 ] && exit 0; [ $s -eq 0 ] && { echo check:no-prisma: found @prisma/client import in src/; exit 1; }; echo check:no-prisma: grep failed with exit $s; exit $s","mutation:dry-run":"stryker run --dryRunOnly","mutation:incremental":"stryker run --incremental"},"_npmUser":{"name":"bymax.one","email":"bymaxone.core@gmail.com"},"_resolved":"/private/var/folders/zf/_zr7t0ms60x6rcwj5zxk90cr0000gn/T/16dcf75b4f5b66210f04f0835a316295/bymax-one-nest-notification-1.0.0.tgz","_integrity":"sha512-UCd1yiFw1UCjzJKDLgFuyI/R+vnHa9KVb7HzQEDU/RUolJp3MbIAgErNEUP3INIcZ+07b0Dml/9u9GJPe2/Pcw==","repository":{"url":"git+https://github.com/bymaxone/nest-notification.git","type":"git"},"_npmVersion":"11.13.0","description":"Multi-channel notification library for NestJS — email, OTP, SMS, push — with pluggable providers and storage, multi-tenant by design, zero runtime dependencies.","directories":{},"lint-staged":{"*.{json,md,yml,yaml}":["prettier --write"],"*.{ts,tsx,js,mjs,cjs}":["eslint --fix","prettier --write"]},"sideEffects":false,"_nodeVersion":"24.16.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"devDependencies":{"jest":"^30.4.2","rxjs":"^7.8.0","tsup":"^8.5.1","husky":"^9.1.7","react":"^19","eslint":"^9.39.4","globals":"^17.6.0","ts-jest":"^29.4.11","ts-node":"^10.9.2","prettier":"^3.8.3","react-dom":"^19","@eslint/js":"^9.39.4","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24","lint-staged":"^17.2.0","@nestjs/core":"^11.1.20","@types/react":"^19","@nestjs/common":"^11.1.20","@commitlint/cli":"^21.2.1","@nestjs/testing":"^11.1.20","reflect-metadata":"^0.2.2","@testing-library/dom":"^10.4.1","eslint-plugin-import":"^2.32.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9","@testing-library/react":"^16","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.5","eslint-plugin-security":"^4.0.0","jest-environment-jsdom":"^30","@typescript-eslint/parser":"^8.59.3","@stryker-mutator/jest-runner":"^9","@commitlint/config-conventional":"^21.2.0","@typescript-eslint/eslint-plugin":"^8.59.3","eslint-import-resolver-typescript":"^4.4.4","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"mjml":"^5.0.0","rxjs":"^7.8.0","react":"^19.0.0","resend":"^4.0.0 || ^5.0.0 || ^6.0.0","twilio":"^5.0.0 || ^6.0.0","express":"^5.0.0","ioredis":"^5.0.0","handlebars":"^4.7.7","mailgun.js":"^11.0.0 || ^12.0.0 || ^13.0.0","nodemailer":"^9.0.1","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.0","@sendgrid/mail":"^8.0.0","@types/express":"^5.0.0","firebase-admin":"^13.0.0 || ^14.0.0","class-validator":"^0.14.0 || ^0.15.0","reflect-metadata":"^0.2.0","class-transformer":"^0.5.0","@aws-sdk/client-ses":"^3.0.0","@aws-sdk/client-sns":"^3.0.0","@react-email/render":"^1.0.0 || ^2.0.0","@aws-sdk/client-dynamodb":"^3.0.0"},"peerDependenciesMeta":{"mjml":{"optional":true},"react":{"optional":true},"resend":{"optional":true},"twilio":{"optional":true},"express":{"optional":true},"ioredis":{"optional":true},"handlebars":{"optional":true},"mailgun.js":{"optional":true},"nodemailer":{"optional":true},"@sendgrid/mail":{"optional":true},"@types/express":{"optional":true},"firebase-admin":{"optional":true},"class-validator":{"optional":true},"class-transformer":{"optional":true},"@aws-sdk/client-ses":{"optional":true},"@aws-sdk/client-sns":{"optional":true},"@react-email/render":{"optional":true},"@aws-sdk/client-dynamodb":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-notification_1.0.0_1785430852420_0.6549887267937065","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@bymax-one/nest-notification","version":"1.0.1","keywords":["nestjs","notification","email","otp","sms","push","resend","sendgrid","ses","twilio","fcm","multi-tenant","redis","transactional","saas"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-notification@1.0.1","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-notification#readme","bugs":{"url":"https://github.com/bymaxone/nest-notification/issues"},"dist":{"shasum":"62f77c1a69054c1dae37a16204fa746d983392ad","tarball":"https://registry.npmjs.org/@bymax-one/nest-notification/-/nest-notification-1.0.1.tgz","fileCount":16,"integrity":"sha512-fn3j4YdsF/tqFM5/W5xWSxd3Ir2ftAT61maLotfh/T4hDuhIqGvJA8dnHMaceMgeghttmSWFzcZRtW7QHwPLvQ==","signatures":[{"sig":"MEQCIAcJG6c66QDsCLE+xCE7qGzVNS3ZcGOJzXalbJn9ik7eAiAh1rJgCOIO1uJqh/Bd5od4LJW8+O15TtWtA4W04LkrVQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-notification@1.0.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":444080},"main":"./dist/server/index.cjs","type":"module","_from":"file:bymax-one-nest-notification-1.0.1.tgz","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"scripts":{"lint":"eslint src scripts test","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","smoke":"node scripts/dogfood-smoke-test.mjs","release":"pnpm publish --provenance","lint:fix":"eslint src scripts test --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts --runInBand","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:types":"tsc --noEmit -p tsconfig.typetest.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack .","check:no-prisma":"grep -rq '@prisma/client' src; s=$?; [ $s -eq 1 ] && exit 0; [ $s -eq 0 ] && { echo check:no-prisma: found @prisma/client import in src/; exit 1; }; echo check:no-prisma: grep failed with exit $s; exit $s","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","mutation:incremental":"stryker run --incremental"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9d848c04-89b6-4beb-a7c4-50b00262ae8c"}},"_resolved":"/tmp/7e925bab0868b0fb2f6007e97ca442c0/bymax-one-nest-notification-1.0.1.tgz","_integrity":"sha512-fn3j4YdsF/tqFM5/W5xWSxd3Ir2ftAT61maLotfh/T4hDuhIqGvJA8dnHMaceMgeghttmSWFzcZRtW7QHwPLvQ==","repository":{"url":"git+https://github.com/bymaxone/nest-notification.git","type":"git"},"_npmVersion":"11.16.0","description":"Multi-channel notification library for NestJS — email, OTP, SMS, push — with pluggable providers and storage, multi-tenant by design, zero runtime dependencies.","directories":{},"lint-staged":{"*.{json,md,yml,yaml}":["prettier --write"],"*.{ts,tsx,js,mjs,cjs}":["eslint --fix","prettier --write"]},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"devDependencies":{"jest":"^30.4.2","rxjs":"^7.8.0","tsup":"^8.5.1","husky":"^9.1.7","react":"^19","eslint":"^9.39.4","globals":"^17.6.0","ts-jest":"^29.4.11","ts-node":"^10.9.2","prettier":"^3.8.3","react-dom":"^19","@eslint/js":"^9.39.4","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24","lint-staged":"^17.2.0","@nestjs/core":"^11.1.20","@types/react":"^19","@nestjs/common":"^11.1.20","@commitlint/cli":"^21.2.1","@nestjs/testing":"^11.1.20","reflect-metadata":"^0.2.2","@testing-library/dom":"^10.4.1","eslint-plugin-import":"^2.32.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9","@testing-library/react":"^16","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.5","eslint-plugin-security":"^4.0.0","jest-environment-jsdom":"^30","@typescript-eslint/parser":"^8.59.3","@stryker-mutator/jest-runner":"^9","@commitlint/config-conventional":"^21.2.0","@typescript-eslint/eslint-plugin":"^8.59.3","eslint-import-resolver-typescript":"^4.4.4","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"mjml":"^5.0.0","rxjs":"^7.8.0","react":"^19.0.0","resend":"^4.0.0 || ^5.0.0 || ^6.0.0","twilio":"^5.0.0 || ^6.0.0","express":"^5.0.0","ioredis":"^5.0.0","handlebars":"^4.7.9","mailgun.js":"^11.0.0 || ^12.0.0 || ^13.0.0","nodemailer":"^9.0.1","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","@sendgrid/mail":"^8.0.0","@types/express":"^5.0.0","firebase-admin":"^13.0.0 || ^14.0.0","class-validator":"^0.14.0 || ^0.15.0","reflect-metadata":"^0.2.0","class-transformer":"^0.5.0","@aws-sdk/client-ses":"^3.0.0","@aws-sdk/client-sns":"^3.0.0","@react-email/render":"^1.0.0 || ^2.0.0","@aws-sdk/client-dynamodb":"^3.0.0"},"peerDependenciesMeta":{"mjml":{"optional":true},"react":{"optional":true},"resend":{"optional":true},"twilio":{"optional":true},"express":{"optional":true},"ioredis":{"optional":true},"handlebars":{"optional":true},"mailgun.js":{"optional":true},"nodemailer":{"optional":true},"@sendgrid/mail":{"optional":true},"@types/express":{"optional":true},"firebase-admin":{"optional":true},"class-validator":{"optional":true},"class-transformer":{"optional":true},"@aws-sdk/client-ses":{"optional":true},"@aws-sdk/client-sns":{"optional":true},"@react-email/render":{"optional":true},"@aws-sdk/client-dynamodb":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-notification_1.0.1_1785636670480_0.5382937859170289","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@bymax-one/nest-notification","version":"1.0.2","keywords":["nestjs","notification","email","otp","sms","push","resend","sendgrid","ses","twilio","fcm","multi-tenant","redis","transactional","saas"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-notification@1.0.2","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-notification#readme","bugs":{"url":"https://github.com/bymaxone/nest-notification/issues"},"dist":{"shasum":"e6402a665e5e44a688225b9faaf8afd23c69605b","tarball":"https://registry.npmjs.org/@bymax-one/nest-notification/-/nest-notification-1.0.2.tgz","fileCount":16,"integrity":"sha512-6Es9yfBGhOCCXZpn1onSjV4kMKczP4y5lqbABcSG3s8o03ajO7SRQmsQK7PMisj6e2g7+MyO4tmf+Au/3aucFA==","signatures":[{"sig":"MEQCIGWqo3tQ8H3KoUkEfzbezW2+unzqynuQO2KsJKJA7irJAiBuNMVd3jAI+AtnHoQetdJ+TdC0Tal8bfoZnpT/M5gC+A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-notification@1.0.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":448892},"main":"./dist/server/index.cjs","pnpm":{"overrides":{"qs":"^6.15.2","esbuild":"^0.28.1","fast-uri":"^3.1.5","js-yaml@3":"^3.15.0","js-yaml@4":"^4.3.0","brace-expansion@1":"^1.1.18","brace-expansion@2":"^2.1.4","brace-expansion@5":"^5.0.9"}},"type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"gitHead":"b8a357bc110932ef75620674af47eb9550c9edab","scripts":{"lint":"eslint src scripts test","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","smoke":"node scripts/dogfood-smoke-test.mjs","prepare":"husky","release":"npm publish --provenance --access public","lint:fix":"eslint src scripts test --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts --runInBand","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:types":"tsc --noEmit -p tsconfig.typetest.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack .","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm test:types && pnpm lint && pnpm check:no-prisma && pnpm test:cov:all && pnpm build && pnpm check:published","check:no-prisma":"grep -rq '@prisma/client' src; s=$?; [ $s -eq 1 ] && exit 0; [ $s -eq 0 ] && { echo check:no-prisma: found @prisma/client import in src/; exit 1; }; echo check:no-prisma: grep failed with exit $s; exit $s","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","mutation:incremental":"stryker run --incremental"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9d848c04-89b6-4beb-a7c4-50b00262ae8c"}},"repository":{"url":"git+https://github.com/bymaxone/nest-notification.git","type":"git"},"_npmVersion":"11.16.0","description":"Multi-channel notification library for NestJS — email, OTP, SMS, push — with pluggable providers and storage, multi-tenant by design, zero runtime dependencies.","directories":{},"lint-staged":{"*.{json,md,yml,yaml}":["prettier --write"],"*.{ts,tsx,js,mjs,cjs}":["eslint --fix","prettier --write"]},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@10.8.1","devDependencies":{"jest":"^30.4.2","rxjs":"^7.8.0","tsup":"^8.5.1","husky":"^9.1.7","react":"^19","eslint":"^9.39.4","globals":"^17.6.0","ts-jest":"^29.4.11","ts-node":"^10.9.2","prettier":"^3.8.3","react-dom":"^19","@eslint/js":"^9.39.4","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24","lint-staged":"^17.2.0","@nestjs/core":"^11.1.20","@types/react":"^19","@nestjs/common":"^11.1.20","@commitlint/cli":"^21.2.1","@nestjs/testing":"^11.1.20","reflect-metadata":"^0.2.2","@testing-library/dom":"^10.4.1","eslint-plugin-import":"^2.32.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9","@testing-library/react":"^16","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.5","eslint-plugin-security":"^4.0.0","jest-environment-jsdom":"^30","@typescript-eslint/parser":"^8.59.3","@stryker-mutator/jest-runner":"^9","@commitlint/config-conventional":"^21.2.0","@typescript-eslint/eslint-plugin":"^8.59.3","eslint-import-resolver-typescript":"^4.4.4","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"mjml":"^5.0.0","rxjs":"^7.8.0","react":"^19.0.0","resend":"^4.0.0 || ^5.0.0 || ^6.0.0","twilio":"^5.0.0 || ^6.0.0","express":"^5.0.0","ioredis":"^5.0.0","handlebars":"^4.7.9","mailgun.js":"^11.0.0 || ^12.0.0 || ^13.0.0","nodemailer":"^9.0.1","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","@sendgrid/mail":"^8.0.0","@types/express":"^5.0.0","firebase-admin":"^13.0.0 || ^14.0.0","class-validator":"^0.14.0 || ^0.15.0","reflect-metadata":"^0.2.0","class-transformer":"^0.5.0","@aws-sdk/client-ses":"^3.0.0","@aws-sdk/client-sns":"^3.0.0","@react-email/render":"^1.0.0 || ^2.0.0","@aws-sdk/client-dynamodb":"^3.0.0"},"peerDependenciesMeta":{"mjml":{"optional":true},"react":{"optional":true},"resend":{"optional":true},"twilio":{"optional":true},"express":{"optional":true},"ioredis":{"optional":true},"handlebars":{"optional":true},"mailgun.js":{"optional":true},"nodemailer":{"optional":true},"@sendgrid/mail":{"optional":true},"@types/express":{"optional":true},"firebase-admin":{"optional":true},"class-validator":{"optional":true},"class-transformer":{"optional":true},"@aws-sdk/client-ses":{"optional":true},"@aws-sdk/client-sns":{"optional":true},"@react-email/render":{"optional":true},"@aws-sdk/client-dynamodb":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-notification_1.0.2_1785858364847_0.38835453566799405","host":"s3://npm-registry-packages-npm-production"}},"1.0.4":{"name":"@bymax-one/nest-notification","version":"1.0.4","keywords":["nestjs","notification","email","otp","sms","push","resend","sendgrid","ses","twilio","fcm","multi-tenant","redis","transactional","saas"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-notification@1.0.4","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-notification#readme","bugs":{"url":"https://github.com/bymaxone/nest-notification/issues"},"dist":{"shasum":"32baf10ba9f3d529a61042648a651a97c90393b4","tarball":"https://registry.npmjs.org/@bymax-one/nest-notification/-/nest-notification-1.0.4.tgz","fileCount":16,"integrity":"sha512-324NzoD47QdcZXicEKwB5cey72duwMOwqPPceQKzDtXLLJBIl7RtBm8Ben6oioMWEsnt1Xj4NiprFdG1KkBNMg==","signatures":[{"sig":"MEYCIQD71NCFJSJsH2STMyqX/bso2zLJrTx/ji5XE+UqH+haHwIhAMtr63gdiGjzWN7UHwZ69W/sTu7oSUMYSJroBg+6BnzM","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-notification@1.0.4","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":451904},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"gitHead":"eda99380627e1460501cb9ad8e348194ced17550","scripts":{"lint":"eslint src scripts test","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","smoke":"node scripts/dogfood-smoke-test.mjs","prepare":"husky","release":"npm publish --provenance --access public","lint:fix":"eslint src scripts test --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts --runInBand","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:types":"tsc --noEmit -p tsconfig.typetest.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack .","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm test:types && pnpm lint && pnpm check:no-prisma && pnpm test:cov:all && pnpm build && pnpm check:published","check:no-prisma":"grep -rq '@prisma/client' src; s=$?; [ $s -eq 1 ] && exit 0; [ $s -eq 0 ] && { echo check:no-prisma: found @prisma/client import in src/; exit 1; }; echo check:no-prisma: grep failed with exit $s; exit $s","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","mutation:incremental":"stryker run --incremental"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9d848c04-89b6-4beb-a7c4-50b00262ae8c"}},"repository":{"url":"git+https://github.com/bymaxone/nest-notification.git","type":"git"},"_npmVersion":"11.16.0","description":"Multi-channel notification library for NestJS — email, OTP, SMS, push — with pluggable providers and storage, multi-tenant by design, zero runtime dependencies.","directories":{},"lint-staged":{"*.{json,md,yml,yaml}":["prettier --write"],"*.{ts,tsx,js,mjs,cjs}":["eslint --fix","prettier --write"]},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.20.0","devDependencies":{"jest":"^30.4.2","rxjs":"^7.8.0","tsup":"^8.5.1","husky":"^9.1.7","react":"^19","eslint":"^9.39.4","globals":"^17.6.0","ts-jest":"^29.4.11","ts-node":"^10.9.2","prettier":"^3.8.3","react-dom":"^19","@eslint/js":"^9.39.4","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24","lint-staged":"^17.2.0","@nestjs/core":"^11.1.20","@types/react":"^19","@nestjs/common":"^11.1.20","@commitlint/cli":"^21.2.1","@nestjs/testing":"^11.1.20","reflect-metadata":"^0.2.2","@testing-library/dom":"^10.4.1","eslint-plugin-import":"^2.32.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9","@testing-library/react":"^16","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.5","eslint-plugin-security":"^4.0.0","jest-environment-jsdom":"^30","@typescript-eslint/parser":"^8.59.3","@stryker-mutator/jest-runner":"^9","@commitlint/config-conventional":"^21.2.0","@typescript-eslint/eslint-plugin":"^8.59.3","eslint-import-resolver-typescript":"^4.4.4","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"mjml":"^5.0.0","rxjs":"^7.8.0","react":"^19.0.0","resend":"^4.0.0 || ^5.0.0 || ^6.0.0","twilio":"^5.0.0 || ^6.0.0","express":"^5.0.0","ioredis":"^5.0.0","handlebars":"^4.7.9","mailgun.js":"^11.0.0 || ^12.0.0 || ^13.0.0","nodemailer":"^9.0.1","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","@sendgrid/mail":"^8.0.0","@types/express":"^5.0.0","firebase-admin":"^13.0.0 || ^14.0.0","class-validator":"^0.14.0 || ^0.15.0","reflect-metadata":"^0.2.0","class-transformer":"^0.5.0","@aws-sdk/client-ses":"^3.0.0","@aws-sdk/client-sns":"^3.0.0","@react-email/render":"^1.0.0 || ^2.0.0","@aws-sdk/client-dynamodb":"^3.0.0"},"peerDependenciesMeta":{"mjml":{"optional":true},"react":{"optional":true},"resend":{"optional":true},"twilio":{"optional":true},"express":{"optional":true},"ioredis":{"optional":true},"handlebars":{"optional":true},"mailgun.js":{"optional":true},"nodemailer":{"optional":true},"@sendgrid/mail":{"optional":true},"@types/express":{"optional":true},"firebase-admin":{"optional":true},"class-validator":{"optional":true},"class-transformer":{"optional":true},"@aws-sdk/client-ses":{"optional":true},"@aws-sdk/client-sns":{"optional":true},"@react-email/render":{"optional":true},"@aws-sdk/client-dynamodb":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-notification_1.0.4_1786015916324_0.26526304424788605","host":"s3://npm-registry-packages-npm-production"}},"1.0.5":{"name":"@bymax-one/nest-notification","version":"1.0.5","keywords":["nestjs","notification","email","otp","sms","push","resend","sendgrid","ses","twilio","fcm","multi-tenant","redis","transactional","saas"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-notification@1.0.5","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-notification#readme","bugs":{"url":"https://github.com/bymaxone/nest-notification/issues"},"dist":{"shasum":"92d4bed3004099caba416f795927fd16d8498b6f","tarball":"https://registry.npmjs.org/@bymax-one/nest-notification/-/nest-notification-1.0.5.tgz","fileCount":16,"integrity":"sha512-yfyHEj3lPLamR2SgZs+5cheBSHvYSWKCv+ZEcjZkPeuKJVY+1iD1p5CsMctfXHG/fW9MB0/9xd+oRpXFZThQbQ==","signatures":[{"sig":"MEUCIAhHsmeHrRKd3gii/9XV4Kjev4tVVeYi/svvCi8pk4jlAiEAttEf5Uvb+jPOB98J5z/D1hlCSZwDVqK2iZezZWykG28=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-notification@1.0.5","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":452954},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"gitHead":"ef226c3a099ad783a1d55cca8a0b0a5c3e8f064a","scripts":{"lint":"eslint src scripts test","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","smoke":"node scripts/dogfood-smoke-test.mjs","prepare":"husky","release":"npm publish --provenance --access public","lint:fix":"eslint src scripts test --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts --runInBand","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:types":"tsc --noEmit -p tsconfig.typetest.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack .","check:mutants":"node scripts/check-mutation-directives.mjs","mutation:full":"node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm test:types && pnpm lint && pnpm check:mutants && pnpm check:no-prisma && pnpm test:cov:all && pnpm build && pnpm check:published","check:no-prisma":"grep -rq '@prisma/client' src; s=$?; [ $s -eq 1 ] && exit 0; [ $s -eq 0 ] && { echo check:no-prisma: found @prisma/client import in src/; exit 1; }; echo check:no-prisma: grep failed with exit $s; exit $s","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9d848c04-89b6-4beb-a7c4-50b00262ae8c"}},"repository":{"url":"git+https://github.com/bymaxone/nest-notification.git","type":"git"},"_npmVersion":"11.16.0","description":"Multi-channel notification library for NestJS — email, OTP, SMS, push — with pluggable providers and storage, multi-tenant by design, zero runtime dependencies.","directories":{},"lint-staged":{"*.{json,md,yml,yaml}":["prettier --write"],"*.{ts,tsx,js,mjs,cjs}":["eslint --fix","prettier --write"]},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.20.0","devDependencies":{"jest":"^30.4.2","rxjs":"^7.8.0","tsup":"^8.5.1","husky":"^9.1.7","react":"^19","eslint":"^9.39.4","globals":"^17.6.0","ts-jest":"^29.4.11","ts-node":"^10.9.2","prettier":"^3.8.3","react-dom":"^19","@eslint/js":"^9.39.4","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24","lint-staged":"^17.2.0","@nestjs/core":"^11.1.20","@types/react":"^19","@nestjs/common":"^11.1.20","@commitlint/cli":"^21.2.1","@nestjs/testing":"^11.1.20","reflect-metadata":"^0.2.2","@testing-library/dom":"^10.4.1","eslint-plugin-import":"^2.32.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9","@testing-library/react":"^16","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.5","eslint-plugin-security":"^4.0.0","jest-environment-jsdom":"^30","@typescript-eslint/parser":"^8.59.3","@stryker-mutator/jest-runner":"^9","@commitlint/config-conventional":"^21.2.0","@typescript-eslint/eslint-plugin":"^8.59.3","eslint-import-resolver-typescript":"^4.4.4","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"mjml":"^5.0.0","rxjs":"^7.8.0","react":"^19.0.0","resend":"^4.0.0 || ^5.0.0 || ^6.0.0","twilio":"^5.0.0 || ^6.0.0","express":"^5.0.0","ioredis":"^5.0.0","handlebars":"^4.7.9","mailgun.js":"^11.0.0 || ^12.0.0 || ^13.0.0","nodemailer":"^9.0.1","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","@sendgrid/mail":"^8.0.0","@types/express":"^5.0.0","firebase-admin":"^13.0.0 || ^14.0.0","class-validator":"^0.14.0 || ^0.15.0","reflect-metadata":"^0.2.0","class-transformer":"^0.5.0","@aws-sdk/client-ses":"^3.0.0","@aws-sdk/client-sns":"^3.0.0","@react-email/render":"^1.0.0 || ^2.0.0","@aws-sdk/client-dynamodb":"^3.0.0"},"peerDependenciesMeta":{"mjml":{"optional":true},"react":{"optional":true},"resend":{"optional":true},"twilio":{"optional":true},"express":{"optional":true},"ioredis":{"optional":true},"handlebars":{"optional":true},"mailgun.js":{"optional":true},"nodemailer":{"optional":true},"@sendgrid/mail":{"optional":true},"@types/express":{"optional":true},"firebase-admin":{"optional":true},"class-validator":{"optional":true},"class-transformer":{"optional":true},"@aws-sdk/client-ses":{"optional":true},"@aws-sdk/client-sns":{"optional":true},"@react-email/render":{"optional":true},"@aws-sdk/client-dynamodb":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-notification_1.0.5_1786203522400_0.46660399916475703","host":"s3://npm-registry-packages-npm-production"}},"1.0.6":{"name":"@bymax-one/nest-notification","version":"1.0.6","keywords":["nestjs","notification","email","otp","sms","push","resend","sendgrid","ses","twilio","fcm","multi-tenant","redis","transactional","saas"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-notification@1.0.6","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-notification#readme","bugs":{"url":"https://github.com/bymaxone/nest-notification/issues"},"dist":{"shasum":"14c8ccf22ec2673a345073d8f312050fe2bf3bc9","tarball":"https://registry.npmjs.org/@bymax-one/nest-notification/-/nest-notification-1.0.6.tgz","fileCount":16,"integrity":"sha512-HN2vWdXXlMB1trDPOSzNcIgavbnyVdOnQBeIMa30aWb/aRlAte71btfPcxH6k82mpRKZyT8YyyI2WFFXuFFTXg==","signatures":[{"sig":"MEUCIDFc2isEgphUjvXiro7M0hEKhJdHPWgvJXqICVXqzKl/AiEAtNAVXX94FQX9R0pDLvoLF4aUhYEJKYR+Oz+0ZtxRK8k=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-notification@1.0.6","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":454553},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"gitHead":"3ccd416ff82e7a5b9bbe735e3321b579ddb42442","scripts":{"lint":"eslint src scripts test","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","smoke":"node scripts/dogfood-smoke-test.mjs","prepare":"husky","release":"npm publish --provenance --access public","lint:fix":"eslint src scripts test --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts --runInBand","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:types":"tsc --noEmit -p tsconfig.typetest.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack .","check:mutants":"node scripts/check-mutation-directives.mjs","mutation:full":"node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm test:types && pnpm lint && pnpm check:mutants && pnpm check:no-prisma && pnpm test:cov:all && pnpm build && pnpm check:published","check:no-prisma":"grep -rq '@prisma/client' src; s=$?; [ $s -eq 1 ] && exit 0; [ $s -eq 0 ] && { echo check:no-prisma: found @prisma/client import in src/; exit 1; }; echo check:no-prisma: grep failed with exit $s; exit $s","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9d848c04-89b6-4beb-a7c4-50b00262ae8c"}},"repository":{"url":"git+https://github.com/bymaxone/nest-notification.git","type":"git"},"_npmVersion":"11.16.0","description":"Multi-channel notification library for NestJS — email, OTP, SMS, push — with pluggable providers and storage, multi-tenant by design, zero runtime dependencies.","directories":{},"lint-staged":{"*.{json,md,yml,yaml}":["prettier --write"],"*.{ts,tsx,js,mjs,cjs}":["eslint --fix","prettier --write"]},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.20.0","devDependencies":{"jest":"^30.4.2","rxjs":"^7.8.0","tsup":"^8.5.1","husky":"^9.1.7","react":"^19","eslint":"^9.39.4","globals":"^17.6.0","ts-jest":"^29.4.11","ts-node":"^10.9.2","prettier":"^3.8.3","react-dom":"^19","@eslint/js":"^9.39.4","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24","lint-staged":"^17.2.0","@nestjs/core":"^11.1.20","@types/react":"^19","@nestjs/common":"^11.1.20","@commitlint/cli":"^21.2.1","@nestjs/testing":"^11.1.20","reflect-metadata":"^0.2.2","@testing-library/dom":"^10.4.1","eslint-plugin-import":"^2.32.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9","@testing-library/react":"^16","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.5","eslint-plugin-security":"^4.0.0","jest-environment-jsdom":"^30","@typescript-eslint/parser":"^8.59.3","@stryker-mutator/jest-runner":"^9","@commitlint/config-conventional":"^21.2.0","@typescript-eslint/eslint-plugin":"^8.59.3","eslint-import-resolver-typescript":"^4.4.4","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"mjml":"^5.0.0","rxjs":"^7.8.0","react":"^19.0.0","resend":"^4.0.0 || ^5.0.0 || ^6.0.0","twilio":"^5.0.0 || ^6.0.0","express":"^5.0.0","ioredis":"^5.0.0","handlebars":"^4.7.9","mailgun.js":"^11.0.0 || ^12.0.0 || ^13.0.0","nodemailer":"^9.0.1","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","@sendgrid/mail":"^8.0.0","@types/express":"^5.0.0","firebase-admin":"^13.0.0 || ^14.0.0","class-validator":"^0.14.0 || ^0.15.0","reflect-metadata":"^0.2.0","class-transformer":"^0.5.0","@aws-sdk/client-ses":"^3.0.0","@aws-sdk/client-sns":"^3.0.0","@react-email/render":"^1.0.0 || ^2.0.0","@aws-sdk/client-dynamodb":"^3.0.0"},"peerDependenciesMeta":{"mjml":{"optional":true},"react":{"optional":true},"resend":{"optional":true},"twilio":{"optional":true},"express":{"optional":true},"ioredis":{"optional":true},"handlebars":{"optional":true},"mailgun.js":{"optional":true},"nodemailer":{"optional":true},"@sendgrid/mail":{"optional":true},"@types/express":{"optional":true},"firebase-admin":{"optional":true},"class-validator":{"optional":true},"class-transformer":{"optional":true},"@aws-sdk/client-ses":{"optional":true},"@aws-sdk/client-sns":{"optional":true},"@react-email/render":{"optional":true},"@aws-sdk/client-dynamodb":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-notification_1.0.6_1786381375799_0.8853188494884094","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@bymax-one/nest-notification","version":"1.1.0","keywords":["nestjs","notification","email","otp","sms","push","resend","sendgrid","ses","twilio","fcm","multi-tenant","redis","transactional","saas"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-notification@1.1.0","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-notification#readme","bugs":{"url":"https://github.com/bymaxone/nest-notification/issues"},"dist":{"shasum":"8d1f62e13fd94a65a98d1d88e18780b46db3e673","tarball":"https://registry.npmjs.org/@bymax-one/nest-notification/-/nest-notification-1.1.0.tgz","fileCount":16,"integrity":"sha512-G06xL2TW5UX36ErurW+fo6DRyJni8CJfgoo4b3EnRzSrRuYIj1nEdgxFwXcz39tx7bb73/vJQRAPxmgLGHOlKw==","signatures":[{"sig":"MEUCIGAmJNoTnkJYjjwAxh4FKZrl4j/ve2Gs9gHoFsxooWJlAiEAlFyC71GOgSNjFRBxVxjuy8RTWgIe1R0cCN67f3f2kcU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-notification@1.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":455700},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"gitHead":"9110e300b417e943dd95d0a25bf30337da600393","scripts":{"lint":"eslint src scripts test","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","smoke":"node scripts/dogfood-smoke-test.mjs","prepare":"husky","release":"npm publish --provenance --access public","lint:fix":"eslint src scripts test --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts --runInBand","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:types":"tsc --noEmit -p tsconfig.typetest.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack .","check:mutants":"node scripts/check-mutation-directives.mjs","mutation:full":"node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm test:types && pnpm lint && pnpm check:mutants && pnpm check:no-prisma && pnpm test:cov:all && pnpm build && pnpm check:published","check:no-prisma":"grep -rq '@prisma/client' src; s=$?; [ $s -eq 1 ] && exit 0; [ $s -eq 0 ] && { echo check:no-prisma: found @prisma/client import in src/; exit 1; }; echo check:no-prisma: grep failed with exit $s; exit $s","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9d848c04-89b6-4beb-a7c4-50b00262ae8c"}},"repository":{"url":"git+https://github.com/bymaxone/nest-notification.git","type":"git"},"_npmVersion":"11.16.0","description":"Multi-channel notification library for NestJS — email, OTP, SMS, push — with pluggable providers and storage, multi-tenant by design, zero runtime dependencies.","directories":{},"lint-staged":{"*.{json,md,yml,yaml}":["prettier --write"],"*.{ts,tsx,js,mjs,cjs}":["eslint --fix","prettier --write"]},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.20.0","devDependencies":{"jest":"^30.4.2","rxjs":"^7.8.0","tsup":"^8.5.1","husky":"^9.1.7","react":"^19","eslint":"^9.39.4","globals":"^17.6.0","ts-jest":"^29.4.11","ts-node":"^10.9.2","prettier":"^3.8.3","react-dom":"^19","@eslint/js":"^9.39.4","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24","lint-staged":"^17.2.0","@nestjs/core":"^11.1.20","@types/react":"^19","@nestjs/common":"^11.1.20","@commitlint/cli":"^21.2.1","@nestjs/testing":"^11.1.20","reflect-metadata":"^0.2.2","@testing-library/dom":"^10.4.1","eslint-plugin-import":"^2.32.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9","@testing-library/react":"^16","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.5","eslint-plugin-security":"^4.0.0","jest-environment-jsdom":"^30","@typescript-eslint/parser":"^8.59.3","@stryker-mutator/jest-runner":"^9","@commitlint/config-conventional":"^21.2.0","@typescript-eslint/eslint-plugin":"^8.59.3","eslint-import-resolver-typescript":"^4.4.4","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"mjml":"^5.0.0","rxjs":"^7.8.0","react":"^19.0.0","resend":"^4.0.0 || ^5.0.0 || ^6.0.0","twilio":"^5.0.0 || ^6.0.0","express":"^5.0.0","ioredis":"^6.0.0","handlebars":"^4.7.9","mailgun.js":"^11.0.0 || ^12.0.0 || ^13.0.0","nodemailer":"^9.0.1","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","@sendgrid/mail":"^8.0.0","@types/express":"^5.0.0","firebase-admin":"^13.0.0 || ^14.0.0","class-validator":"^0.14.0 || ^0.15.0","reflect-metadata":"^0.2.0","class-transformer":"^0.5.0","@aws-sdk/client-ses":"^3.0.0","@aws-sdk/client-sns":"^3.0.0","@react-email/render":"^1.0.0 || ^2.0.0","@aws-sdk/client-dynamodb":"^3.0.0"},"peerDependenciesMeta":{"mjml":{"optional":true},"react":{"optional":true},"resend":{"optional":true},"twilio":{"optional":true},"express":{"optional":true},"ioredis":{"optional":true},"handlebars":{"optional":true},"mailgun.js":{"optional":true},"nodemailer":{"optional":true},"@sendgrid/mail":{"optional":true},"@types/express":{"optional":true},"firebase-admin":{"optional":true},"class-validator":{"optional":true},"class-transformer":{"optional":true},"@aws-sdk/client-ses":{"optional":true},"@aws-sdk/client-sns":{"optional":true},"@react-email/render":{"optional":true},"@aws-sdk/client-dynamodb":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-notification_1.1.0_1786446104162_0.6791137703933088","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@bymax-one/nest-notification","version":"1.1.1","keywords":["nestjs","notification","email","otp","sms","push","resend","sendgrid","ses","twilio","fcm","multi-tenant","redis","transactional","saas"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-notification@1.1.1","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-notification#readme","bugs":{"url":"https://github.com/bymaxone/nest-notification/issues"},"dist":{"shasum":"1222e4a8c0ad28ab07e36dec2d9224c3aa4b1b1e","tarball":"https://registry.npmjs.org/@bymax-one/nest-notification/-/nest-notification-1.1.1.tgz","fileCount":16,"integrity":"sha512-b/K1NkSOSfb9mXdZY0ZI5mPE9oFNyyVfDDF4+/unFFBBthdNN40LjqK0vBWqNSd3tbpKrPOlM550JSuNzscFEA==","signatures":[{"sig":"MEUCIQCgaO+aofX9rKkkNmDnKBAx0RUSmeEof8ccqloDbDCEbQIgG1Ym//reubZX9yBehtQg9sMIg4F2v5fwWKyZ0HnqUUU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-notification@1.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":527159},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"gitHead":"07092a1633aa2556c1446fc96e6816d1f9993c5f","scripts":{"lint":"eslint src scripts test","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","smoke":"node scripts/dogfood-smoke-test.mjs","prepare":"husky","release":"npm publish --provenance --access public","lint:fix":"eslint src scripts test --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts --runInBand","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:types":"tsc --noEmit -p tsconfig.typetest.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack .","check:mutants":"node scripts/check-mutation-directives.mjs","mutation:full":"node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm test:types && pnpm lint && pnpm check:mutants && pnpm check:no-prisma && pnpm test:cov:all && pnpm build && pnpm check:published","check:no-prisma":"grep -rq '@prisma/client' src; s=$?; [ $s -eq 1 ] && exit 0; [ $s -eq 0 ] && { echo check:no-prisma: found @prisma/client import in src/; exit 1; }; echo check:no-prisma: grep failed with exit $s; exit $s","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9d848c04-89b6-4beb-a7c4-50b00262ae8c"}},"repository":{"url":"git+https://github.com/bymaxone/nest-notification.git","type":"git"},"_npmVersion":"11.17.0","description":"Multi-channel notification library for NestJS — email, OTP, SMS, push — with pluggable providers and storage, multi-tenant by design, zero runtime dependencies.","directories":{},"lint-staged":{"*.{json,md,yml,yaml}":["prettier --write"],"*.{ts,tsx,js,mjs,cjs}":["eslint --fix","prettier --write"]},"sideEffects":false,"_nodeVersion":"24.19.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.20.0","devDependencies":{"jest":"^30.4.2","rxjs":"^7.8.0","tsup":"^8.5.1","husky":"^9.1.7","react":"^19","eslint":"^9.39.4","globals":"^17.6.0","ts-jest":"^29.4.11","ts-node":"^10.9.2","prettier":"^3.8.3","react-dom":"^19","@eslint/js":"^9.39.4","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24","lint-staged":"^17.2.0","@nestjs/core":"^11.1.20","@types/react":"^19","@nestjs/common":"^11.1.20","@commitlint/cli":"^21.2.1","@nestjs/testing":"^11.1.20","reflect-metadata":"^0.2.2","@testing-library/dom":"^10.4.1","eslint-plugin-import":"^2.32.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9","@testing-library/react":"^16","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.5","eslint-plugin-security":"^4.0.0","jest-environment-jsdom":"^30","@typescript-eslint/parser":"^8.59.3","@stryker-mutator/jest-runner":"^9","@commitlint/config-conventional":"^21.2.0","@typescript-eslint/eslint-plugin":"^8.59.3","eslint-import-resolver-typescript":"^4.4.4","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"mjml":"^5.0.0","rxjs":"^7.8.0","react":"^19.0.0","resend":"^4.0.0 || ^5.0.0 || ^6.0.0","twilio":"^5.0.0 || ^6.0.0","express":"^5.0.0","ioredis":"^6.0.0","handlebars":"^4.7.9","mailgun.js":"^11.0.0 || ^12.0.0 || ^13.0.0","nodemailer":"^9.0.1","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","@sendgrid/mail":"^8.0.0","@types/express":"^5.0.0","firebase-admin":"^13.0.0 || ^14.0.0","class-validator":"^0.14.0 || ^0.15.0","reflect-metadata":"^0.2.0","class-transformer":"^0.5.0","@aws-sdk/client-ses":"^3.0.0","@aws-sdk/client-sns":"^3.0.0","@react-email/render":"^1.0.0 || ^2.0.0","@aws-sdk/client-dynamodb":"^3.0.0"},"peerDependenciesMeta":{"mjml":{"optional":true},"react":{"optional":true},"resend":{"optional":true},"twilio":{"optional":true},"express":{"optional":true},"ioredis":{"optional":true},"handlebars":{"optional":true},"mailgun.js":{"optional":true},"nodemailer":{"optional":true},"@sendgrid/mail":{"optional":true},"@types/express":{"optional":true},"firebase-admin":{"optional":true},"class-validator":{"optional":true},"class-transformer":{"optional":true},"@aws-sdk/client-ses":{"optional":true},"@aws-sdk/client-sns":{"optional":true},"@react-email/render":{"optional":true},"@aws-sdk/client-dynamodb":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-notification_1.1.1_1786702440226_0.7663620837339262","host":"s3://npm-registry-packages-npm-production"}},"1.1.2":{"name":"@bymax-one/nest-notification","version":"1.1.2","keywords":["nestjs","notification","email","otp","sms","push","resend","sendgrid","ses","twilio","fcm","multi-tenant","redis","transactional","saas"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-notification@1.1.2","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-notification#readme","bugs":{"url":"https://github.com/bymaxone/nest-notification/issues"},"dist":{"shasum":"82d4adee643694d6d1768e68bf93a0856dd0bb2d","tarball":"https://registry.npmjs.org/@bymax-one/nest-notification/-/nest-notification-1.1.2.tgz","fileCount":16,"integrity":"sha512-bxaeRIn9fmDmKdl9X2FWnnSe/V/LSuNPj5bRLW18vaCrS1UenKEQg/K3kfV0Fj2cU3oJmbzFWa7pTNGnUmLU8Q==","signatures":[{"sig":"MEUCIQDRIrSe2Jt7nAFpzsoDZTgRCizprEB6R1fkUOCeB1wOeQIgSbP9SE1SIWkwWclYP5XAj511/tVy949bnGQlq3McmXE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-notification@1.1.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":531359},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"gitHead":"f877c6165e31d051d2ec1163a46049e2804a1a28","scripts":{"lint":"eslint src scripts test","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","smoke":"node scripts/dogfood-smoke-test.mjs","prepare":"husky","release":"npm publish --provenance --access public","lint:fix":"eslint src scripts test --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts --runInBand","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:types":"tsc --noEmit -p tsconfig.typetest.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack .","check:mutants":"node scripts/check-mutation-directives.mjs","mutation:full":"node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm test:types && pnpm lint && pnpm check:mutants && pnpm check:no-prisma && pnpm test:cov:all && pnpm build && pnpm check:published","check:no-prisma":"grep -rq '@prisma/client' src; s=$?; [ $s -eq 1 ] && exit 0; [ $s -eq 0 ] && { echo check:no-prisma: found @prisma/client import in src/; exit 1; }; echo check:no-prisma: grep failed with exit $s; exit $s","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9d848c04-89b6-4beb-a7c4-50b00262ae8c"}},"repository":{"url":"git+https://github.com/bymaxone/nest-notification.git","type":"git"},"_npmVersion":"11.17.0","description":"Multi-channel notification library for NestJS — email, OTP, SMS, push — with pluggable providers and storage, multi-tenant by design, zero runtime dependencies.","directories":{},"lint-staged":{"*.{json,md,yml,yaml}":["prettier --write"],"*.{ts,tsx,js,mjs,cjs}":["eslint --fix","prettier --write"]},"sideEffects":false,"_nodeVersion":"24.19.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.20.0","devDependencies":{"jest":"^30.4.2","rxjs":"^7.8.0","tsup":"^8.5.1","husky":"^9.1.7","react":"^19","eslint":"^9.39.4","globals":"^17.6.0","ts-jest":"^29.4.11","ts-node":"^10.9.2","prettier":"^3.8.3","react-dom":"^19","@eslint/js":"^9.39.4","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24","lint-staged":"^17.2.0","@nestjs/core":"^11.1.20","@types/react":"^19","@nestjs/common":"^11.1.20","@commitlint/cli":"^21.2.1","@nestjs/testing":"^11.1.20","reflect-metadata":"^0.2.2","@testing-library/dom":"^10.4.1","eslint-plugin-import":"^2.32.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9","@testing-library/react":"^16","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.5","eslint-plugin-security":"^4.0.0","jest-environment-jsdom":"^30","@typescript-eslint/parser":"^8.59.3","@stryker-mutator/jest-runner":"^9","@commitlint/config-conventional":"^21.2.0","@typescript-eslint/eslint-plugin":"^8.59.3","eslint-import-resolver-typescript":"^4.4.4","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"mjml":"^5.0.0","rxjs":"^7.8.0","react":"^19.0.0","resend":"^4.0.0 || ^5.0.0 || ^6.0.0","twilio":"^5.0.0 || ^6.0.0","express":"^5.0.0","ioredis":"^6.0.0","handlebars":"^4.7.9","mailgun.js":"^11.0.0 || ^12.0.0 || ^13.0.0","nodemailer":"^9.0.1","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","@sendgrid/mail":"^8.0.0","@types/express":"^5.0.0","firebase-admin":"^13.0.0 || ^14.0.0","class-validator":"^0.14.0 || ^0.15.0","reflect-metadata":"^0.2.0","class-transformer":"^0.5.0","@aws-sdk/client-ses":"^3.0.0","@aws-sdk/client-sns":"^3.0.0","@react-email/render":"^1.0.0 || ^2.0.0","@aws-sdk/client-dynamodb":"^3.0.0"},"peerDependenciesMeta":{"mjml":{"optional":true},"react":{"optional":true},"resend":{"optional":true},"twilio":{"optional":true},"express":{"optional":true},"ioredis":{"optional":true},"handlebars":{"optional":true},"mailgun.js":{"optional":true},"nodemailer":{"optional":true},"@sendgrid/mail":{"optional":true},"@types/express":{"optional":true},"firebase-admin":{"optional":true},"class-validator":{"optional":true},"class-transformer":{"optional":true},"@aws-sdk/client-ses":{"optional":true},"@aws-sdk/client-sns":{"optional":true},"@react-email/render":{"optional":true},"@aws-sdk/client-dynamodb":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-notification_1.1.2_1786725588932_0.5200457332076744","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"@bymax-one/nest-notification","version":"1.2.0","keywords":["nestjs","notification","email","otp","sms","push","resend","sendgrid","ses","twilio","fcm","multi-tenant","redis","transactional","saas"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-notification@1.2.0","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-notification#readme","bugs":{"url":"https://github.com/bymaxone/nest-notification/issues"},"dist":{"shasum":"f97507ed017fa1e29fa711aba9c068f7861bab0d","tarball":"https://registry.npmjs.org/@bymax-one/nest-notification/-/nest-notification-1.2.0.tgz","fileCount":16,"integrity":"sha512-SpL3hyxCF31zGtx3smwlIPGfi8SaCTN8/CCpdfLl0ArGFFZLGVEbPTHIFv/L/hSeh7zGIpUW01IZIw6oy2BejQ==","signatures":[{"sig":"MEQCIFShVtX+LeKySH8h38CpYttAbxK0nsVYphXEW2qOfdJnAiAk0NW2YFQrBVvwr34SYqqWrzyVTIHuHVwtOsisxECSuA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-notification@1.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":558002},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"gitHead":"9e5f108ae8aad41bd555114cc5c5f1110dc4187d","scripts":{"lint":"eslint src scripts test","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","smoke":"node scripts/dogfood-smoke-test.mjs","prepare":"husky","release":"npm publish --provenance --access public","lint:fix":"eslint src scripts test --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts --runInBand","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:types":"tsc --noEmit -p tsconfig.typetest.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack .","check:mutants":"node scripts/check-mutation-directives.mjs","mutation:full":"node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm test:types && pnpm lint && pnpm check:mutants && pnpm check:no-prisma && pnpm test:cov:all && pnpm build && pnpm check:published","check:no-prisma":"grep -rq '@prisma/client' src; s=$?; [ $s -eq 1 ] && exit 0; [ $s -eq 0 ] && { echo check:no-prisma: found @prisma/client import in src/; exit 1; }; echo check:no-prisma: grep failed with exit $s; exit $s","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9d848c04-89b6-4beb-a7c4-50b00262ae8c"}},"repository":{"url":"git+https://github.com/bymaxone/nest-notification.git","type":"git"},"_npmVersion":"11.17.0","description":"Multi-channel notification library for NestJS — email, OTP, SMS, push — with pluggable providers and storage, multi-tenant by design, zero runtime dependencies.","directories":{},"lint-staged":{"*.{json,md,yml,yaml}":["prettier --write"],"*.{ts,tsx,js,mjs,cjs}":["eslint --fix","prettier --write"]},"sideEffects":false,"_nodeVersion":"24.19.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.20.0","devDependencies":{"jest":"^30.4.2","rxjs":"^7.8.0","tsup":"^8.5.1","husky":"^9.1.7","react":"^19","eslint":"^9.39.4","globals":"^17.6.0","ts-jest":"^29.4.11","ts-node":"^10.9.2","prettier":"^3.8.3","react-dom":"^19","@eslint/js":"^9.39.4","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24","lint-staged":"^17.2.0","@nestjs/core":"^11.1.20","@types/react":"^19","@nestjs/common":"^11.1.20","@commitlint/cli":"^21.2.1","@nestjs/testing":"^11.1.20","reflect-metadata":"^0.2.2","@testing-library/dom":"^10.4.1","eslint-plugin-import":"^2.32.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9","@testing-library/react":"^16","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.5","eslint-plugin-security":"^4.0.0","jest-environment-jsdom":"^30","@typescript-eslint/parser":"^8.59.3","@stryker-mutator/jest-runner":"^9","@commitlint/config-conventional":"^21.2.0","@typescript-eslint/eslint-plugin":"^8.59.3","eslint-import-resolver-typescript":"^4.4.4","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"mjml":"^5.0.0","rxjs":"^7.8.0","react":"^19.0.0","resend":"^4.0.0 || ^5.0.0 || ^6.0.0","twilio":"^5.0.0 || ^6.0.0","express":"^5.0.0","ioredis":"^6.0.0","handlebars":"^4.7.9","mailgun.js":"^11.0.0 || ^12.0.0 || ^13.0.0","nodemailer":"^9.0.1","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","@sendgrid/mail":"^8.0.0","@types/express":"^5.0.0","firebase-admin":"^13.0.0 || ^14.0.0","class-validator":"^0.14.0 || ^0.15.0","reflect-metadata":"^0.2.0","class-transformer":"^0.5.0","@aws-sdk/client-ses":"^3.0.0","@aws-sdk/client-sns":"^3.0.0","@react-email/render":"^1.0.0 || ^2.0.0","@aws-sdk/client-dynamodb":"^3.0.0"},"peerDependenciesMeta":{"mjml":{"optional":true},"react":{"optional":true},"resend":{"optional":true},"twilio":{"optional":true},"express":{"optional":true},"ioredis":{"optional":true},"handlebars":{"optional":true},"mailgun.js":{"optional":true},"nodemailer":{"optional":true},"@sendgrid/mail":{"optional":true},"@types/express":{"optional":true},"firebase-admin":{"optional":true},"class-validator":{"optional":true},"class-transformer":{"optional":true},"@aws-sdk/client-ses":{"optional":true},"@aws-sdk/client-sns":{"optional":true},"@react-email/render":{"optional":true},"@aws-sdk/client-dynamodb":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-notification_1.2.0_1786801693793_0.40332188840934236","host":"s3://npm-registry-packages-npm-production"}},"1.2.1":{"name":"@bymax-one/nest-notification","version":"1.2.1","keywords":["nestjs","notification","email","otp","sms","push","resend","sendgrid","ses","twilio","fcm","multi-tenant","redis","transactional","saas"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-notification@1.2.1","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-notification#readme","bugs":{"url":"https://github.com/bymaxone/nest-notification/issues"},"dist":{"shasum":"b0282b903414c4c6646c4eb2e9ff9e4ab4b423ca","tarball":"https://registry.npmjs.org/@bymax-one/nest-notification/-/nest-notification-1.2.1.tgz","fileCount":16,"integrity":"sha512-klPZjOqB5IQnFxGTrjiaHHWZm5x1VgST3LLL7Hqc7G2DXa8ZvFWjABpxtxK26ZmGFiVvyJwbwg0bb91QOaT+mg==","signatures":[{"sig":"MEQCIHihmbOKxQ0bkSwbvInSenKjxX2JTdhxVnXQkjJJu6+4AiAiUcaRP1/5fZK5gtA7JkNkt7+Gqh1h3b3j2yJfyw82KA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-notification@1.2.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":582662},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"gitHead":"fb54491afe08cb0f98654984aa10a0650728199d","scripts":{"lint":"eslint src scripts test","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","smoke":"node scripts/dogfood-smoke-test.mjs","prepare":"husky","release":"npm publish --provenance --access public","lint:fix":"eslint src scripts test --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts --runInBand","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:types":"tsc --noEmit -p tsconfig.typetest.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack .","check:mutants":"node scripts/check-mutation-directives.mjs","mutation:full":"node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm test:types && pnpm lint && pnpm check:mutants && pnpm check:no-prisma && pnpm test:cov:all && pnpm build && pnpm check:published","check:no-prisma":"grep -rq '@prisma/client' src; s=$?; [ $s -eq 1 ] && exit 0; [ $s -eq 0 ] && { echo check:no-prisma: found @prisma/client import in src/; exit 1; }; echo check:no-prisma: grep failed with exit $s; exit $s","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9d848c04-89b6-4beb-a7c4-50b00262ae8c"}},"repository":{"url":"git+https://github.com/bymaxone/nest-notification.git","type":"git"},"_npmVersion":"11.17.0","description":"Multi-channel notification library for NestJS — email, OTP, SMS, push — with pluggable providers and storage, multi-tenant by design, zero runtime dependencies.","directories":{},"lint-staged":{"*.{json,md,yml,yaml}":["prettier --write"],"*.{ts,tsx,js,mjs,cjs}":["eslint --fix","prettier --write"]},"sideEffects":false,"_nodeVersion":"24.19.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.20.0","devDependencies":{"jest":"^30.4.2","rxjs":"^7.8.0","tsup":"^8.5.1","husky":"^9.1.7","react":"^19","eslint":"^9.39.4","globals":"^17.6.0","ts-jest":"^29.4.11","ts-node":"^10.9.2","prettier":"^3.8.3","react-dom":"^19","@eslint/js":"^9.39.4","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24","lint-staged":"^17.2.0","@nestjs/core":"^11.1.20","@types/react":"^19","@nestjs/common":"^11.1.20","@commitlint/cli":"^21.2.1","@nestjs/testing":"^11.1.20","reflect-metadata":"^0.2.2","@testing-library/dom":"^10.4.1","eslint-plugin-import":"^2.32.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9","@testing-library/react":"^16","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.5","eslint-plugin-security":"^4.0.0","jest-environment-jsdom":"^30","@typescript-eslint/parser":"^8.59.3","@stryker-mutator/jest-runner":"^9","@commitlint/config-conventional":"^21.2.0","@typescript-eslint/eslint-plugin":"^8.59.3","eslint-import-resolver-typescript":"^4.4.4","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"mjml":"^5.0.0","rxjs":"^7.8.0","react":"^19.0.0","resend":"^4.0.0 || ^5.0.0 || ^6.0.0","twilio":"^5.0.0 || ^6.0.0","express":"^5.0.0","ioredis":"^6.0.0","handlebars":"^4.7.9","mailgun.js":"^11.0.0 || ^12.0.0 || ^13.0.0","nodemailer":"^9.0.1","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","@sendgrid/mail":"^8.0.0","@types/express":"^5.0.0","firebase-admin":"^13.0.0 || ^14.0.0","class-validator":"^0.14.0 || ^0.15.0","reflect-metadata":"^0.2.0","class-transformer":"^0.5.0","@aws-sdk/client-ses":"^3.0.0","@aws-sdk/client-sns":"^3.0.0","@react-email/render":"^1.0.0 || ^2.0.0","@aws-sdk/client-dynamodb":"^3.0.0"},"peerDependenciesMeta":{"mjml":{"optional":true},"react":{"optional":true},"resend":{"optional":true},"twilio":{"optional":true},"express":{"optional":true},"ioredis":{"optional":true},"handlebars":{"optional":true},"mailgun.js":{"optional":true},"nodemailer":{"optional":true},"@sendgrid/mail":{"optional":true},"@types/express":{"optional":true},"firebase-admin":{"optional":true},"class-validator":{"optional":true},"class-transformer":{"optional":true},"@aws-sdk/client-ses":{"optional":true},"@aws-sdk/client-sns":{"optional":true},"@react-email/render":{"optional":true},"@aws-sdk/client-dynamodb":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-notification_1.2.1_1786834351327_0.03729988666543571","host":"s3://npm-registry-packages-npm-production"}},"1.2.2":{"name":"@bymax-one/nest-notification","version":"1.2.2","keywords":["nestjs","notification","email","otp","sms","push","resend","sendgrid","ses","twilio","fcm","multi-tenant","redis","transactional","saas"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-notification@1.2.2","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-notification#readme","bugs":{"url":"https://github.com/bymaxone/nest-notification/issues"},"dist":{"shasum":"8cc921fb5844b4a1bea2cedfd40cf99c2e45eb0a","tarball":"https://registry.npmjs.org/@bymax-one/nest-notification/-/nest-notification-1.2.2.tgz","fileCount":16,"integrity":"sha512-s4tz82RqEbL7vApYXUwEQAWpP6CAHcL0z5oEAkPtBKUGMknrlq70b4i3l/ck0w/SW6i1kVXlOxUzZ++2YhGz+Q==","signatures":[{"sig":"MEQCIBvb4gRfgf14XgNd6zdYDlkHlRW7ALQmkpUhyuqlth9uAiAetThgp8a8qQFkLy30y8uAgc9AjP5VzcfrSQWbvAEV5Q==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-notification@1.2.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":590114},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"gitHead":"0a1298667551016d866d0985691078b5abb89b01","scripts":{"lint":"eslint src scripts test","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","smoke":"node scripts/dogfood-smoke-test.mjs","prepare":"husky","release":"npm publish --provenance --access public","lint:fix":"eslint src scripts test --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts --runInBand","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:types":"tsc --noEmit -p tsconfig.typetest.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack .","check:mutants":"node scripts/check-mutation-directives.mjs","mutation:full":"node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm test:types && pnpm lint && pnpm check:mutants && pnpm check:no-prisma && pnpm test:cov:all && pnpm build && pnpm check:published","check:no-prisma":"grep -rq '@prisma/client' src; s=$?; [ $s -eq 1 ] && exit 0; [ $s -eq 0 ] && { echo check:no-prisma: found @prisma/client import in src/; exit 1; }; echo check:no-prisma: grep failed with exit $s; exit $s","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9d848c04-89b6-4beb-a7c4-50b00262ae8c"}},"repository":{"url":"git+https://github.com/bymaxone/nest-notification.git","type":"git"},"_npmVersion":"11.17.0","description":"Multi-channel notification library for NestJS — email, OTP, SMS, push — with pluggable providers and storage, multi-tenant by design, zero runtime dependencies.","directories":{},"lint-staged":{"*.{json,md,yml,yaml}":["prettier --write"],"*.{ts,tsx,js,mjs,cjs}":["eslint --fix","prettier --write"]},"sideEffects":false,"_nodeVersion":"24.19.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.20.0","devDependencies":{"jest":"^30.4.2","rxjs":"^7.8.0","tsup":"^8.5.1","husky":"^9.1.7","react":"^19","eslint":"^9.39.4","globals":"^17.6.0","ts-jest":"^29.4.11","ts-node":"^10.9.2","prettier":"^3.8.3","react-dom":"^19","@eslint/js":"^9.39.4","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24","lint-staged":"^17.2.0","@nestjs/core":"^11.1.20","@types/react":"^19","@nestjs/common":"^11.1.20","@commitlint/cli":"^21.2.1","@nestjs/testing":"^11.1.20","reflect-metadata":"^0.2.2","@testing-library/dom":"^10.4.1","eslint-plugin-import":"^2.32.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9","@testing-library/react":"^16","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.5","eslint-plugin-security":"^4.0.0","jest-environment-jsdom":"^30","@typescript-eslint/parser":"^8.59.3","@stryker-mutator/jest-runner":"^9","@commitlint/config-conventional":"^21.2.0","@typescript-eslint/eslint-plugin":"^8.59.3","eslint-import-resolver-typescript":"^4.4.4","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"mjml":"^5.0.0","rxjs":"^7.8.0","react":"^19.0.0","resend":"^4.0.0 || ^5.0.0 || ^6.0.0","twilio":"^5.0.0 || ^6.0.0","express":"^5.0.0","ioredis":"^6.0.0","handlebars":"^4.7.9","mailgun.js":"^11.0.0 || ^12.0.0 || ^13.0.0","nodemailer":"^9.0.1","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","@sendgrid/mail":"^8.0.0","@types/express":"^5.0.0","firebase-admin":"^13.0.0 || ^14.0.0","class-validator":"^0.14.0 || ^0.15.0","reflect-metadata":"^0.2.0","class-transformer":"^0.5.0","@aws-sdk/client-ses":"^3.0.0","@aws-sdk/client-sns":"^3.0.0","@react-email/render":"^1.0.0 || ^2.0.0","@aws-sdk/client-dynamodb":"^3.0.0"},"peerDependenciesMeta":{"mjml":{"optional":true},"react":{"optional":true},"resend":{"optional":true},"twilio":{"optional":true},"express":{"optional":true},"ioredis":{"optional":true},"handlebars":{"optional":true},"mailgun.js":{"optional":true},"nodemailer":{"optional":true},"@sendgrid/mail":{"optional":true},"@types/express":{"optional":true},"firebase-admin":{"optional":true},"class-validator":{"optional":true},"class-transformer":{"optional":true},"@aws-sdk/client-ses":{"optional":true},"@aws-sdk/client-sns":{"optional":true},"@react-email/render":{"optional":true},"@aws-sdk/client-dynamodb":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-notification_1.2.2_1786837225734_0.4387056377817249","host":"s3://npm-registry-packages-npm-production"}},"1.3.0":{"name":"@bymax-one/nest-notification","version":"1.3.0","keywords":["nestjs","notification","email","otp","sms","push","resend","sendgrid","ses","twilio","fcm","multi-tenant","redis","transactional","saas"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-notification@1.3.0","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-notification#readme","bugs":{"url":"https://github.com/bymaxone/nest-notification/issues"},"dist":{"shasum":"d1391fb57043704695cf8209c2582dd4f612222e","tarball":"https://registry.npmjs.org/@bymax-one/nest-notification/-/nest-notification-1.3.0.tgz","fileCount":20,"integrity":"sha512-zbFmfWVcc4KBdW+IfV80m/Jb5iyf8K+iTww6OYTbbngu4PSwDwDyPlXxPAGZlZChVm993LqEycDPxGJUwVIP6w==","signatures":[{"sig":"MEQCIF+VakyMi5iB1pOcOYP83EinDdUJToMtZcmBfVh81vkuAiBGd+Couh9ODwHF4THV+NdRjXFomvtQqT8dSTDx1Fh8EQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-notification@1.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":661663},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./testing":{"import":{"types":"./dist/testing/index.d.ts","default":"./dist/testing/index.mjs"},"require":{"types":"./dist/testing/index.d.cts","default":"./dist/testing/index.cjs"}},"./package.json":"./package.json"},"gitHead":"cf3f48c15e3109a9c350d1c3b548990a633acfdf","scripts":{"lint":"eslint src scripts test","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","smoke":"node scripts/dogfood-smoke-test.mjs","prepare":"husky","release":"npm publish --provenance --access public","lint:fix":"eslint src scripts test --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts --runInBand","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:types":"tsc --noEmit -p tsconfig.typetest.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack .","check:mutants":"node scripts/check-mutation-directives.mjs","mutation:full":"node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm test:types && pnpm lint && pnpm check:mutants && pnpm check:no-prisma && pnpm test:cov:all && pnpm build && pnpm check:published","check:no-prisma":"grep -rq '@prisma/client' src; s=$?; [ $s -eq 1 ] && exit 0; [ $s -eq 0 ] && { echo check:no-prisma: found @prisma/client import in src/; exit 1; }; echo check:no-prisma: grep failed with exit $s; exit $s","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9d848c04-89b6-4beb-a7c4-50b00262ae8c"}},"repository":{"url":"git+https://github.com/bymaxone/nest-notification.git","type":"git"},"_npmVersion":"11.17.0","description":"Multi-channel notification library for NestJS — email, OTP, SMS, push — with pluggable providers and storage, multi-tenant by design, zero runtime dependencies.","directories":{},"lint-staged":{"*.{json,md,yml,yaml}":["prettier --write"],"*.{ts,tsx,js,mjs,cjs}":["eslint --fix","prettier --write"]},"sideEffects":false,"_nodeVersion":"24.19.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"],"testing":["./dist/testing/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.20.0","devDependencies":{"jest":"^30.4.2","rxjs":"^7.8.0","tsup":"^8.5.1","husky":"^9.1.7","react":"^19","eslint":"^9.39.4","globals":"^17.6.0","ts-jest":"^29.4.11","ts-node":"^10.9.2","prettier":"^3.8.3","react-dom":"^19","@eslint/js":"^9.39.4","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24","lint-staged":"^17.2.0","@nestjs/core":"^11.1.20","@types/react":"^19","@nestjs/common":"^11.1.20","@commitlint/cli":"^21.2.1","@nestjs/testing":"^11.1.20","reflect-metadata":"^0.2.2","@testing-library/dom":"^10.4.1","eslint-plugin-import":"^2.32.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9","@testing-library/react":"^16","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.5","eslint-plugin-security":"^4.0.0","jest-environment-jsdom":"^30","@typescript-eslint/parser":"^8.59.3","@stryker-mutator/jest-runner":"^9","@commitlint/config-conventional":"^21.2.0","@typescript-eslint/eslint-plugin":"^8.59.3","eslint-import-resolver-typescript":"^4.4.4","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"mjml":"^5.0.0","rxjs":"^7.8.0","react":"^19.0.0","resend":"^4.0.0 || ^5.0.0 || ^6.0.0","twilio":"^5.0.0 || ^6.0.0","express":"^5.0.0","ioredis":"^6.0.0","handlebars":"^4.7.9","mailgun.js":"^11.0.0 || ^12.0.0 || ^13.0.0","nodemailer":"^9.0.1","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","@sendgrid/mail":"^8.0.0","@types/express":"^5.0.0","firebase-admin":"^13.0.0 || ^14.0.0","class-validator":"^0.14.0 || ^0.15.0","reflect-metadata":"^0.2.0","class-transformer":"^0.5.0","@aws-sdk/client-ses":"^3.0.0","@aws-sdk/client-sns":"^3.0.0","@react-email/render":"^1.0.0 || ^2.0.0","@aws-sdk/client-dynamodb":"^3.0.0"},"peerDependenciesMeta":{"mjml":{"optional":true},"react":{"optional":true},"resend":{"optional":true},"twilio":{"optional":true},"express":{"optional":true},"ioredis":{"optional":true},"handlebars":{"optional":true},"mailgun.js":{"optional":true},"nodemailer":{"optional":true},"@sendgrid/mail":{"optional":true},"@types/express":{"optional":true},"firebase-admin":{"optional":true},"class-validator":{"optional":true},"class-transformer":{"optional":true},"@aws-sdk/client-ses":{"optional":true},"@aws-sdk/client-sns":{"optional":true},"@react-email/render":{"optional":true},"@aws-sdk/client-dynamodb":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-notification_1.3.0_1787051628476_0.6456355374785694","host":"s3://npm-registry-packages-npm-production"}},"1.3.1":{"name":"@bymax-one/nest-notification","version":"1.3.1","keywords":["nestjs","notification","email","otp","sms","push","resend","sendgrid","ses","twilio","fcm","multi-tenant","redis","transactional","saas"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-notification@1.3.1","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-notification#readme","bugs":{"url":"https://github.com/bymaxone/nest-notification/issues"},"dist":{"shasum":"a98d3a87f4d5a9fbb224e32d38319c26b4da6aec","tarball":"https://registry.npmjs.org/@bymax-one/nest-notification/-/nest-notification-1.3.1.tgz","fileCount":20,"integrity":"sha512-YgPP2eKhCA7hohZSNLxog5+FuUAvYPE8FYvdFvRGQaiff/rRGAm16RYM408lWBLed+d8I7U1kI5NC+uBhctWEA==","signatures":[{"sig":"MEUCIQD1WdYZKKWTX/xjJ5Ai7euK/JYFk2U8K5hvjC0ZYzmPZwIgGm9BsdEMjNZLuySD+9tDAoIlAvqS6cEqYsHq9voCiD4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-notification@1.3.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":666372},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./testing":{"import":{"types":"./dist/testing/index.d.ts","default":"./dist/testing/index.mjs"},"require":{"types":"./dist/testing/index.d.cts","default":"./dist/testing/index.cjs"}},"./package.json":"./package.json"},"gitHead":"40ed4258f57016b3ff649fb6f6b29ec439be669b","scripts":{"lint":"eslint src scripts test","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","smoke":"node scripts/dogfood-smoke-test.mjs","prepare":"husky","release":"npm publish --provenance --access public","lint:fix":"eslint src scripts test --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts --runInBand","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:types":"tsc --noEmit -p tsconfig.typetest.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack .","check:mutants":"node scripts/check-mutation-directives.mjs","mutation:full":"node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm test:types && pnpm lint && pnpm check:mutants && pnpm check:no-prisma && pnpm test:cov:all && pnpm build && pnpm check:published","check:no-prisma":"grep -rq '@prisma/client' src; s=$?; [ $s -eq 1 ] && exit 0; [ $s -eq 0 ] && { echo check:no-prisma: found @prisma/client import in src/; exit 1; }; echo check:no-prisma: grep failed with exit $s; exit $s","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9d848c04-89b6-4beb-a7c4-50b00262ae8c"}},"repository":{"url":"git+https://github.com/bymaxone/nest-notification.git","type":"git"},"_npmVersion":"11.17.0","description":"Multi-channel notification library for NestJS — email, OTP, SMS, push — with pluggable providers and storage, multi-tenant by design, zero runtime dependencies.","directories":{},"lint-staged":{"*.{json,md,yml,yaml}":["prettier --write"],"*.{ts,tsx,js,mjs,cjs}":["eslint --fix","prettier --write"]},"sideEffects":false,"_nodeVersion":"24.19.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"],"testing":["./dist/testing/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.20.0","devDependencies":{"jest":"^30.4.2","rxjs":"^7.8.0","tsup":"^8.5.1","husky":"^9.1.7","react":"^19","eslint":"^9.39.4","globals":"^17.6.0","ts-jest":"^29.4.11","ts-node":"^10.9.2","prettier":"^3.8.3","react-dom":"^19","@eslint/js":"^9.39.4","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24","lint-staged":"^17.2.0","@nestjs/core":"^11.1.20","@types/react":"^19","@nestjs/common":"^11.1.20","@commitlint/cli":"^21.2.1","@nestjs/testing":"^11.1.20","reflect-metadata":"^0.2.2","@testing-library/dom":"^10.4.1","eslint-plugin-import":"^2.32.0","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9","@testing-library/react":"^16","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.5","eslint-plugin-security":"^4.0.0","jest-environment-jsdom":"^30","@typescript-eslint/parser":"^8.59.3","@stryker-mutator/jest-runner":"^9","@commitlint/config-conventional":"^21.2.0","@typescript-eslint/eslint-plugin":"^8.59.3","eslint-import-resolver-typescript":"^4.4.4","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"mjml":"^5.0.0","rxjs":"^7.8.0","react":"^19.0.0","resend":"^4.0.0 || ^5.0.0 || ^6.0.0","twilio":"^5.0.0 || ^6.0.0","express":"^5.0.0","ioredis":"^6.0.0","handlebars":"^4.7.9","mailgun.js":"^11.0.0 || ^12.0.0 || ^13.0.0","nodemailer":"^9.0.1","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","@sendgrid/mail":"^8.0.0","@types/express":"^5.0.0","firebase-admin":"^13.0.0 || ^14.0.0","class-validator":"^0.14.0 || ^0.15.0","reflect-metadata":"^0.2.0","class-transformer":"^0.5.0","@aws-sdk/client-ses":"^3.0.0","@aws-sdk/client-sns":"^3.0.0","@react-email/render":"^1.0.0 || ^2.0.0","@aws-sdk/client-dynamodb":"^3.0.0"},"peerDependenciesMeta":{"mjml":{"optional":true},"react":{"optional":true},"resend":{"optional":true},"twilio":{"optional":true},"express":{"optional":true},"ioredis":{"optional":true},"handlebars":{"optional":true},"mailgun.js":{"optional":true},"nodemailer":{"optional":true},"@sendgrid/mail":{"optional":true},"@types/express":{"optional":true},"firebase-admin":{"optional":true},"class-validator":{"optional":true},"class-transformer":{"optional":true},"@aws-sdk/client-ses":{"optional":true},"@aws-sdk/client-sns":{"optional":true},"@react-email/render":{"optional":true},"@aws-sdk/client-dynamodb":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-notification_1.3.1_1787052692094_0.2900629558856611","host":"s3://npm-registry-packages-npm-production"}},"1.4.0":{"_id":"@bymax-one/nest-notification@1.4.0","bugs":{"url":"https://github.com/bymaxone/nest-notification/issues"},"dist":{"shasum":"31976ee891c8562dff962a6f328ae60b61a9280d","tarball":"https://registry.npmjs.org/@bymax-one/nest-notification/-/nest-notification-1.4.0.tgz","fileCount":20,"integrity":"sha512-skcHYmabBm86GMOYLcRXUPyILdbwKruu5s7mW4kPRquHHtaPXK8/QpmGHiBagr926v1LPpyQlqGKu5/gk68SXg==","signatures":[{"sig":"MEUCIQCwTlhLFhEXCOtpYWkMCJ6OnsyU8zrccDixPvJ1Fm29fwIgQTdasT+4u4N9MVH4MQbMdP/IZPC9HCXQG8Sy8hCyCdc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHO67s1ut9+oIRI8qwjcZ1l3+lqMJu/9Hx+LZvQqQ0lVAiA2zVysrndOk5bOCx5N9nAZaX9D+hDuYs5MuX09nIj52A=="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-notification@1.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":691517},"main":"./dist/server/index.cjs","name":"@bymax-one/nest-notification","type":"module","types":"./dist/server/index.d.cts","author":{"name":"Bymax One","email":"support@bymax.one"},"module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./react":{"import":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.mjs"},"require":{"types":"./dist/react/index.d.cts","default":"./dist/react/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./testing":{"import":{"types":"./dist/testing/index.d.ts","default":"./dist/testing/index.mjs"},"require":{"types":"./dist/testing/index.d.cts","default":"./dist/testing/index.cjs"}},"./package.json":"./package.json"},"gitHead":"783c0990bfe2d9e7caf88d1491a9c35d480b2e0a","license":"MIT","scripts":{"lint":"eslint src scripts test --max-warnings 0","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"rm -rf dist coverage","smoke":"node scripts/dogfood-smoke-test.mjs","prepare":"husky","release":"npm publish --provenance --access public","lint:fix":"eslint src scripts test --fix --max-warnings 0","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts --runInBand","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","test:types":"tsc --noEmit -p tsconfig.typetest.json","test:watch":"jest --watch","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack .","check:mutants":"node scripts/check-mutation-directives.mjs","mutation:full":"node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm test:types && pnpm lint && pnpm check:mutants && pnpm check:no-prisma && pnpm test:cov:all && pnpm build && pnpm check:published","check:no-prisma":"grep -rq '@prisma/client' src; s=$?; [ $s -eq 1 ] && exit 0; [ $s -eq 0 ] && { echo check:no-prisma: found @prisma/client import in src/; exit 1; }; echo check:no-prisma: grep failed with exit $s; exit $s","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly"},"version":"1.4.0","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9d848c04-89b6-4beb-a7c4-50b00262ae8c"}},"homepage":"https://github.com/bymaxone/nest-notification#readme","keywords":["nestjs","notification","email","otp","sms","push","resend","sendgrid","ses","twilio","fcm","multi-tenant","redis","transactional","saas"],"repository":{"url":"git+https://github.com/bymaxone/nest-notification.git","type":"git"},"_npmVersion":"11.19.0","description":"Multi-channel notification library for NestJS — email, OTP, SMS, push — with pluggable providers and storage, multi-tenant by design, zero runtime dependencies.","directories":{},"lint-staged":{"*.{json,md,yml,yaml}":["prettier --write"],"*.{ts,tsx,js,mjs,cjs}":["eslint --fix","prettier --write"]},"maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"sideEffects":false,"_nodeVersion":"24.20.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"typesVersions":{"*":{"react":["./dist/react/index.d.cts"],"shared":["./dist/shared/index.d.cts"],"testing":["./dist/testing/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.20.0","devDependencies":{"jest":"^30.4.2","rxjs":"^7.8.0","tsup":"^8.5.1","husky":"^9.1.7","react":"^19","eslint":"^10.9.1","globals":"^17.6.0","ts-jest":"^29.4.11","ts-node":"^10.9.2","prettier":"^3.8.3","react-dom":"^19","@eslint/js":"^10.0.1","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24","lint-staged":"^17.2.0","@nestjs/core":"^11.1.20","@types/react":"^19","@nestjs/common":"^11.1.20","@commitlint/cli":"^21.2.1","@nestjs/testing":"^11.1.20","reflect-metadata":"^0.2.2","@testing-library/dom":"^10.4.1","@arethetypeswrong/cli":"^0.18.5","@stryker-mutator/core":"^9","@testing-library/react":"^16","eslint-config-prettier":"^10.1.8","eslint-plugin-import-x":"^4.17.1","eslint-plugin-prettier":"^5.5.5","eslint-plugin-security":"^4.0.0","jest-environment-jsdom":"^30","@typescript-eslint/parser":"^8.59.3","@stryker-mutator/jest-runner":"^9","@commitlint/config-conventional":"^21.2.0","@typescript-eslint/eslint-plugin":"^8.59.3","eslint-import-resolver-typescript":"^4.4.4","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"mjml":"^5.0.0","rxjs":"^7.8.0","react":"^19.0.0","resend":"^4.0.0 || ^5.0.0 || ^6.0.0","twilio":"^5.0.0 || ^6.0.0","express":"^5.0.0","ioredis":"^6.0.0","handlebars":"^4.7.9","mailgun.js":"^11.0.0 || ^12.0.0 || ^13.0.0","nodemailer":"^9.0.1","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","@sendgrid/mail":"^8.0.0","@types/express":"^5.0.0","firebase-admin":"^13.0.0 || ^14.0.0","class-validator":"^0.14.0 || ^0.15.0","reflect-metadata":"^0.2.0","class-transformer":"^0.5.0","@aws-sdk/client-ses":"^3.0.0","@aws-sdk/client-sns":"^3.0.0","@react-email/render":"^1.0.0 || ^2.0.0","@aws-sdk/client-dynamodb":"^3.0.0"},"peerDependenciesMeta":{"mjml":{"optional":true},"react":{"optional":true},"resend":{"optional":true},"twilio":{"optional":true},"express":{"optional":true},"ioredis":{"optional":true},"handlebars":{"optional":true},"mailgun.js":{"optional":true},"nodemailer":{"optional":true},"@sendgrid/mail":{"optional":true},"@types/express":{"optional":true},"firebase-admin":{"optional":true},"class-validator":{"optional":true},"class-transformer":{"optional":true},"@aws-sdk/client-ses":{"optional":true},"@aws-sdk/client-sns":{"optional":true},"@react-email/render":{"optional":true},"@aws-sdk/client-dynamodb":{"optional":true}},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/nest-notification_1.4.0_1788608854959_0.7139409224974982"}}},"time":{"created":"2026-07-30T17:00:52.185Z","modified":"2026-09-05T11:47:35.409Z","1.0.0":"2026-07-30T17:00:52.584Z","1.0.1":"2026-08-02T02:11:10.650Z","1.0.2":"2026-08-04T15:46:04.982Z","1.0.4":"2026-08-06T11:31:56.479Z","1.0.5":"2026-08-08T15:38:42.557Z","1.0.6":"2026-08-10T17:02:55.997Z","1.1.0":"2026-08-11T11:01:44.319Z","1.1.1":"2026-08-14T10:14:00.372Z","1.1.2":"2026-08-14T16:39:49.096Z","1.2.0":"2026-08-15T13:48:13.960Z","1.2.1":"2026-08-15T22:52:31.492Z","1.2.2":"2026-08-15T23:40:25.899Z","1.3.0":"2026-08-18T11:13:48.627Z","1.3.1":"2026-08-18T11:31:32.279Z","1.4.0":"2026-09-05T11:47:35.063Z"},"bugs":{"url":"https://github.com/bymaxone/nest-notification/issues"},"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","homepage":"https://github.com/bymaxone/nest-notification#readme","keywords":["nestjs","notification","email","otp","sms","push","resend","sendgrid","ses","twilio","fcm","multi-tenant","redis","transactional","saas"],"repository":{"url":"git+https://github.com/bymaxone/nest-notification.git","type":"git"},"description":"Multi-channel notification library for NestJS — email, OTP, SMS, push — with pluggable providers and storage, multi-tenant by design, zero runtime dependencies.","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"readme":"<p align=\"center\">\n  <img src=\"https://img.shields.io/badge/%40bymax--one-nest--notification-000000?style=for-the-badge&logo=nestjs&logoColor=E0234E\" alt=\"@bymax-one/nest-notification\" />\n</p>\n\n<h1 align=\"center\">@bymax-one/nest-notification</h1>\n\n<p align=\"center\">\n  <strong>Transactional notification for NestJS & React</strong><br />\n  <sub>Email · OTP · Multi-Tenant · Pluggable Providers · Prisma-Free · Zero Runtime Dependencies</sub>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/@bymax-one/nest-notification\"><img src=\"https://img.shields.io/npm/v/@bymax-one/nest-notification?style=flat-square&colorA=000000&colorB=000000\" alt=\"npm version\" /></a>\n  <a href=\"https://www.npmjs.com/package/@bymax-one/nest-notification\"><img src=\"https://img.shields.io/npm/dm/@bymax-one/nest-notification?style=flat-square&colorA=000000&colorB=000000\" alt=\"npm downloads\" /></a>\n  <a href=\"https://github.com/bymaxone/nest-notification/actions/workflows/ci.yml\"><img src=\"https://img.shields.io/github/actions/workflow/status/bymaxone/nest-notification/ci.yml?branch=main&style=flat-square&colorA=000000&label=CI\" alt=\"CI status\" /></a>\n  <a href=\"https://github.com/bymaxone/nest-notification/actions/workflows/ci.yml\"><img src=\"https://img.shields.io/badge/coverage-100%25-brightgreen?style=flat-square&colorA=000000\" alt=\"coverage\" /></a>\n  <a href=\"https://github.com/bymaxone/nest-notification/blob/main/docs/mutation_testing_results.md\"><img src=\"https://img.shields.io/badge/mutation-100.00%25-brightgreen?style=flat-square&colorA=000000\" alt=\"mutation score\" /></a>\n  <a href=\"https://scorecard.dev/viewer/?uri=github.com/bymaxone/nest-notification\"><img src=\"https://api.scorecard.dev/projects/github.com/bymaxone/nest-notification/badge?style=flat-square\" alt=\"OpenSSF Scorecard\" /></a>\n  <a href=\"https://github.com/bymaxone/nest-notification/blob/main/LICENSE\"><img src=\"https://img.shields.io/github/license/bymaxone/nest-notification?style=flat-square&colorA=000000&colorB=000000\" alt=\"license\" /></a>\n  <a href=\"https://www.typescriptlang.org/\"><img src=\"https://img.shields.io/badge/TypeScript-strict-3178C6?style=flat-square&logo=typescript&logoColor=white\" alt=\"TypeScript\" /></a>\n  <a href=\"https://nodejs.org/\"><img src=\"https://img.shields.io/badge/Node.js-24%2B-339933?style=flat-square&logo=node.js&logoColor=white\" alt=\"Node.js\" /></a>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://github.com/bymaxone/nest-notification\">GitHub</a> ·\n  <a href=\"https://github.com/bymaxone/nest-notification/issues\">Issues</a> ·\n  <a href=\"#-quick-start\">Quick Start</a> ·\n  <a href=\"#-api-reference\">API Reference</a> ·\n  <a href=\"https://github.com/bymaxone/nest-notification-example\">Example App</a>\n</p>\n\n---\n\n## ✨ Overview\n\n`@bymax-one/nest-notification` is a **transactional notification library for NestJS 11** that ships two channels — **email** and **OTP** (one-time passwords) — behind a single dynamic module, plus a React subpath for the OTP input box.\n\nEverything that touches the outside world — the email transport, the OTP store, the template renderer, the audit sink — is an **interface** you implement or pick from the bundled reference adapters. The same module runs on Resend or SendGrid, on Redis or an in-memory map, with a Prisma audit log or none at all, without changing a single call site.\n\n### Why nest-notification?\n\n- **🔌 Your database, your provider** — The library defines the contracts (`IEmailProvider`, `IOtpStorage`, `IEmailTemplateRenderer`, `INotificationLogRepository`). You supply the implementations. It **never imports `@prisma/client`** or any other ORM — a CI gate fails the build if one appears — so a cross-cutting concern never hard-wires your schema.\n- **🏢 Multi-tenant from the first line** — Every operation is scoped by `tenantId`, store keys are `sha256(sha256(tenantId):sha256(recipient))`, and the audit interceptor resolves the trusted tenant from the request rather than the request body when you configure a resolver.\n- **🔒 Native crypto only** — Codes come from `crypto.randomInt` and are compared with `crypto.timingSafeEqual`. No `crypto-js`, no `otpauth`, no `uuid`, no `nanoid` — the most security-sensitive path carries no third-party supply-chain risk.\n- **⚛️ Atomic by construction** — The attempt counter and the resend cooldown are mutated **inside the storage** (Redis Lua / `SET NX EX`), never by a service-side read-then-write. A `get` + `update` pair races, and the race is exactly how a max-attempts ceiling gets bypassed under concurrent requests.\n- **🪶 Pay for what you use** — `\"dependencies\": {}`. NestJS, your email SDK, your Redis client, and React are all peer dependencies, and only the channels you configure are registered in the container.\n\n```\npnpm add @bymax-one/nest-notification\n```\n\n---\n\n## 🔥 Features\n\n### 📧 Email\n\n- ✅ **Pluggable transport** — `IEmailProvider` with a bundled `ResendEmailProvider`, an `SmtpEmailProvider` that speaks the protocol every relay and mail-capture server understands, and a `NoOpEmailProvider` for dev/test that logs subject and recipient only, never the body\n- ✅ **Template rendering** — `IEmailTemplateRenderer` with a bundled `{{var}}` renderer that **HTML-escapes** interpolated values in the HTML body\n- ✅ **Canonical template names** — `CANONICAL_EMAIL_TEMPLATES` so providers and templates agree on the wire\n- ✅ **Attachment guard** — a configurable total size ceiling (10 MiB by default) rejected before the provider is called\n\n### 🔢 OTP\n\n- ✅ **CSPRNG codes** — `numeric` / `alpha` / `alphanumeric`, built character by character from `crypto.randomInt` so every position is uniform\n- ✅ **TTL + max attempts** — with the counter spent atomically inside the storage\n- ✅ **Atomic resend cooldown** — acquired with `SET NX EX` and released only on delivery failure, so two concurrent resends cannot both win\n- ✅ **Constant-time verification** — `crypto.timingSafeEqual`, never `===`\n- ✅ **Per-purpose overrides** — length, code type, TTL, attempts, and cooldown tuned per purpose (a password reset is not an email verification)\n- ✅ **Optional email delivery** — hand the code to the email channel, or take it and deliver it yourself\n\n### 🏢 Multi-Tenant & Audit\n\n- ✅ **SHA-256 storage keys** — `sha256(sha256(tenantId):sha256(recipient))`: no recipient PII in a key, and the encoding adds no ambiguity of its own, so two pairs never share one by construction\n- ✅ **`tenantIdResolver`** — the audited tenant comes from a trusted source (a JWT claim, a subdomain, a gateway-checked header), not the payload\n- ✅ **Opt-in audit log** — a fire-and-forget `INotificationLogRepository` plus a `NotificationAuditInterceptor`; audit failures never break delivery\n- ✅ **This library never logs a code** — not to a logger, not to an audit entry, not into an error message it authors, enforced by a regression test. Text a **provider** authors is a separate problem with a documented ceiling: an error quoting the body in another encoding survives every guard, so control whether provider error text reaches your logs at all\n\n### 🧩 Developer Experience\n\n- ✅ **Three subpaths** — server, zero-dependency shared types, and React hooks\n- ✅ **Stable error catalog** — 23 namespaced codes shared byte-for-byte between server and frontend, so you localize on the `code`\n- ✅ **Dual-format output** — ESM + CJS + declarations for both, verified against the packed tarball on every PR\n- ✅ **Typed end to end** — TypeScript `strict` with `exactOptionalPropertyTypes` and `noUncheckedIndexedAccess`; zero `any`\n\n---\n\n## 📦 Subpath Exports\n\nOne package, four entry points — import only what your app needs:\n\n| Subpath     | Import                                 | Purpose                                                |               Peer deps                |\n| ----------- | -------------------------------------- | ------------------------------------------------------ | :------------------------------------: |\n| **Server**  | `@bymax-one/nest-notification`         | NestJS module, services, providers, errors, tokens     | NestJS 11 (+ your provider/store SDKs) |\n| **Shared**  | `@bymax-one/nest-notification/shared`  | Types + constants (error codes, TTLs)                  |                  None                  |\n| **React**   | `@bymax-one/nest-notification/react`   | `useOtpInput` + `useOtpCountdown` (UX/state only)      |                React 19                |\n| **Testing** | `@bymax-one/nest-notification/testing` | Executable `IOtpStorage` contract for your own storage |                  None                  |\n\n```\nshared (zero deps)\n  ↗            ↖\nserver        react\n```\n\n`shared` is independent, `react` depends only on `react`, and `server` is independent — importing one never drags in another's peers. Every subpath ships ESM (`.mjs`), CJS (`.cjs`), and declarations for both formats (`.d.ts` + `.d.cts`).\n\n---\n\n> [!TIP]\n> Prefer to learn from a working app? See the [nest-notification-example](https://github.com/bymaxone/nest-notification-example) — a full NestJS project wired with this library.\n\n## 🚀 Quick Start\n\n### 1. Install\n\n```bash\n# Using pnpm (recommended)\npnpm add @bymax-one/nest-notification\n\n# Using npm\nnpm install @bymax-one/nest-notification\n\n# Using yarn\nyarn add @bymax-one/nest-notification\n```\n\n> [!IMPORTANT]\n> You must also install the **peer dependencies** for the subpaths and channels you use:\n\n```bash\n# Server subpath (required)\npnpm add @nestjs/common @nestjs/core reflect-metadata rxjs\n\n# Production email + OTP over Redis (optional — pick your own provider/store)\npnpm add resend ioredis\n\n# …or SMTP instead of Resend (any relay, plus Mailpit/MailHog in tests)\npnpm add nodemailer ioredis\n\n# React subpath (optional)\npnpm add react\n```\n\n> [!NOTE]\n> Requires Node.js **24+** and NestJS **11**. Every provider and storage SDK is an _optional_ peer dependency: install only the ones your configuration actually names.\n\n### 2. Development — `NoOpEmailProvider` + `InMemoryOtpStorage`\n\nNo external services. Emails are logged (subject and recipient only, never the body), and OTP state lives in process memory. Ideal for local dev and tests.\n\n```typescript\nimport { Module } from '@nestjs/common'\nimport {\n  BymaxNotificationModule,\n  NoOpEmailProvider,\n  InMemoryOtpStorage\n} from '@bymax-one/nest-notification'\n\n@Module({\n  imports: [\n    BymaxNotificationModule.forRoot({\n      email: {\n        provider: new NoOpEmailProvider(),\n        defaultFrom: 'no-reply@dev.local'\n      },\n      otp: {\n        storage: new InMemoryOtpStorage(),\n        defaultLength: 6,\n        defaultTtlSeconds: 600,\n        defaultMaxAttempts: 5,\n        resendCooldownSeconds: 60\n      }\n    })\n  ]\n})\nexport class AppModule {}\n```\n\nInject `OtpService` anywhere and the two-step flow is complete:\n\n```typescript\nimport { Injectable } from '@nestjs/common'\nimport { OtpService } from '@bymax-one/nest-notification'\n\n@Injectable()\nexport class VerificationService {\n  constructor(private readonly otp: OtpService) {}\n\n  /** Generate + deliver an email-verification OTP. */\n  async start(tenantId: string, email: string): Promise<{ expiresAt: number }> {\n    const { expiresAt } = await this.otp.generate({\n      tenantId,\n      recipient: email,\n      purpose: 'email_verification',\n      deliverVia: 'email'\n    })\n    return { expiresAt }\n  }\n\n  /** Verify a submitted code. */\n  async confirm(tenantId: string, email: string, code: string): Promise<boolean> {\n    const result = await this.otp.verify({\n      tenantId,\n      recipient: email,\n      purpose: 'email_verification',\n      code\n    })\n    return result.valid\n  }\n}\n```\n\n> [!NOTE]\n> `generate()` returns only `expiresAt` and `cooldownSeconds` — never the code. Resolve the `tenantId` in your controller from a trusted source and pass it down; the service takes it as an explicit argument, so nothing is silently overridden.\n\n### 3. Production — Resend + Redis\n\nReal email via [Resend](https://resend.com) and OTP state in Redis (keys are SHA-256 hashed). Wire your `ioredis` client however your app already does.\n\n```typescript\nimport { Module } from '@nestjs/common'\nimport Redis from 'ioredis'\nimport {\n  BymaxNotificationModule,\n  ResendEmailProvider,\n  RedisOtpStorage,\n  DefaultTemplateRenderer\n} from '@bymax-one/nest-notification'\n\nconst redis = new Redis(process.env.REDIS_URL!)\n\n@Module({\n  imports: [\n    BymaxNotificationModule.forRoot({\n      global: {\n        redisNamespace: 'notification',\n        defaultLocale: 'en',\n        // Trust the tenant from a gateway-verified header, not the request body.\n        tenantIdResolver: (req) => String(req.headers['x-tenant-id'] ?? 'default')\n      },\n      email: {\n        provider: new ResendEmailProvider({ apiKey: process.env.RESEND_API_KEY! }),\n        defaultFrom: 'no-reply@acme.com',\n        defaultFromName: 'Acme',\n        templateRenderer: new DefaultTemplateRenderer({\n          templates: {\n            'otp_code::en': {\n              subject: 'Your Acme verification code',\n              html: '<p>Your code is <strong>{{code}}</strong>. It expires in {{expiresInMinutes}} minutes.</p>',\n              text: 'Your code is {{code}}. It expires in {{expiresInMinutes}} minutes.'\n            }\n          }\n        })\n      },\n      otp: {\n        storage: new RedisOtpStorage({ redisClient: redis }),\n        defaultLength: 6,\n        defaultCodeType: 'numeric',\n        defaultTtlSeconds: 600,\n        defaultMaxAttempts: 5,\n        resendCooldownSeconds: 60,\n        perPurpose: {\n          password_reset: {\n            length: 8,\n            codeType: 'alphanumeric',\n            ttlSeconds: 900,\n            maxAttempts: 5,\n            resendCooldownSeconds: 60\n          }\n        }\n      }\n    })\n  ]\n})\nexport class AppModule {}\n```\n\nFor async configuration (e.g. reading secrets from `ConfigService`), use `forRootAsync`:\n\n```typescript\nimport { ConfigModule, ConfigService } from '@nestjs/config'\n\nBymaxNotificationModule.forRootAsync({\n  imports: [ConfigModule],\n  inject: [ConfigService],\n  useFactory: (config: ConfigService) => ({\n    email: {\n      provider: new ResendEmailProvider({ apiKey: config.getOrThrow('RESEND_API_KEY') }),\n      defaultFrom: config.getOrThrow('MAIL_FROM')\n    },\n    otp: { storage: new RedisOtpStorage({ redisClient: redis }) }\n  })\n})\n```\n\n> [!IMPORTANT]\n> `forRootAsync` supports the `useFactory` pattern. `useClass` / `useExisting` are not implemented and are rejected at startup.\n\n### 4. SMTP — any relay, and end-to-end tests\n\n`SmtpEmailProvider` speaks the protocol rather than one vendor's HTTP API, so the same adapter drives a corporate relay, Postfix, SES-via-SMTP, and the mail-capture servers (Mailpit, MailHog) that make email flows testable end to end. Requires the optional `nodemailer` peer dependency.\n\n```typescript\nimport { BymaxNotificationModule, SmtpEmailProvider } from '@bymax-one/nest-notification'\n\n// Local capture server — no credentials, no TLS. Read the captured mail at :8025.\nconst mailpit = new SmtpEmailProvider({ host: 'localhost', port: 1025, secure: false })\n\n// Authenticated production relay. STARTTLS is already mandatory by default here.\nconst relay = new SmtpEmailProvider({\n  host: process.env.SMTP_HOST,\n  port: 587,\n  credentials: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS }\n})\n\nBymaxNotificationModule.forRoot({\n  email: {\n    provider: process.env.NODE_ENV === 'production' ? relay : mailpit,\n    defaultFrom: 'no-reply@acme.com'\n  }\n})\n```\n\n| Option                                                    | Default               | Notes                                                                        |\n| --------------------------------------------------------- | --------------------- | ---------------------------------------------------------------------------- |\n| `host`                                                    | —                     | Required. Without it `isConfigured()` is `false` and `send()` throws.        |\n| `port`                                                    | `587`                 | Submission port.                                                             |\n| `secure`                                                  | `true` iff port 465   | Implicit TLS from the first byte.                                            |\n| `requireTls`                                              | `true` off-loopback   | Makes the STARTTLS upgrade mandatory. See the TLS note below.                |\n| `credentials`                                             | — (open relay)        | `{ user, pass }`. Supplying it declares the deployment logs in.              |\n| `tls`                                                     | —                     | `rejectUnauthorized` / `servername` / `ca`, for a relay behind a private CA. |\n| `connectionTimeout` / `greetingTimeout` / `socketTimeout` | `10s` / `10s` / `20s` | Bounded on purpose — Nodemailer's own defaults run into minutes.             |\n\n> [!IMPORTANT]\n> `isConfigured()` answers on the real configuration, not on \"a transport object exists\". Omitting `credentials` marks the relay as open and is fine; supplying them half-loaded — an `SMTP_PASS` that never made it into the environment — reports **not configured** and fails the send closed, rather than silently attempting an anonymous delivery a permissive relay might accept.\n\n> [!WARNING]\n> **STARTTLS is mandatory by default for any non-loopback host.** On a connection that does not start out encrypted, whether TLS happens at all is decided by the _plaintext_ EHLO banner: an attacker with network position strips the `250-STARTTLS` line, the transport never upgrades, and the credentials and the message body — which carries the OTP code — cross the network in the clear. So `requireTls` defaults to `true` unless the host is `localhost` / `127.0.0.1` / `::1` or `secure` is already on. If your relay genuinely cannot upgrade, opt out explicitly with `requireTls: false`; a Mailpit or MailHog container reached by its compose service name rather than over loopback is the common case.\n\nThree further behaviours worth knowing:\n\n- The returned `messageId` is the RFC-5322 `Message-ID` with its angle brackets, so an audit entry correlates with the header the recipient received.\n- `tags` are not forwarded, because SMTP has no tag facility — they still reach the audit log through `EmailService`.\n- A line break in `from`, `replyTo`, a recipient, or a custom header name/value is **rejected before the send**. Nodemailer already neutralizes those, so this is defence in depth — but header injection is the one place where trusting a peer dependency's current behaviour would be the whole security boundary. The subject is deliberately exempt: a stray trailing newline from a template is plausible there, and Nodemailer folds it away.\n\n### 5. Bring Your Own Provider\n\nEvery external boundary is an interface — implement it and pass the instance (or class) to `forRoot`. The bundled `ResendEmailProvider` and `RedisOtpStorage` are reference implementations, not requirements.\n\n```typescript\nimport type {\n  IEmailProvider,\n  EmailSendOptions,\n  EmailSendResult\n} from '@bymax-one/nest-notification'\n\nexport class SendGridEmailProvider implements IEmailProvider {\n  readonly name = 'sendgrid'\n\n  isConfigured(): boolean {\n    return Boolean(this.apiKey)\n  }\n\n  async send(options: EmailSendOptions): Promise<EmailSendResult> {\n    // Call SendGrid; throw on failure — EmailService maps it to a NotificationException.\n    return { messageId: '…' }\n  }\n}\n```\n\n```typescript\nimport type { IOtpStorage } from '@bymax-one/nest-notification'\n// Implement set / get / consumeAttempt / update / delete /\n// tryAcquireCooldown / getCooldown / clearCooldown.\n```\n\n> [!IMPORTANT]\n> `consumeAttempt` and `tryAcquireCooldown` **must be atomic** — one indivisible read-modify-write (a Redis Lua script, or a synchronous operation for an in-memory store). Implemented as a `get` followed by an `update`, two concurrent requests both read the same attempt count and the `maxAttempts` ceiling stops being a ceiling.\n\nAdapter examples for several providers and stores live under [`docs/templates/`](./docs/templates/) and [`docs/schemas/`](./docs/schemas/):\n\n| Email provider          | Adapter                                   |\n| ----------------------- | ----------------------------------------- |\n| Resend                  | bundled — `ResendEmailProvider`           |\n| SMTP (any relay)        | bundled — `SmtpEmailProvider`             |\n| Mailpit / MailHog       | bundled — `SmtpEmailProvider`             |\n| AWS SES (SMTP endpoint) | bundled — `SmtpEmailProvider`             |\n| SendGrid                | implement `IEmailProvider` (sketch above) |\n| AWS SES (HTTP API)      | implement `IEmailProvider`                |\n| Mailgun                 | implement `IEmailProvider`                |\n\n| Template engine | Adapter                                                                                              |\n| --------------- | ---------------------------------------------------------------------------------------------------- |\n| Default         | bundled — `DefaultTemplateRenderer` (`{{var}}` interpolation, HTML-escaped)                          |\n| Handlebars      | [`docs/templates/handlebars-renderer.example.md`](./docs/templates/handlebars-renderer.example.md)   |\n| MJML            | [`docs/templates/mjml-renderer.example.md`](./docs/templates/mjml-renderer.example.md)               |\n| React Email     | [`docs/templates/react-email-renderer.example.md`](./docs/templates/react-email-renderer.example.md) |\n\n### 6. Audit Log\n\nThe library never imports Prisma. You implement `INotificationLogRepository` against your own client; the module calls it fire-and-forget, so the audit sink can never crash the notification flow. A copy-pasteable Prisma schema fragment lives in [`docs/schemas/notification-log.prisma`](./docs/schemas/notification-log.prisma) and a full repository in [`docs/schemas/prisma-repository.example.md`](./docs/schemas/prisma-repository.example.md).\n\n```typescript\nimport { Injectable } from '@nestjs/common'\nimport type { INotificationLogRepository, NotificationLogEntry } from '@bymax-one/nest-notification'\nimport { PrismaClient } from '@prisma/client' // your app's dependency, not the library's\n\n@Injectable()\nexport class PrismaNotificationLogRepository implements INotificationLogRepository {\n  readonly name = 'prisma'\n\n  constructor(private readonly prisma: PrismaClient) {}\n\n  async create(entry: NotificationLogEntry): Promise<void> {\n    await this.prisma.notificationLog.create({\n      data: {\n        timestamp: new Date(entry.timestamp),\n        tenantId: entry.tenantId,\n        channel: entry.channel,\n        verb: entry.verb,\n        recipient: entry.recipient, // already masked if you set `audit.maskRecipient`\n        purpose: entry.purpose ?? null,\n        providerName: entry.providerName,\n        messageId: entry.messageId ?? null,\n        errorMessage: entry.errorMessage ?? null,\n        userId: entry.userId ?? null\n      }\n    })\n  }\n}\n```\n\n```typescript\nBymaxNotificationModule.forRoot({\n  email: { provider: new ResendEmailProvider({ apiKey }), defaultFrom: 'no-reply@acme.com' },\n  otp: { storage: new RedisOtpStorage({ redisClient: redis }) },\n  audit: {\n    repository: new PrismaNotificationLogRepository(prisma),\n    swallowErrors: true, // default — audit failures never break delivery\n    maskRecipient: (r) => r.replace(/^(.).*(@.*)$/, '$1***$2') // jane@acme.com -> j***@acme.com\n  }\n})\n```\n\nTo capture HTTP-level `sent` / `failed` entries automatically, apply the interceptor:\n\n```typescript\nimport { NotificationAuditInterceptor } from '@bymax-one/nest-notification'\n// @UseInterceptors(NotificationAuditInterceptor) on a controller/handler, or wire it globally.\n```\n\n### 7. Frontend Integration (React)\n\nThe `./react` subpath is browser-only state and UX — it drives the OTP-input box and a countdown. **Verifying the code is your app's job**: the hooks carry no HTTP client and no Node builtins, so nothing about your API shape is assumed.\n\n```tsx\nimport { useOtpInput, useOtpCountdown } from '@bymax-one/nest-notification/react'\n\nfunction OtpForm({ expiresAt }: { expiresAt: number }) {\n  const { values, onChange, onKeyDown, onPaste, refs, isComplete } = useOtpInput({\n    length: 6,\n    type: 'numeric',\n    onComplete: (full) => void submitToBackend(full)\n  })\n  const { formatted, expired } = useOtpCountdown({ expiresAt })\n\n  return (\n    <form>\n      {values.map((v, i) => (\n        <input\n          key={i}\n          ref={refs[i]}\n          value={v}\n          onChange={onChange(i)}\n          onKeyDown={onKeyDown(i)}\n          onPaste={i === 0 ? onPaste : undefined}\n          inputMode=\"numeric\"\n          maxLength={1}\n        />\n      ))}\n      <p>{expired ? 'Code expired' : `Expires in ${formatted}`}</p>\n      {/* Never render the code itself — `onComplete` hands it to your submit path.\n          Echoing it into a label leaks it to screen readers, screenshots and\n          session-replay tools, which is exactly what the server side avoids. */}\n      <button disabled={!isComplete}>Verify</button>\n    </form>\n  )\n}\n```\n\nPair it with the `./shared` subpath to branch on server errors without duplicating string literals:\n\n```typescript\nimport { NOTIFICATION_ERROR_CODES } from '@bymax-one/nest-notification/shared'\n\nif (error.code === NOTIFICATION_ERROR_CODES.OTP_MAX_ATTEMPTS_EXCEEDED) {\n  // Show your own localized copy — the library never ships translations.\n}\n```\n\n---\n\n## ⚙️ Configuration\n\nConfigure via `forRoot(options)` or `forRootAsync({ useFactory })`. **At least one channel must be configured**, and configuring a channel the library does not implement throws at startup rather than failing on the first send. The full reference is in [`docs/technical_specification.md` §4](./docs/technical_specification.md); the most-used options:\n\n| Section  | Option                  | Default          | Notes                                            |\n| -------- | ----------------------- | ---------------- | ------------------------------------------------ |\n| `global` | `redisNamespace`        | `'notification'` | Prefix for store keys.                           |\n| `global` | `defaultLocale`         | `'en'`           | Template locale fallback.                        |\n| `global` | `tenantIdResolver`      | —                | `(req) => tenantId`; the audit source of truth.  |\n| `email`  | `provider`              | — (required)     | Instance or class implementing `IEmailProvider`. |\n| `email`  | `defaultFrom`           | — (required)     | Must look like an email address.                 |\n| `email`  | `templateRenderer`      | default renderer | Any `IEmailTemplateRenderer`.                    |\n| `email`  | `maxAttachmentBytes`    | `10485760`       | 10 MiB attachment guard.                         |\n| `otp`    | `storage`               | — (required)     | Instance or class implementing `IOtpStorage`.    |\n| `otp`    | `defaultLength`         | `6`              | 1–32.                                            |\n| `otp`    | `defaultCodeType`       | `'numeric'`      | `numeric` \\| `alpha` \\| `alphanumeric`.          |\n| `otp`    | `defaultTtlSeconds`     | `600`            | Code lifetime.                                   |\n| `otp`    | `defaultMaxAttempts`    | `5`              | Verify attempts before lock-out.                 |\n| `otp`    | `resendCooldownSeconds` | `60`             | Anti-resend window (atomic `SET NX EX`).         |\n| `otp`    | `perPurpose`            | `{}`             | Per-purpose overrides of the above.              |\n| `audit`  | `repository`            | — (required)     | Any `INotificationLogRepository`.                |\n| `audit`  | `swallowErrors`         | `true`           | Keep audit failures out of the delivery path.    |\n| `audit`  | `maskRecipient`         | identity         | Minimize recipient PII before persisting.        |\n\n---\n\n## 🎨 Templates\n\nEmail rendering goes through `IEmailTemplateRenderer`. The bundled `DefaultTemplateRenderer` does `{{var}}` interpolation with **automatic HTML escaping in the HTML body** — subject and plaintext are left raw, since neither is an HTML context. That closes a stored-XSS vector: a display name containing markup renders as text, not as an element, in the recipient's mail client.\n\nRegister named templates per `name::locale`; the renderer falls back to the `en` locale when a locale-specific template is missing.\n\n`CANONICAL_EMAIL_TEMPLATES` exports stable names for common transactional emails (`otp_code`, `otp_password_reset`, `otp_resent`, `welcome`, `password_reset_success`, `trial_expiring`, …) so providers and templates agree on the wire. For richer output, plug in Handlebars, MJML, or React Email — examples under [`docs/templates/`](./docs/templates/).\n\n---\n\n## 🏗️ Architecture\n\nThe package runs **inside** your NestJS application as a dynamic module — not as a separate service:\n\n```\n┌──────────────────────────────────────────────────┐\n│            Your NestJS Application                │\n│                                                  │\n│  ┌────────────────────────────────────────────┐  │\n│  │      @bymax-one/nest-notification          │  │\n│  │                                            │  │\n│  │  NotificationService                       │  │\n│  │      ├── EmailService                      │  │\n│  │      └── OtpService ←→ Crypto (node:crypto)│  │\n│  │  NotificationAuditInterceptor              │  │\n│  └───┬────────┬─────────┬──────────┬──────────┘  │\n│      │        │         │          │             │\n│  ┌───▼───┐ ┌──▼─────┐ ┌─▼───────┐ ┌▼──────────┐  │\n│  │IEmail │ │IOtp    │ │IEmail   │ │INotifi…   │  │\n│  │Provi… │ │Storage │ │Template │ │LogRepo    │  │\n│  │(yours)│ │(yours) │ │Renderer │ │(yours)    │  │\n│  └───────┘ └────────┘ └─────────┘ └───────────┘  │\n└──────────────────────────────────────────────────┘\n```\n\n`NotificationService` is the façade: it dispatches to the channel services and exposes `getEnabledChannels()`, `getEmail()`, and `getOtp()` for direct access. Only the channels present in your configuration are registered, so an unconfigured channel is a startup error, not a runtime surprise.\n\n### Design Principles\n\n| Principle                     | Description                                                                                                                                                       |\n| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **🔌 Interface-Driven**       | Define contracts, inject implementations — works with any email provider, any store, any ORM (or none)                                                            |\n| **🔒 Secure by Default**      | CSPRNG codes, constant-time comparison, hashed keys, and a never-log-codes invariant — all on by default, nothing to opt into                                     |\n| **⚛️ Atomic Where It Counts** | The attempt counter and the resend cooldown are mutated by the storage in one indivisible step; a service-side read-then-write is a bug, not a style choice       |\n| **🪶 Zero Runtime Deps**      | `\"dependencies\": {}` — adds nothing of its own; crypto is native `node:crypto`. Required peers (NestJS…) come from your app, optional ones only when you use them |\n| **🌳 Tree-Shakeable**         | `sideEffects: false`, subpath exports, ESM + CJS dual output with declarations for both                                                                           |\n| **⚡ Conditional Loading**    | Unconfigured channels don't register — no wasted memory or startup time, and no half-wired channel that fails on the first send                                   |\n\n---\n\n## 🔐 Security Model\n\nOTP codes are bearer secrets with a short life and a wide blast radius: whoever holds one is the account. The model below is built around keeping them unreadable, unguessable, and unresendable.\n\n### SHA-256 storage keys\n\nOTP entries and resend cooldowns are stored under a key derived from `sha256(sha256(tenantId):sha256(recipient))` — never the plaintext recipient or tenant id:\n\n```\nnotification:otp:email_verification:7f3d8c91…  (64 hex chars)\n```\n\n- **Privacy.** An operator with `KEYS notification:otp:*` access to Redis — or anyone holding a leaked backup — cannot enumerate which addresses have a pending OTP. The recipient never appears in a key.\n- **Isolation.** Each component is hashed to a fixed length before the two are joined, so distinct `(tenantId, recipient)` pairs cannot produce the same key. This is a property of the encoding rather than of the digest — joining the raw values around a delimiter would map `('acme:bob', 'x')` and `('acme', 'bob:x')` onto one key while SHA-256's preimage and collision resistance held perfectly. Both components must be non-empty; an empty one is refused rather than hashed, because it would place every caller that omits it into a single shared scope.\n\nThe trade-off — opaque keys you cannot read back to a recipient — is intentional: keys are an index, not a data source. The recipient lives only inside the TTL-bound value and, optionally masked, in the audit log.\n\n### `tenantIdResolver` prevents tenant spoofing\n\nWhen you expose notification endpoints over HTTP, a caller could forge another tenant's id in the request body — `POST { \"tenantId\": \"tenant_a\", … }` sent from `tenant_b` to verify someone else's OTP. Configure a `tenantIdResolver` that reads the tenant from a **trusted** source: a verified JWT claim, a subdomain, or a gateway-checked header.\n\n```typescript\nimport type { NotificationRequest } from '@bymax-one/nest-notification'\n\nBymaxNotificationModule.forRoot({\n  global: {\n    // Subdomain-based: `acme.app.com` -> `acme`.\n    tenantIdResolver: (req: NotificationRequest) => req.hostname?.split('.')[0] ?? 'default'\n  }\n  // …channels\n})\n\n// JWT-claim based (the request is augmented by your auth middleware):\nconst tenantIdResolver = (req: NotificationRequest): string =>\n  String(req.headers['x-tenant-id'] ?? 'default')\n```\n\n`NotificationRequest` is a minimal, framework-agnostic request shape declared by the library itself, so a public signature never drags in a framework's types. When a resolver is set, the `NotificationAuditInterceptor` uses it as the **source of truth** for the audited tenant id — any `tenantId` in the payload becomes a suggestion the resolver overrides.\n\n> [!NOTE]\n> The resolver governs what the audit interceptor trusts. Service methods still take an explicit `tenantId`: resolve the tenant in your controller and pass it down — there is no hidden override of a method argument.\n\n### The library never writes a code anywhere readable\n\nEverything below is about text **this library authors**. Text a provider authors is a separate\nproblem with a documented ceiling — see [Errors](#errors) — and\nthe two must not be read as one guarantee.\n\nThe library **never** writes a code to a sink that can be read back:\n\n- Not to the audit log — an entry is `{ verb, tenantId, recipient, purpose, providerName, … }`, never `code`.\n- Not to a console or logger line.\n- Not inside an `errorMessage`, which carries the message only — never a stack trace.\n- Not inside a rethrown delivery error: the renderer and the provider both receive the code (template `data`, rendered body), so on a failed delivery every **literal** occurrence of the code — the form it was issued in, which is the form this library's own text carries — is scrubbed to `[redacted]` across the outgoing error chain — message **and** stack at every link, cycle-safe with no depth limit — before the audit write and the rethrow, and a non-`Error` rejection is flattened to a redacted string. The email channel's own `failed` audit entry goes further than redaction: OTP delivery sets `publishProviderText: false`, so that entry records the fixed label `[provider text withheld]` plus the SMTP reply codes a fixed grammar can express — never the provider's own words. It also declares the code via `auditRedactValues`, which stays as precision for the paths that do publish text. Default V8 stack frames carry only function names and source locations, never argument values; the header line (`Error: <message>`) is where a code could ride a stack, and the scrub covers it.\n\nWithin this library, codes exist only inside the OTP store, under a TTL, and in process memory for the duration of the request — a provider that quotes the body back is outside that boundary, and its ceiling is documented under [Errors](#errors). `audit.maskRecipient` minimizes the recipient before it is persisted (`jane@acme.com` → `j***@acme.com`). A regression test asserts the invariant directly rather than trusting review: `JSON.stringify(auditEntry).includes(code) === false`.\n\n### Attempt ceilings and cooldowns are atomic\n\n`consumeAttempt` is the only writer of the attempt counter, and `tryAcquireCooldown` (`SET NX EX`) is the only acquirer of the resend window — both inside the storage, in one indivisible step. A service-side `get` followed by an `update` looks equivalent and passes every sequential test, but two concurrent verifies read the same count and each writes back the same increment: five attempts become unbounded. The same shape lets two concurrent resends both pass the cooldown check.\n\n### Security Checklist\n\nWhen integrating `@bymax-one/nest-notification` in production, verify each of the following:\n\n- `tenantIdResolver` reads from a **verified** source (JWT claim, subdomain, gateway-signed header) — never the request body\n- `audit.maskRecipient` is configured if audit rows are retained beyond the operational minimum\n- A custom `IOtpStorage` implements `consumeAttempt` and `tryAcquireCooldown` atomically\n- The recipient passed to the service is canonical (`email.trim().toLowerCase()`) — the library does not normalize it, so `A@x.com` and `a@x.com` are distinct keys\n- `resendCooldownSeconds` and `defaultMaxAttempts` are tuned per purpose, not left at the defaults for high-value flows\n- Your own controller rate-limits the OTP endpoints — the library bounds attempts per code, not requests per IP\n\n---\n\n## 🛡️ Security Table\n\n| Layer              | Implementation                                                                                                                                                                                  |\n| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Code Generation    | `crypto.randomInt` per character over the configured alphabet — uniform at every position, no modulo bias                                                                                       |\n| Code Comparison    | `crypto.timingSafeEqual` (constant-time), never `===`                                                                                                                                           |\n| Storage Keys       | `sha256(sha256(tenantId):sha256(recipient))` — no recipient PII; the encoding adds no ambiguity, so no two pairs share a key by construction                                                    |\n| Attempt Ceiling    | Counter spent atomically inside the storage (Redis Lua) — never a service-side read-then-write                                                                                                  |\n| Resend Cooldown    | `SET NX EX` acquire, released only on delivery failure — two concurrent resends cannot both win                                                                                                 |\n| Code Lifetime      | TTL-bound in the store; expiry and absence are reported identically so neither leaks the other                                                                                                  |\n| Code Exposure      | Never logged, audited, or placed in an error message or stack trace **by this library** — asserted by a regression test. Provider-authored text has a documented ceiling: see [Errors](#errors) |\n| Recipient PII      | Absent from keys; optionally masked before it reaches the audit sink                                                                                                                            |\n| Provider Secrets   | The Resend API key, the SMTP password and the Redis client live in private fields; serializing a provider omits them, and a failing SMTP session has its password scrubbed from the error       |\n| Tenant Isolation   | `tenantId` scopes every operation and is resolved from a trusted source, not the payload                                                                                                        |\n| Template Injection | HTML body escaped on interpolation by the bundled renderer — closes stored XSS through a display name                                                                                           |\n| Attachment DoS     | Total attachment size rejected against a budget before the provider is called                                                                                                                   |\n| Audit Failures     | Fire-and-forget with `swallowErrors` — an audit outage never becomes a delivery outage                                                                                                          |\n| Supply Chain       | `\"dependencies\": {}`; published with npm provenance (OIDC), CodeQL and OpenSSF Scorecard on every push                                                                                          |\n\n> [!IMPORTANT]\n> This package uses **zero external cryptographic dependencies**. All operations use Node.js native `node:crypto`, eliminating supply chain attack vectors for critical security code.\n\n---\n\n## 🧱 Tech Stack\n\n<p>\n  <img src=\"https://img.shields.io/badge/NestJS-11-E0234E?style=flat-square&logo=nestjs&logoColor=white\" alt=\"NestJS\" />\n  <img src=\"https://img.shields.io/badge/TypeScript-strict-3178C6?style=flat-square&logo=typescript&logoColor=white\" alt=\"TypeScript\" />\n  <img src=\"https://img.shields.io/badge/React-19-61DAFB?style=flat-square&logo=react&logoColor=black\" alt=\"React\" />\n  <img src=\"https://img.shields.io/badge/Node.js-24%2B-339933?style=flat-square&logo=node.js&logoColor=white\" alt=\"Node.js\" />\n  <img src=\"https://img.shields.io/badge/Redis-7%2B-DC382D?style=flat-square&logo=redis&logoColor=white\" alt=\"Redis\" />\n  <img src=\"https://img.shields.io/badge/Resend-email-000000?style=flat-square&logo=resend&logoColor=white\" alt=\"Resend\" />\n  <img src=\"https://img.shields.io/badge/Jest-30-C21325?style=flat-square&logo=jest&logoColor=white\" alt=\"Jest\" />\n</p>\n\n---\n\n## 🧪 Testing & Quality\n\nA one-time password is a credential, so the suite is held to a bar beyond \"it runs\" — every behavior is pinned so that a regression **fails a test**.\n\n- ✅ **100% line coverage** — statements, branches, functions, and lines, enforced per file as a release gate across unit + e2e\n- ✅ **100% mutation score** — verified with [Stryker](https://stryker-mutator.io/): every viable seeded fault killed, with **no survivors**, against a `break` threshold of 100. The equivalents that no test can kill carry their reason on the line they apply to, so the number is an accounting rather than a target\n- ✅ **680 tests** — unit and end-to-end, spanning all four subpaths\n- ✅ **Invariants asserted, not assumed** — the never-log-codes rule is a test (`JSON.stringify(entry).includes(code) === false` on every audit entry, and the thrown exception is serialized recursively — message, stack, response body, every nested `cause` — asserting the code appears at no depth), not a review convention\n- ✅ **Published shape verified** — `attw` resolves every entrypoint against the packed tarball, and a dogfood smoke test installs the package into a scratch consumer before any tag is cut\n- ✅ **Every equivalent mutant documented** — the ones no test can kill carry an inline `// Stryker disable` with the reason, so the score is an accounting rather than a number\n\n| Gate           | Standard                                                               |\n| -------------- | ---------------------------------------------------------------------- |\n| Type safety    | TypeScript `strict`, zero `any`                                        |\n| Coverage       | **100%** line/branch/function/statement per file (`pnpm test:cov:all`) |\n| Mutation       | Stryker score **100%** (`break: 100`), no survivors                    |\n| Lint           | ESLint flat config + `eslint-plugin-security`, zero warnings           |\n| Bundle budgets | server < 30 KB · shared < 4 KB · react < 8 KB brotli (`pnpm size`)     |\n| Prisma-free    | `pnpm check:no-prisma` — the library never imports `@prisma/client`    |\n| Export map     | `pnpm check:exports` — `attw` against the packed tarball, ESM and CJS  |\n| Supply chain   | published with npm provenance (OIDC), CodeQL + OpenSSF Scorecard       |\n\n```bash\npnpm test          # unit suite\npnpm test:cov:all  # unit + e2e, 100% coverage gate\npnpm mutation      # Stryker mutation testing\n```\n\n> [!NOTE]\n> Line coverage proves a line _executed_ under test; mutation testing proves a test _would fail_ if that line were wrong. The full methodology and per-area breakdown are in [docs/mutation_testing_results.md](./docs/mutation_testing_results.md).\n\n---\n\n## 📖 API Reference\n\n### Module\n\n| Member                                   | Returns         | Purpose                                                      |\n| ---------------------------------------- | --------------- | ------------------------------------------------------------ |\n| `BymaxNotificationModule.forRoot()`      | `DynamicModule` | Synchronous registration from a literal options object       |\n| `BymaxNotificationModule.forRootAsync()` | `DynamicModule` | Async registration via `useFactory` (+ `imports` / `inject`) |\n\n### Services\n\n| Service               | Method                    | Returns                          | Purpose                                                               |\n| --------------------- | ------------------------- | -------------------------------- | --------------------------------------------------------------------- |\n| `OtpService`          | `generate()`              | `{ expiresAt, cooldownSeconds }` | Mint a code, store it, optionally deliver it — never returns the code |\n| `OtpService`          | `verify()`                | `OtpVerifyResult`                | Spend one attempt and compare in constant time                        |\n| `OtpService`          | `consume()`               | `void`                           | Invalidate a verified code so it cannot be replayed                   |\n| `OtpService`          | `resend()`                | `{ expiresAt, cooldownSeconds }` | Re-issue under the atomic cooldown                                    |\n| `OtpService`          | `getStatus()`             | `OtpStatusResult`                | Read-only view (existence, expiry, attempts) — never the code         |\n| `EmailService`        | `send()`                  | `{ messageId }`                  | Send a literal subject + body                                         |\n| `EmailService`        | `sendTemplate()`          | `{ messageId }`                  | Render a named template for a locale, then send                       |\n| `NotificationService` | `dispatch()`              | `DispatchResult`                 | Channel-agnostic façade over the configured channels                  |\n| `NotificationService` | `getEnabledChannels()`    | `NotificationChannel[]`          | Which channels this instance actually registered                      |\n| `NotificationService` | `getEmail()` / `getOtp()` | the channel service              | Direct access when you already know the channel                       |\n\n### Interceptor\n\n| Member                         | Purpose                                                                                          |\n| ------------------------------ | ------------------------------------------------------------------------------------------------ |\n| `NotificationAuditInterceptor` | Records `sent` / `failed` audit entries at the HTTP layer, tenant resolved by `tenantIdResolver` |\n\n### Errors\n\n| Member                           | Purpose                                                                    |\n| -------------------------------- | -------------------------------------------------------------------------- |\n| `NotificationException`          | `HttpException` carrying a stable `code`, a status, and optional `details` |\n| `NotificationExceptionOptions`   | Constructor options bag: `{ status?, message?, cause? }`                   |\n| `NOTIFICATION_ERROR_CODES`       | The 23 stable `notification.*` codes — branch on these, not on messages    |\n| `NOTIFICATION_ERROR_DEFINITIONS` | Server-side code → HTTP status + default English message                   |\n\nError codes are namespaced (`notification.otp_invalid_code`, `notification.otp_cooldown_active`, `notification.email_send_failed`, …) and never change once published. Default messages are English; localize on the `code`.\n\nFailures raised by the library carry the underlying error as the native `Error.cause` — a provider's `connect ECONNREFUSED` sits on `exception.cause`, where cause-walking log serializers (e.g. pino's `err`) print it alongside the stable code. The cause is stored as a **log-safe copy**: `name`, `message`, `stack`, and the nested `cause` chain survive (depth-bounded); every other property is dropped, because SDK errors routinely retain the request payload (an axios-style `config.data`) and for an OTP email that payload contains the code. The cause never enters the HTTP response body either: `details` stays reserved for the structured, client-safe context shown above. To attach a cause in your own code, pass the options bag as the third constructor argument — `new NotificationException('EMAIL_SEND_FAILED', { providerName }, { cause: error })`; the positional `(key, details, status, message)` form keeps working.\n\n**If you send a secret through `EmailService` yourself — declare it, and treat the provider's error text as untrusted anyway.** A relay or DLP filter that quotes the rejected content puts the rendered body (and any secret inside it) into the provider error, which rides the `cause` into your logs. Pass `auditRedactValues: [code]` on the send input: the value is scrubbed from the attached `cause`, from the failed-audit `errorMessage`, and — forwarded as `EmailSendOptions.redactValues` — from the provider's own error logging. As defense-in-depth for the undeclared case, any run of 16+ characters of the rendered body detected inside a provider error is redacted automatically.\n\n**Withholding provider text — `publishProviderText: false`.** For a message whose body carries a credential, set it on the send input (`EmailService.send` or `sendTemplate`); the built-in OTP delivery already does. The failure then carries **no byte the provider wrote**: no `cause`, and the audit entry's `errorMessage` is the fixed label `[provider text withheld]`.\n\nWhat it publishes instead is what a fixed grammar can express — the basic SMTP reply code and, when present, the RFC 3463 enhanced code — as structured fields. The two surfaces name them differently, so read the right keys: the audit entry carries `deliveryStatus` and `deliveryEnhancedStatus`, while the exception's `details` carries `status` and `enhanced`.\n\n```ts\nawait emailService.send({\n  tenantId,\n  to: user.email,\n  subject: 'Your code',\n  html: rendered,\n  publishProviderText: false // this body carries a credential\n})\n// On failure: NotificationException('notification.email_send_failed')\n//   details → { providerName: 'smtp', status: 550, enhanced: '5.7.1' }\n//   cause   → absent\n// Audit entry: { verb: 'failed', errorMessage: '[provider text withheld]',\n//                deliveryStatus: 550, deliveryEnhancedStatus: '5.7.1', … }\n```\n\nThree properties worth knowing before you rely on it. The published codes are **independent of the secret** — the same reply publishes `550 5.7.1` whether the code was `550571`, `123456` or absent — which is why they are safe to log while the prose is not. **Ambiguity resolves to silence**: a text carrying two different replies publishes neither, so a cause chain cannot have `424` and `242` read as one value. And a code that would carry a **declared** secret is dropped, since this library's invariant is that a code's characters never reach an audit entry.\n\nThe default is `true`, and deliberately so: for a receipt or a marketing send the provider's message is often the only diagnosis available. The flag is **per send, never global** — one setting cannot serve an OTP path and a marketing path in the same application.\n\n**Know the ceiling of both guards.** They remove the shapes they can predict, and a relay quotes what it _transmitted_, not what you handed it — a MIME body travels raw, quoted-printable or base64. A declared `123456` cannot match `MTIzNDU2`, and echo detection compares the error against the rendered body it holds, not against the encoding the relay chose; measured, an error quoting the base64 of the body survives both. No list of values closes an encoding gap. So control whether provider error text reaches your logs at all for secret-bearing sends — pass `publishProviderText: false` on the send input, which withholds every provider-authored byte and publishes only the SMTP reply codes a fixed grammar can express — and use declared values as precision on top of that rather than as the barrier. If you consume `@bymax-one/nest-auth` and bind its bundled `DefaultAuthEmailProvider`, that provider is the call site, so the declaration belongs there rather than in your application code; if you implement `nest-auth`'s email port yourself, your adapter is the call site and the declaration is yours to make.\n\n### Reference adapters\n\n| Export                          | Implements                   | Use for                                      |\n| ------------------------------- | ---------------------------- | -------------------------------------------- |\n| `ResendEmailProvider`           | `IEmailProvider`             | Production email via Resend                  |\n| `SmtpEmailProvider`             | `IEmailProvider`             | Any SMTP relay, and Mailpit/MailHog in tests |\n| `NoOpEmailProvider`             | `IEmailProvider`             | Dev/test — logs subject and recipient only   |\n| `RedisOtpStorage`               | `IOtpStorage`                | Production OTP state (atomic via Lua)        |\n| `InMemoryOtpStorage`            | `IOtpStorage`                | Dev/test — single-process only               |\n| `DefaultTemplateRenderer`       | `IEmailTemplateRenderer`     | `{{var}}` interpolation with HTML escaping   |\n| `NoOpNotificationLogRepository` | `INotificationLogRepository` | Audit configured but intentionally discarded |\n\n### Utilities\n\n| Export                  | Purpose                                                                 |\n| ----------------------- | ----------------------------------------------------------------------- |\n| `hashTenantRecipient()` | The key derivation, exposed so a custom storage produces identical keys |\n| `generateOtpCode()`     | The CSPRNG generator, for a storage or provider that mints its own      |\n| `safeCompare()`         | Constant-time string comparison over `crypto.timingSafeEqual`           |\n| `toRetryAfterHeader()`  | Format a cooldown as a `Retry-After` header value                       |\n| `cooldownExpiresAt()`   | Absolute expiry timestamp for a remaining cooldown                      |\n| `formatCooldown()`      | Human-readable cooldown, for a UI countdown                             |\n\n### React hooks (`./react`)\n\n| Hook                | Returns                                                                             | Purpose                                              |\n| ------------------- | ----------------------------------------------------------------------------------- | ---------------------------------------------------- |\n| `useOtpInput()`     | `{ values, setValue, onChange, onKeyDown, onPaste, refs, code, isComplete, reset }` | Multi-slot OTP input with paste and arrow navigation |\n| `useOtpCountdown()` | `{ remainingSeconds, formatted, expired }`                                          | Expiry countdown for the resend button               |\n\n### Constants\n\n| Export                      | Subpath                     | Purpose                                                        |\n| --------------------------- | --------------------------- | -------------------------------------------------------------- |\n| `NOTIFICATION_ERROR_CODES`  | `./shared` (and the server) | The 22 stable codes, with zero dependencies on the shared side |\n| `DEFAULT_TTLS`              | `./shared` (and the server) | Default lifetimes, so a frontend countdown matches the backend |\n| `NOTIFICATION_PURPOSES`     | server                      | Canonical purposes (`email_verification`, `password_reset`, …) |\n| `CANONICAL_EMAIL_TEMPLATES` | server                      | Canonical template names (`otp_code`, `welcome`, …)            |\n\n> [!IMPORTANT]\n> Only `NOTIFICATION_ERROR_CODES` and `DEFAULT_TTLS` are re-exported from `./shared`. `NOTIFICATION_PURPOSES` and `CANONICAL_EMAIL_TEMPLATES` live on the server subpath — importing them into a browser bundle would pull NestJS in with them. Pass the purpose to your frontend as a plain string.\n\n---\n\n## 🤝 Contributing\n\nContributions are welcome! Please read our [contributing guidelines](./CONTRIBUTING.md) before submitting a pull request.\n\n```bash\n# Clone the repository\ngit clone https://github.com/bymaxone/nest-notification.git\ncd nest-notification\n\n# Install dependencies\npnpm install\n\n# Run tests\npnpm test\n\n# Build\npnpm build\n\n# Type check\npnpm typecheck\n```\n\n---\n\n## 🔒 Security Policy\n\nIf you discover a security vulnerability, please **do not** open a public issue. Instead, email us at **support@bymax.one** with details. We take security seriously and will respond promptly. The full policy is in [SECURITY.md](./SECURITY.md).\n\n---\n\n## 📄 License\n\n[MIT](./LICENSE) © [Bymax One](https://github.com/bymaxone)\n\n---\n\n<p align=\"center\">\n  <sub>Built with ❤️ by <a href=\"https://github.com/bymaxone\">Bymax One</a></sub>\n</p>\n","readmeFilename":"README.md"}