From 2aeb52bf13e1686f6ff80c23aa067e265a267304 Mon Sep 17 00:00:00 2001 From: Philipp Kunz Date: Tue, 27 May 2025 18:00:14 +0000 Subject: [PATCH] fix(structure): Unify structure even further --- readme.hints.md | 23 +++++ readme.next-steps.md | 94 ++++++++++++++++++ readme.plan.md | Bin 9124 -> 14976 bytes readme.progress-2025-05-27.md | 81 +++++++++++++++ test/test.bouncemanager.ts | 10 +- test/test.smartmail.ts | 14 +-- ts/classes.dcrouter.ts | 66 +++--------- ts/mail/core/classes.email.ts | 10 +- ts/mail/core/classes.templatemanager.ts | 2 +- .../routing/classes.unified.email.server.ts | 46 +-------- 10 files changed, 231 insertions(+), 115 deletions(-) create mode 100644 readme.next-steps.md create mode 100644 readme.progress-2025-05-27.md diff --git a/readme.hints.md b/readme.hints.md index af61143..9206f2b 100644 --- a/readme.hints.md +++ b/readme.hints.md @@ -609,4 +609,27 @@ email.to.forEach(recipient => { // Convert back to options format const optionsAgain = email.toEmailOptions(); +``` + +### Template Email Creation (2025-05-27) +The Email class now supports template creation without recipients: +- IEmailOptions 'to' field is now optional (for templates) +- Email constructor allows creation without recipients +- Recipients are added later when the email is actually sent + +```typescript +// Template creation (no recipients) +const emailOptions: IEmailOptions = { + from: 'noreply@example.com', + subject: 'Welcome {{name}}', + text: 'Hello {{name}}!', + // 'to' is omitted for templates + variables: { name: 'User' } +}; + +const templateEmail = new Email(emailOptions); +// templateEmail.to is an empty array [] + +// Later, when sending: +templateEmail.to = ['recipient@example.com']; ``` \ No newline at end of file diff --git a/readme.next-steps.md b/readme.next-steps.md new file mode 100644 index 0000000..43496fb --- /dev/null +++ b/readme.next-steps.md @@ -0,0 +1,94 @@ +# Quick Start - Next Steps for DcRouter + +## 🎯 One Simple Goal +Make DcRouter only know about UnifiedEmailServer. Remove all other email component references. + +## 🚀 Start Here (Day 1) + +### 1. Open `ts/classes.dcrouter.ts` and remove these properties: +```typescript +// DELETE THESE LINES: +public domainRouter: DomainRouter; +public deliveryQueue: UnifiedDeliveryQueue; +public deliverySystem: MultiModeDeliverySystem; + +// KEEP ONLY: +public unifiedEmailServer: UnifiedEmailServer; +``` + +### 2. Update the constructor to stop initializing removed components + +### 3. Simplify `setupUnifiedEmailHandling()`: +```typescript +private async setupUnifiedEmailHandling() { + if (!this.options.emailPortConfig) return; + + // Create unified email server + this.unifiedEmailServer = new UnifiedEmailServer(this, { + ports: this.options.emailPortConfig.ports, + hostname: this.options.emailPortConfig.hostname, + // ... other config + }); + + // Start it + await this.unifiedEmailServer.start(); + + // Generate routes for proxy + const routes = this.unifiedEmailServer.generateProxyRoutes(); + // Add routes to smartproxy... +} +``` + +### 4. Update start/stop methods: +```typescript +// In start(): +if (this.unifiedEmailServer) { + await this.unifiedEmailServer.start(); +} + +// In stop(): +if (this.unifiedEmailServer) { + await this.unifiedEmailServer.stop(); +} +``` + +### 5. Fix any TypeScript errors that appear + +## 📝 Day 1 Checklist +- [ ] Removed domainRouter property +- [ ] Removed deliveryQueue property +- [ ] Removed deliverySystem property +- [ ] Updated constructor +- [ ] Simplified setupUnifiedEmailHandling() +- [ ] Updated start() method +- [ ] Updated stop() method +- [ ] Fixed TypeScript errors +- [ ] Build passes: `pnpm run build` + +## 🔧 Day 2: Fix TypeScript Warnings + +### In `ts/mail/routing/classes.unified.email.server.ts`: +1. Remove unused imports +2. Fix unused variables (`keySize`, `isValidEmail`, etc.) +3. Remove unused event handler parameters + +### Run and fix: +```bash +pnpm run build +# Fix any warnings that appear +``` + +## ✅ Definition of Done +- DcRouter only has `unifiedEmailServer` property +- No direct access to email components from DcRouter +- Build passes with no errors +- Minimal TypeScript warnings + +## 💡 Tips +- Use `git grep domainRouter` to find all references +- Check for any methods in DcRouter that access removed properties +- Run tests after each major change to catch issues early + +--- + +**Remember: The goal is simplification. If in doubt, remove code rather than refactor it.** \ No newline at end of file diff --git a/readme.plan.md b/readme.plan.md index eeceddbcee57aef8d23247585dcf8a520ed3be4c..8fdc106cfbdc32a57b1ee5ce76c5f7385941c5c5 100644 GIT binary patch literal 14976 zcmbVTTXP#ncHYV(p7WT8-qn@~YDws3#apmt7fn(!*P_TJ=yfVC*J6li5@P`~$n*?J zSkYEqvQ?>U)p9yPV9s)50mfkW@j=f=8IY8rkvr-Pe1zmhQ{=KpUR;@3c4?*6KW>Vxi2m>jmw6(LnM=jaAv&1RU#SJ(2lXG&R+_sW0^d57m-I>J=?G~w(MNaIzxWtZsczplft|Ka29`;WIDezb|Z+T~4K9{hNkIBOnlneTBH zbGlsE(WJ~4mAOjF9M|A_A93YV>nby-ZEC=W;)uU2U1#Z|Suxf7B7_Rs&^+Xh`q{)4-BhY;rT zWKtC+4s}z^OjVFwUSz?(HNx66l;SkS0u3Wedu~gcPpm2rE-=11PB@f%TDP9*??Di< zTorE1oZ$G+A#u>YEhs0|qFnyAwlzd8d>t(vIJJpgT!ve|c@QD8VG&48y5fdILK<}u@Wm(wUO%qB$JK3C<#r%C33C;x@ z;!iGjZqK1cVwj-zX9et-w{qd|5mOgsv4C=ywknrqR$Raas4BGN^n{mcS#GmTv0j1M zYBR&GNtGB^m9>~**?T{qCXi6iU!(TQvoKgRnq=tHSvJe6rJ37mTBJ^92flueqfP9g z7}k>9OePZxJD{W%XWzlTI+JG5)@ru&7u#susaNl4KDbbqd3U}{!{vz=2=Y)~X3#I} zcvX}yRb)GxcE~)bXEBlB_{74zcP2o4Oo?%Nz+G_X0^HeIWD#|f(n1|q=Fvg!93S8U z|DmQKlbu(*gzohAk8dJ>5gTIKg!gb;An`qvJGBc7$C*!-I1_Jx9u5}?6Qb_?<{ENu z4hFL|kB_LcjY;eJhK>>Ux7xa%b)M&4`t&rr2B&EZ*Lf1MChU%3;u+%ud;zCM*Wce% zrTypurQ+eQl?oxQIR$}5k+P;}zUQzxiUMesZo`O_0)O}f<_$oJ9&`>@Z|Gku{Fj`~;M8M)Dx|;5 zIS!P>-MWN>ue#{AzjF>1Yaj&ImEs(9>mRuX?@lc-#wi z0doA;Kl|AlLty&kCWJ(0S4dxx>;^CWTe}z|kPVQ^G0&{VgEIuKE-HIN6 z+O8Ak#Nf~LgV9E0fGCRc6!ztK(j37<(+5*LtMAR>-w=+#2m(5~b{^?ER`-YXI?M4@ zfJXu_O+RtVd}6%2T)Tgmpb!)!Bnw7*_b}&rc`pUDfy$*eqJUH%c~I2s&tvgLB(Qxi z!|kE42yd--_wI|LLc{~RJYCUW8hC(7hoH-vu}JRNV;ijFGFaanKqQLE#P2j0>jvgU zp+*AZL!4yJY;|RAfF+Eg8tXj}6=r}2Jc4`fAfhdJ4vkH|-bbqg>q1X^%mw1ZAy>u`Xgzgd# z!qt9r{Vf+pNVKYF6g^<$Dg$Q)K?IF8{hb-GSh_SRY$-fg^FrJ^!c$#J)o;^p;$g({ zogkurV$+(yST^jUiLj8y-H6cDE+D=KD!j1pFdBaLA)qF=mQmrhk=J@hehhD^fCnKc z-n@mi9}dC%I4r}rHhF^Zs%0#9a}`iK18knvd~0?Usvg8pN!OELv;+vZVQ90kr3sHB zL5W8ED8)DC5{VVf-`qm?V}(nU5uM`x0;PUoI;Y!klPAFoNuzsuMVtgIgb3LWLo^QN zU&%RrxM;ws9%*ttcST{3z|eKM^@*IUNiyGPvMvj)(+1vDD?LXvMc@&~dwu4(8Iquwhv z*_54SwGopV{yHV^6Sf8R^ZN)@)iiShy>;`%kN;TS$p1jsL7r8aq|@-a$GcxxYE2x0+|pesWo4>8J7eAn$4f zX?&5<5_*%XB!l7Np=5=~2lqF-=Nch}02N+Ad)c{(Gj34ZOUMY3qO6bCeOW!QbCBlF z-}(n1=*G&;LHaVAe>Sj!GShG~I9@~{vwV%%?O}YNk*pAA_`Bp;L+ZdF%&cwXl~)fQ z28t6`Ht4#Z!9fr`qt_78viX@BHLGdKmv#w5j2OfwlPOFuoq_DmXM`WnM19H#ClalJ zK6Nb{bnq*)sLy8E|kzHJ}$CFKBh+B8psa&Rece&Fb4 za5A`@CzKjT#T(?w+zuQvy;Xl>u(A2g{clw-be>PZ(1NvpIAz#fp{0StXd8z{sO!EE z6NxtmFE+X_s%kbgkM7^^K80sVhGu;Ego*J7ADfp?N|$_W7EgZpsu7ge?7@ByOgAjBPfE^g7UzS zUN7anBP|E=QP=rKL_modY3iKwD*Y^@1xxGM^(puCLv+2B5w0a_{}R58_PNOWC@f`?A(DCK!K|%_9)Z{vEy;C7qXNkEJ zPbYyK;b8@HL5k_;-O%Lq{LGfhrfK0SRS5WZ4C6Vyc^fjxibohI7rQ4G646NEPvHS0 ztVT15Apmwb(6Rg$-$sa(Vwyq-KlEmLo?PP`KJsgbC0Swy{EO3UZZR{QMPEzOc?X%n z`Jz3hzDiLULDu{!-0x@MTzn%MhkwB@DrrL)qQy2;??M)00r7@`V(%1(=8SoYB-b5- zv1+%|U6c^Wj3;SBP6it}LWvK9^>V;*p(%`hc>lv+cgckUFzt)`X^6%E@uuLvcuSvq zBRzofT6p2*G-0+14BtbiOT=5)|A1HvAH%y?EStE5RwmT$x-uz(*90)V;JYI4ETY=x zIoDw#;*y_>$I)D&8;F7_Nt^IYOskV@{KbaHtZr^_{04<{ja3xK*1bkdl^tYdZw8+T z(!M!gK+={M(7|AE8)<6(#nb{I9;>#=;CZr;bIY^-rVjK>KTfLYP>Jarmt*zFpTW(l zX^}-7XMq0K;vDcH0vFM7Vz8u)YVeN)9b!U#Jcsua0X#Ba?JC^tDu)JZ%`a~P*T6`n zWbh~^qTK=vi#(V493bo*X&lWajv&BDNu5}Sdb#t}8A;R;lr<0?76;W|1jo#MmcUu% zqCkyXa)+P8zyZAM=SbJM8WmRHf;QEsH7Fd8IZ}kG0P0*v>T#Ma#NI_+!ZU_6-ek$? zBVR^iUEbH+C83I?LJH6)8#FbzpSTQUoD!I@c8`b`bpVleGe`u2P{#5pw|ER`-HM3~kxQyfE5A`zjb?FO~0 z5kxFeFF5b^Xsu{Q! z?8HSuBe5>4)7FwmP+WMSJ@DC`0`)G378DpLNt1<)0~No?yVbe0)-+8mP05oADrx^w zjY>3}+-}L%i7a7m2zkbx#uBuLq`rh6kM!1hl3&1E-?&i5q8@AM%seA)h-iyU3@8m^ zk2WZu%?=5~eR&wwR7k?Q>@@S(+9%=nr-AV$ncK_Bde-J*96Kenz_07NTtNmze(EH}fDt7x&5mamN zoOqipSRxQ@-u!DQF8I*gO*n@(U726SHSlv3#(_S&`SfkIjB#7l>X>eEnvGj=Qdg9y zUJT6xlO{{hf<~>PN)V3!L7w1tnb#4I%tIk=zDB04vI_@ybzEizR7#rbcC@uXE4QDb z_wR@Qz+%6xv&l<#U?FGqtVTl;E%eBu9>a`1at}Gy+eRj3;Rf?`tO`R?w$GN^_-EBD zU2qNME_V2nIsIba9PS*vFwc&5p2G`x4n_qCR~RG#-=oma`X%~^@-(~5QhHC7zt%Ji zkWXL9{lYwl>Tmz^oxROrPt~}&QE?(s4;S*h(KihY-~Q*HyuG8xiMzU*)}6ih>FO-O z@nN3U-iGFuCa2Z8*s;FRQQ2%OmT4qd#ZN>v=SyoB%1^>uKvO!Y)1*ZFZ~D)Uj&9+C za8uWM_xj$bu5dqwz(4)?=6}VOOX42MXY({GUB$M<2ip%RJ#Ix$qzCneY_I`wT)BI4 zXVwkUiQw;YP`RcNV>?e1{5{L?!59p;D;riS6LmzN-|<*qq64?12E>2t!A@ zkae8W!SwCoqQ}+DjcH~uCpruF_aiO=^`$!`G_;z&cG!%4+5z`n`fl5C=6;9ri1sIs zJ!_+7S3wI(J4mMjlnA+!4+}Xi)Q|l(XqrLtWo))THRIp=FVULa(~s5HB0BZ?4f-Bt z>2OND!Q1#YhJ;6RFg__uIE9AI+Sz(~w0A^Qs6F09IOA*qTv%(n?e5f~A+y9}Ak%S^ zIKh)(wtxIDh8kdZf}$3+mp+Bc`*8cwI^~s~6fd?C2#C9b@A!r4UMk77arnT6wrwc>maH&fh&O@wv5HQ65V6}+A~RQqQHLN^oi>?U z+PrYyNF9ajshz>XNcg|H2#UxiybcYaP+&oh=R8tGAVoKZq+DATZ6UXhyjxWcNGPgA zH`l(OJNE1AS))=@z;?}fHOPkCDvM&8q^S(87QO?*Pi+Ze46xE35yXERpc(ye(gC-& z_YIfRbQeYSY7cE1JJHs`y~i_SO`od?Ra^v1&{P1F0i4!`1m2=idO_A=!4OCbqUL3~mJXI{%9Rw)wANgEtuCs%X@MUPi5h8t45sCTXel^O{LIbTO+hAmavEM>Ux3 zgvQ#<>_vhORQ2gnE+gX@v9*KAi{FgEz1Fxun85%BZ^* zW|i5*n}H|`McIdvLb2QNut(ZG&I^mvQ>^1u40R_`Quc8TDAl917SjOgqXiiSZ3)F2 zY4Ab!)Fw0j%7n%dv46TKAZN|x{_w9x%qD(1Q<hqT&{oY>NwhBb*jVX0oG#Z^uy z?tgdz@stJ{q=}+c7$@vJe{S}UUhMl&%U@p%U2gNlrMj~aHieln$oci5UBSCyTRlFeb6E7<-O@1nkby<*yU&vt9I|6(LwRA&5G+xwAV2Ixr@_PhpTe#OIM<3#xcJa?3e+hp%yt9iDN_J@2gU)fJj*;6M#MShKb2ALtR!)tT+x zjCZ}>QJ@dY{{p04Lh^%Rr&hSxIGL3-6iuDs#|z5Lu1CR<*DZF_kv8%Ye5MfMJ?aW6 zbS5DcN46*%{4K(^u_-J}IjPZ*Uk3;yJxuh^A%)`8CSf$4e3GN10SP5ahF@LJ*l>VP zeWN4<2EWJqALRK{^Q=I48~4KYdS6e`(#Tkst#|-INHwX6(}WXJB%tKX4*l=>L^UOt zZ#3lXAOC$F-sWS}e9I-GMbK$yq3%Ghs#z}P1cdj#J5z8uuyDD&nj-Nhjm5Cb?CXJ% zbN;7!p=p@2u~uxgxe;ZZ9=MZw^EiQu&S?`z`4(0RQr*hyZyWflH$4R}83xFH zQW2o2;unEzNDNJ((IiR7Hr(mscY;U;g{%U9bAYDo3oE$IynXZL?VEqX-*p`9i^Iew z!V=F$`e#(k`GYvcS@1ae=k1$6`?XHA>gC%XA$uasPCKM@@817v?&%Mg ze!oEpdYm7rvTYD4g^>!Rtiy9NyR#~njN^9!%CcK+PB89m64ot2wN+620G6r_y zmU83=xmG!9D=1!rlr{%7qTg7dW*0#$xtg9)sYql_aA(uUZ~keo~fD&S{%4Owe>vbObGi zM$-{pr>l-p)4;Jbt`0sQCQvw^>URxyw7HGiQt{AKG#o-dDAkD0RE=nwevV%&1 zi)v@&L7+J6KbIgX9!<>dj;+7Zga>)T)egr9KgE7O&l`kCeBM ziEx>p4?H9^XAC+a6LBIE7p2AE+6`Y36oZX=>v3Z>aFOdE`?CG>LHp^`@WHbL!ou6Ixmx0Dv1raOkZU}@G*`N|`668)3jVqHW&RGC z*WP$*a40fT6v+}%s`N4ub$xjQlBiF+U}2`47<3wzsU3}=OBtuqnG3;@e45ZG-2zd@ z98-*>^V$;ZY4?rg!FuVyTVNPCFA;{r9x5B9O?bQpr-;WMH_#I1*br9sNqV~hPyXt& z`w&2U-AbzQ+QL50w?%vfzO^KNu^MC~{^8J%?xjT-L1PH69PAyGagVg_7>?6l^y-yzAS!)Z9$eIBP51gdCw)JI4K+r { tap.test('BounceManager - should process bounce emails correctly', async () => { const bounceManager = new BounceManager(); - // Create a mock bounce email as Smartmail - const bounceEmail = new plugins.smartmail.Smartmail({ + // Create a mock bounce email + const bounceEmail = new Email({ from: 'mailer-daemon@example.com', subject: 'Mail delivery failed: returning message to sender', - body: ` + text: ` This message was created automatically by mail delivery software. A message that you sent could not be delivered to one or more of its recipients. @@ -123,7 +123,7 @@ tap.test('BounceManager - should process bounce emails correctly', async () => { Status: 5.2.2 diagnostic-code: smtp; 552 5.2.2 Mailbox full `, - creationObjectRef: {} + to: 'sender@example.com' // Bounce emails are typically sent back to the original sender }); const result = await bounceManager.processBounceEmail(bounceEmail); diff --git a/test/test.smartmail.ts b/test/test.smartmail.ts index 43f6844..2234881 100644 --- a/test/test.smartmail.ts +++ b/test/test.smartmail.ts @@ -76,7 +76,7 @@ tap.test('TemplateManager - should register and retrieve templates', async (tool expect(templates.some(t => t.id === 'test-template')).toEqual(true); }); -tap.test('TemplateManager - should create smartmail from template', async (tools) => { +tap.test('TemplateManager - should create email from template', async (tools) => { const templateManager = new TemplateManager({ from: 'test@example.com' }); @@ -93,15 +93,15 @@ tap.test('TemplateManager - should create smartmail from template', async (tools category: 'test' }); - // Create smartmail from template - const smartmail = await templateManager.createSmartmail('welcome-test', { + // Create email from template + const email = await templateManager.createEmail('welcome-test', { name: 'John Doe' }); - expect(smartmail).toBeTruthy(); - expect(smartmail.options.from).toEqual('welcome@example.com'); - expect(smartmail.getSubject()).toEqual('Welcome, John Doe!'); - expect(smartmail.getBody(true).indexOf('Hello, John Doe!') > -1).toEqual(true); + expect(email).toBeTruthy(); + expect(email.from).toEqual('welcome@example.com'); + expect(email.getSubjectWithVariables()).toEqual('Welcome, John Doe!'); + expect(email.getHtmlWithVariables()?.indexOf('Hello, John Doe!') > -1).toEqual(true); }); tap.test('Email - should handle template variables', async (tools) => { diff --git a/ts/classes.dcrouter.ts b/ts/classes.dcrouter.ts index 137f3a9..d8c03c3 100644 --- a/ts/classes.dcrouter.ts +++ b/ts/classes.dcrouter.ts @@ -438,76 +438,34 @@ export class DcRouter { * This implements the consolidated emailConfig approach */ private async setupUnifiedEmailHandling(): Promise { - logger.log('info', 'Setting up unified email handling with pattern-based routing'); - if (!this.options.emailConfig) { throw new Error('Email configuration is required for unified email handling'); } const emailConfig = this.options.emailConfig; - - // Map external ports to internal ports with support for custom port mapping - const defaultPortMapping = { + const portMapping = this.options.emailPortConfig?.portMapping || { 25: 10025, // SMTP 587: 10587, // Submission 465: 10465 // SMTPS }; - // Use custom port mapping if provided, otherwise fall back to defaults - const portMapping = this.options.emailPortConfig?.portMapping || defaultPortMapping; - - // Create internal email server configuration - const internalEmailConfig: IEmailConfig = { + // Create unified email server with mapped internal ports + this.emailServer = new UnifiedEmailServer(this, { ...emailConfig, + domains: emailConfig.domains || [], // Provide default empty array ports: emailConfig.ports.map(port => portMapping[port] || port + 10000), hostname: 'localhost' // Listen on localhost for SmartProxy forwarding - }; + }); - // If custom MTA options are provided, merge them - if (this.options.emailPortConfig?.portSettings) { - // Will be used in MTA configuration - logger.log('info', 'Custom port settings detected for email configuration'); - } + // Set up error handling + this.emailServer.on('error', (err: Error) => { + logger.log('error', `UnifiedEmailServer error: ${err.message}`); + }); - // Configure custom email storage path if specified - if (this.options.emailPortConfig?.receivedEmailsPath) { - logger.log('info', `Custom email storage path configured: ${this.options.emailPortConfig.receivedEmailsPath}`); - } + // Start the server + await this.emailServer.start(); - try { - // Create unified email server with all components - this.emailServer = new UnifiedEmailServer(this, { - ports: emailConfig.ports.map(port => portMapping[port] || port + 10000), - hostname: 'localhost', // Listen on localhost for SmartProxy forwarding - domains: emailConfig.domains || [], - domainRules: emailConfig.domainRules, - defaultMode: emailConfig.defaultMode, - defaultServer: emailConfig.defaultServer, - defaultPort: emailConfig.defaultPort, - defaultTls: emailConfig.defaultTls, - maxMessageSize: emailConfig.maxMessageSize, - auth: emailConfig.auth, - tls: emailConfig.tls, - dkim: emailConfig.dkim, - outbound: emailConfig.outbound, - rateLimits: emailConfig.rateLimits, - debug: emailConfig.debug - }); - - // Set up event listeners - this.emailServer.on('error', (err: Error) => { - logger.log('error', `UnifiedEmailServer error: ${err.message}`); - }); - - // Start the unified email server - await this.emailServer.start(); - - logger.log('info', `Unified email handling configured with ${emailConfig.domainRules.length} domain rules`); - logger.log('info', `Email server listening on internal ports: ${this.emailServer['options'].ports.join(', ')}`); - } catch (error) { - logger.log('error', `Error setting up unified email handling: ${error.message}`); - throw error; - } + logger.log('info', `Email server started on ports: ${this.emailServer['options'].ports.join(', ')}`); } /** diff --git a/ts/mail/core/classes.email.ts b/ts/mail/core/classes.email.ts index a7087cc..165f15a 100644 --- a/ts/mail/core/classes.email.ts +++ b/ts/mail/core/classes.email.ts @@ -11,7 +11,7 @@ export interface IAttachment { export interface IEmailOptions { from: string; - to: string | string[]; // Support multiple recipients + to?: string | string[]; // Optional for templates cc?: string | string[]; // Optional CC recipients bcc?: string | string[]; // Optional BCC recipients subject: string; @@ -68,16 +68,14 @@ export class Email { this.from = options.from; // Handle to addresses (single or multiple) - this.to = this.parseRecipients(options.to); + this.to = options.to ? this.parseRecipients(options.to) : []; // Handle optional cc and bcc this.cc = options.cc ? this.parseRecipients(options.cc) : []; this.bcc = options.bcc ? this.parseRecipients(options.bcc) : []; - // Validate that we have at least one recipient - if (this.to.length === 0 && this.cc.length === 0 && this.bcc.length === 0) { - throw new Error('Email must have at least one recipient'); - } + // Note: Templates may be created without recipients + // Recipients will be added when the email is actually sent // Set subject with sanitization this.subject = this.sanitizeString(options.subject || ''); diff --git a/ts/mail/core/classes.templatemanager.ts b/ts/mail/core/classes.templatemanager.ts index ac51fb1..01f5343 100644 --- a/ts/mail/core/classes.templatemanager.ts +++ b/ts/mail/core/classes.templatemanager.ts @@ -237,7 +237,7 @@ export class TemplateManager { subject: template.subject, text: template.bodyText || '', html: template.bodyHtml, - to: '', // Will be set when sending + // Note: 'to' is intentionally omitted for templates attachments, variables: context || {} }; diff --git a/ts/mail/routing/classes.unified.email.server.ts b/ts/mail/routing/classes.unified.email.server.ts index 07d8318..751700c 100644 --- a/ts/mail/routing/classes.unified.email.server.ts +++ b/ts/mail/routing/classes.unified.email.server.ts @@ -22,7 +22,6 @@ import type { } from './classes.email.config.js'; import { Email } from '../core/classes.email.js'; import { BounceManager, BounceType, BounceCategory } from '../core/classes.bouncemanager.js'; -import * as tls from 'node:tls'; import { createSmtpServer } from '../delivery/smtpserver/index.js'; import { createPooledSmtpClient } from '../delivery/smtpclient/create-client.js'; import type { SmtpClient } from '../delivery/smtpclient/smtp-client.js'; @@ -167,17 +166,16 @@ export class UnifiedEmailServer extends EventEmitter { private domainRouter: DomainRouter; private servers: any[] = []; private stats: IServerStats; - private processingTimes: number[] = []; // Add components needed for sending and securing emails public dkimCreator: DKIMCreator; - private ipReputationChecker: IPReputationChecker; + private ipReputationChecker: IPReputationChecker; // TODO: Implement IP reputation checks in processEmailByMode private bounceManager: BounceManager; private ipWarmupManager: IPWarmupManager; private senderReputationMonitor: SenderReputationMonitor; public deliveryQueue: UnifiedDeliveryQueue; public deliverySystem: MultiModeDeliverySystem; - private rateLimiter: UnifiedRateLimiter; + private rateLimiter: UnifiedRateLimiter; // TODO: Implement rate limiting in SMTP server handlers private dkimKeys: Map = new Map(); // domain -> private key private smtpClients: Map = new Map(); // host:port -> client @@ -265,7 +263,7 @@ export class UnifiedEmailServer extends EventEmitter { bounceHandler: { processSmtpFailure: this.processSmtpFailure.bind(this) }, - onDeliverySuccess: async (item, result) => { + onDeliverySuccess: async (item, _result) => { // Record delivery success event for reputation monitoring const email = item.processingResult as Email; const senderDomain = email.from.split('@')[1]; @@ -479,13 +477,7 @@ export class UnifiedEmailServer extends EventEmitter { logger.log('info', 'Stopping UnifiedEmailServer'); try { - // Stop all SMTP servers - for (const server of this.servers) { - // Nothing to do, servers will be garbage collected - // The server.stop() method is not needed during this transition - } - - // Clear the servers array + // Clear the servers array - servers will be garbage collected this.servers = []; // Stop the delivery system @@ -523,26 +515,6 @@ export class UnifiedEmailServer extends EventEmitter { - /** - * Update processing time statistics - */ - private updateProcessingTimeStats(): void { - if (this.processingTimes.length === 0) return; - - // Keep only the last 1000 processing times - if (this.processingTimes.length > 1000) { - this.processingTimes = this.processingTimes.slice(-1000); - } - - // Calculate stats - const sum = this.processingTimes.reduce((acc, time) => acc + time, 0); - const avg = sum / this.processingTimes.length; - const max = Math.max(...this.processingTimes); - const min = Math.min(...this.processingTimes); - - this.stats.processingTime = { avg, max, min }; - } - /** * Process email based on the determined mode */ @@ -825,7 +797,6 @@ export class UnifiedEmailServer extends EventEmitter { } const selector = this.options.dkim?.selector || 'default'; - const keySize = this.options.dkim?.keySize || 2048; for (const domain of this.options.domains) { try { @@ -973,15 +944,6 @@ export class UnifiedEmailServer extends EventEmitter { return { ...this.stats }; } - /** - * Validate email address format - */ - private isValidEmail(email: string): boolean { - // Basic validation - a more comprehensive validation could be used - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - return emailRegex.test(email); - } - /** * Send an email through the delivery system * @param email The email to send