'req',
'resize',
'sinon',
+ 'smtp',
'threadjs',
'threadwork',
'tinypool',
- 'pioardi'
- 'jerome-benoit'
versioning-strategy: increase
+ - package-ecosystem: 'npm'
+ directory: '/examples/typescript/smtp-client-pool'
+ schedule:
+ interval: 'daily'
+ labels:
+ - 'dependencies'
+ - 'examples'
+ - 'nocombine'
+ reviewers:
+ - 'pioardi'
+ - 'jerome-benoit'
+ versioning-strategy: increase
- package-ecosystem: 'npm'
directory: '/examples/typescript/http-server-pool/express-worker_threads'
schedule:
### Added
- Add kill handler to worker options allowing to execute custom code when worker is killed.
+- SMTP server pool example: nodemailer.
## [2.6.25] - 2023-08-13
- [Javascript](./examples/javascript/)
- [Typescript](./examples/typescript/)
- [HTTP client pool](./examples/typescript/http-client-pool/)
+ - [SMTP client pool](./examples/typescript/smtp-client-pool/)
- [HTTP server pool](./examples/typescript/http-server-pool/)
- [Express worker_threads pool](./examples/typescript/http-server-pool/express-worker_threads/)
- [Fastify worker_threads pool](./examples/typescript/http-server-pool/fastify-worker_threads/)
console.info(
`Received in ${elapsedTime.toFixed(2)}ms an array with ${
responses.length
- } responses from ${parallelism} parallel requests made with ${workerFunction} on ${requestUrl}:\n`,
+ } responses from ${parallelism} parallel requests made with HTTP client pool task function ${workerFunction} on ${requestUrl}:\n`,
responses
)
} catch (error) {
--- /dev/null
+{
+ "$schema": "https://json.schemastore.org/package",
+ "name": "smtp-client-pool",
+ "version": "1.0.0",
+ "description": "SMTP client pool",
+ "main": "dist/main.js",
+ "type": "module",
+ "volta": {
+ "node": "20.5.1",
+ "pnpm": "8.6.12"
+ },
+ "scripts": {
+ "build": "pnpm build:clean && tsc",
+ "build:clean": "tsc --build --clean",
+ "start": "node dist/main.js",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "nodemailer": "^6.9.4",
+ "poolifier": "^2.6.25"
+ },
+ "devDependencies": {
+ "@types/node": "^20.5.0",
+ "@types/nodemailer": "^6.4.9",
+ "typescript": "^5.1.6"
+ }
+}
--- /dev/null
+lockfileVersion: '6.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+dependencies:
+ nodemailer:
+ specifier: ^6.9.4
+ version: 6.9.4
+ poolifier:
+ specifier: ^2.6.25
+ version: 2.6.25
+
+devDependencies:
+ '@types/node':
+ specifier: ^20.5.0
+ version: 20.5.0
+ '@types/nodemailer':
+ specifier: ^6.4.9
+ version: 6.4.9
+ typescript:
+ specifier: ^5.1.6
+ version: 5.1.6
+
+packages:
+
+ /@types/node@20.5.0:
+ resolution: {integrity: sha512-Mgq7eCtoTjT89FqNoTzzXg2XvCi5VMhRV6+I2aYanc6kQCBImeNaAYRs/DyoVqk1YEUJK5gN9VO7HRIdz4Wo3Q==}
+ dev: true
+
+ /@types/nodemailer@6.4.9:
+ resolution: {integrity: sha512-XYG8Gv+sHjaOtUpiuytahMy2mM3rectgroNbs6R3djZEKmPNiIJwe9KqOJBGzKKnNZNKvnuvmugBgpq3w/S0ig==}
+ dependencies:
+ '@types/node': 20.5.0
+ dev: true
+
+ /nodemailer@6.9.4:
+ resolution: {integrity: sha512-CXjQvrQZV4+6X5wP6ZIgdehJamI63MFoYFGGPtHudWym9qaEHDNdPzaj5bfMCvxG1vhAileSWW90q7nL0N36mA==}
+ engines: {node: '>=6.0.0'}
+ dev: false
+
+ /poolifier@2.6.25:
+ resolution: {integrity: sha512-e8RNC8txuDO7x1ALNMDTUVWyrsMCod3krp/ZIhR+L9Q0KpoywwHekyWnRB4V2PYW/B1yxvXoPbQi1a2hZOfsNw==}
+ engines: {node: '>=16.14.0', pnpm: '>=8.6.0'}
+ requiresBuild: true
+ dev: false
+
+ /typescript@5.1.6:
+ resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+ dev: true
--- /dev/null
+import { smtpClientPool } from './pool.js'
+
+const tos = ['bar@example.com, baz@example.com']
+
+const smtpClientPoolPromises = new Set<Promise<unknown>>()
+for (const to of tos) {
+ smtpClientPoolPromises.add(
+ smtpClientPool.execute({
+ smtpTransport: {
+ host: 'smtp.domain.tld',
+ port: 465,
+ secure: true,
+ auth: {
+ user: 'REPLACE-WITH-YOUR-ALIAS@DOMAIN.TLD',
+ pass: 'REPLACE-WITH-YOUR-GENERATED-PASSWORD'
+ }
+ },
+ mail: {
+ from: '"Foo" <foo@domain.tld>',
+ to,
+ subject: 'Hello',
+ text: 'Hello world?',
+ html: '<b>Hello world?</b>'
+ }
+ })
+ )
+}
+try {
+ const now = performance.now()
+ await Promise.all(smtpClientPoolPromises)
+ const elapsedTime = performance.now() - now
+ console.info(
+ `Send in parallel in ${elapsedTime.toFixed(2)}ms ${
+ tos.length
+ } mails with SMTP client pool`
+ )
+} catch (error) {
+ console.error(error)
+}
--- /dev/null
+import { fileURLToPath } from 'node:url'
+import { dirname, extname, join } from 'node:path'
+import { DynamicThreadPool, availableParallelism } from 'poolifier'
+import { type WorkerData } from './types.js'
+
+const workerFile = join(
+ dirname(fileURLToPath(import.meta.url)),
+ `worker${extname(fileURLToPath(import.meta.url))}`
+)
+
+export const smtpClientPool = new DynamicThreadPool<WorkerData>(
+ 1,
+ availableParallelism(),
+ workerFile,
+ {
+ enableTasksQueue: true,
+ tasksQueueOptions: {
+ concurrency: 8
+ },
+ errorHandler: (e: Error) => {
+ console.error('Thread worker error:', e)
+ }
+ }
+)
--- /dev/null
+import type Mail from 'nodemailer/lib/mailer/index.js'
+import type SMTPTransport from 'nodemailer/lib/smtp-transport/index.js'
+
+export interface WorkerData {
+ smtpTransport: SMTPTransport.Options
+ mail: Mail.Options
+}
--- /dev/null
+import { ThreadWorker } from 'poolifier'
+import { createTransport } from 'nodemailer'
+import type Mail from 'nodemailer/lib/mailer/index.js'
+import { type WorkerData } from './types.js'
+
+class SmtpClientWorker extends ThreadWorker<WorkerData> {
+ public constructor () {
+ super({
+ nodemailer: async (workerData?: WorkerData) => {
+ await createTransport(workerData?.smtpTransport).sendMail(
+ workerData?.mail as Mail.Options
+ )
+ }
+ })
+ }
+}
+
+export const smtpClientWorker = new SmtpClientWorker()
--- /dev/null
+{
+ "$schema": "https://json.schemastore.org/tsconfig",
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ES2022",
+ "moduleResolution": "Node16",
+ "verbatimModuleSyntax": true,
+ "rootDir": "./src",
+ "outDir": "./dist",
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "strict": true,
+ "skipLibCheck": true
+ }
+}