@geekmidas/emailkit 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"client-BkN5BGeJ.mjs","names":["config: EmailClientConfig<T>","options: SendOptions","template: K","options: Omit<SendOptions, 'template'> & {\n props: TemplatePropsFor<T, K>;\n }"],"sources":["../src/client.ts"],"sourcesContent":["import { render } from '@react-email/components';\nimport nodemailer, { type Transporter } from 'nodemailer';\nimport type {\n EmailClient,\n EmailClientConfig,\n SendOptions,\n SendResult,\n TemplateNames,\n TemplatePropsFor,\n TemplateRecord,\n} from './types';\n\nexport class SMTPClient<T extends TemplateRecord> implements EmailClient<T> {\n private transporter: Transporter;\n private config: EmailClientConfig<T>;\n\n constructor(config: EmailClientConfig<T>) {\n this.config = config;\n this.transporter = nodemailer.createTransport(config.smtp as any);\n }\n\n async send(options: SendOptions): Promise<SendResult> {\n const mailOptions = {\n ...this.config.defaults,\n ...options,\n };\n\n if (!mailOptions.from) {\n throw new Error(\n 'The \"from\" field is required in email options or defaults',\n );\n }\n\n if (!mailOptions.text && !mailOptions.html) {\n throw new Error('Either text or html content must be provided');\n }\n\n const info = await this.transporter.sendMail(mailOptions);\n\n return {\n messageId: info.messageId,\n accepted: info.accepted || [],\n rejected: info.rejected || [],\n response: info.response,\n };\n }\n\n async sendTemplate<K extends TemplateNames<T>>(\n template: K,\n options: Omit<SendOptions, 'template'> & {\n props: TemplatePropsFor<T, K>;\n },\n ): Promise<SendResult> {\n const Component = this.config.templates[template];\n if (!Component) {\n throw new Error(`Template \"${String(template)}\" not found`);\n }\n\n const element = Component(options.props);\n const html = await render(element);\n\n return this.send({\n ...options,\n html,\n });\n }\n\n async verify(): Promise<boolean> {\n try {\n await this.transporter.verify();\n return true;\n } catch {\n return false;\n }\n }\n\n async close(): Promise<void> {\n this.transporter.close();\n }\n\n getTemplateNames(): TemplateNames<T>[] {\n return Object.keys(this.config.templates) as TemplateNames<T>[];\n }\n}\n\nexport function createEmailClient<T extends TemplateRecord>(\n config: EmailClientConfig<T>,\n): SMTPClient<T> {\n return new SMTPClient(config);\n}\n"],"mappings":";;;;AAYA,IAAa,aAAb,MAA4E;CAC1E,AAAQ;CACR,AAAQ;CAER,YAAYA,QAA8B;AACxC,OAAK,SAAS;AACd,OAAK,cAAc,WAAW,gBAAgB,OAAO,KAAY;CAClE;CAED,MAAM,KAAKC,SAA2C;EACpD,MAAM,cAAc;GAClB,GAAG,KAAK,OAAO;GACf,GAAG;EACJ;AAED,OAAK,YAAY,KACf,OAAM,IAAI,MACR;AAIJ,OAAK,YAAY,SAAS,YAAY,KACpC,OAAM,IAAI,MAAM;EAGlB,MAAM,OAAO,MAAM,KAAK,YAAY,SAAS,YAAY;AAEzD,SAAO;GACL,WAAW,KAAK;GAChB,UAAU,KAAK,YAAY,CAAE;GAC7B,UAAU,KAAK,YAAY,CAAE;GAC7B,UAAU,KAAK;EAChB;CACF;CAED,MAAM,aACJC,UACAC,SAGqB;EACrB,MAAM,YAAY,KAAK,OAAO,UAAU;AACxC,OAAK,UACH,OAAM,IAAI,OAAO,YAAY,OAAO,SAAS,CAAC;EAGhD,MAAM,UAAU,UAAU,QAAQ,MAAM;EACxC,MAAM,OAAO,MAAM,OAAO,QAAQ;AAElC,SAAO,KAAK,KAAK;GACf,GAAG;GACH;EACD,EAAC;CACH;CAED,MAAM,SAA2B;AAC/B,MAAI;AACF,SAAM,KAAK,YAAY,QAAQ;AAC/B,UAAO;EACR,QAAO;AACN,UAAO;EACR;CACF;CAED,MAAM,QAAuB;AAC3B,OAAK,YAAY,OAAO;CACzB;CAED,mBAAuC;AACrC,SAAO,OAAO,KAAK,KAAK,OAAO,UAAU;CAC1C;AACF;AAED,SAAgB,kBACdH,QACe;AACf,QAAO,IAAI,WAAW;AACvB"}
1
+ {"version":3,"file":"client-BkN5BGeJ.mjs","names":["config: EmailClientConfig<T>","options: SendOptions","template: K","options: Omit<SendOptions, 'template'> & {\n\t\t\tprops: TemplatePropsFor<T, K>;\n\t\t}"],"sources":["../src/client.ts"],"sourcesContent":["import { render } from '@react-email/components';\nimport nodemailer, { type Transporter } from 'nodemailer';\nimport type {\n\tEmailClient,\n\tEmailClientConfig,\n\tSendOptions,\n\tSendResult,\n\tTemplateNames,\n\tTemplatePropsFor,\n\tTemplateRecord,\n} from './types';\n\nexport class SMTPClient<T extends TemplateRecord> implements EmailClient<T> {\n\tprivate transporter: Transporter;\n\tprivate config: EmailClientConfig<T>;\n\n\tconstructor(config: EmailClientConfig<T>) {\n\t\tthis.config = config;\n\t\tthis.transporter = nodemailer.createTransport(config.smtp as any);\n\t}\n\n\tasync send(options: SendOptions): Promise<SendResult> {\n\t\tconst mailOptions = {\n\t\t\t...this.config.defaults,\n\t\t\t...options,\n\t\t};\n\n\t\tif (!mailOptions.from) {\n\t\t\tthrow new Error(\n\t\t\t\t'The \"from\" field is required in email options or defaults',\n\t\t\t);\n\t\t}\n\n\t\tif (!mailOptions.text && !mailOptions.html) {\n\t\t\tthrow new Error('Either text or html content must be provided');\n\t\t}\n\n\t\tconst info = await this.transporter.sendMail(mailOptions);\n\n\t\treturn {\n\t\t\tmessageId: info.messageId,\n\t\t\taccepted: info.accepted || [],\n\t\t\trejected: info.rejected || [],\n\t\t\tresponse: info.response,\n\t\t};\n\t}\n\n\tasync sendTemplate<K extends TemplateNames<T>>(\n\t\ttemplate: K,\n\t\toptions: Omit<SendOptions, 'template'> & {\n\t\t\tprops: TemplatePropsFor<T, K>;\n\t\t},\n\t): Promise<SendResult> {\n\t\tconst Component = this.config.templates[template];\n\t\tif (!Component) {\n\t\t\tthrow new Error(`Template \"${String(template)}\" not found`);\n\t\t}\n\n\t\tconst element = Component(options.props);\n\t\tconst html = await render(element);\n\n\t\treturn this.send({\n\t\t\t...options,\n\t\t\thtml,\n\t\t});\n\t}\n\n\tasync verify(): Promise<boolean> {\n\t\ttry {\n\t\t\tawait this.transporter.verify();\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync close(): Promise<void> {\n\t\tthis.transporter.close();\n\t}\n\n\tgetTemplateNames(): TemplateNames<T>[] {\n\t\treturn Object.keys(this.config.templates) as TemplateNames<T>[];\n\t}\n}\n\nexport function createEmailClient<T extends TemplateRecord>(\n\tconfig: EmailClientConfig<T>,\n): SMTPClient<T> {\n\treturn new SMTPClient(config);\n}\n"],"mappings":";;;;AAYA,IAAa,aAAb,MAA4E;CAC3E,AAAQ;CACR,AAAQ;CAER,YAAYA,QAA8B;AACzC,OAAK,SAAS;AACd,OAAK,cAAc,WAAW,gBAAgB,OAAO,KAAY;CACjE;CAED,MAAM,KAAKC,SAA2C;EACrD,MAAM,cAAc;GACnB,GAAG,KAAK,OAAO;GACf,GAAG;EACH;AAED,OAAK,YAAY,KAChB,OAAM,IAAI,MACT;AAIF,OAAK,YAAY,SAAS,YAAY,KACrC,OAAM,IAAI,MAAM;EAGjB,MAAM,OAAO,MAAM,KAAK,YAAY,SAAS,YAAY;AAEzD,SAAO;GACN,WAAW,KAAK;GAChB,UAAU,KAAK,YAAY,CAAE;GAC7B,UAAU,KAAK,YAAY,CAAE;GAC7B,UAAU,KAAK;EACf;CACD;CAED,MAAM,aACLC,UACAC,SAGsB;EACtB,MAAM,YAAY,KAAK,OAAO,UAAU;AACxC,OAAK,UACJ,OAAM,IAAI,OAAO,YAAY,OAAO,SAAS,CAAC;EAG/C,MAAM,UAAU,UAAU,QAAQ,MAAM;EACxC,MAAM,OAAO,MAAM,OAAO,QAAQ;AAElC,SAAO,KAAK,KAAK;GAChB,GAAG;GACH;EACA,EAAC;CACF;CAED,MAAM,SAA2B;AAChC,MAAI;AACH,SAAM,KAAK,YAAY,QAAQ;AAC/B,UAAO;EACP,QAAO;AACP,UAAO;EACP;CACD;CAED,MAAM,QAAuB;AAC5B,OAAK,YAAY,OAAO;CACxB;CAED,mBAAuC;AACtC,SAAO,OAAO,KAAK,KAAK,OAAO,UAAU;CACzC;AACD;AAED,SAAgB,kBACfH,QACgB;AAChB,QAAO,IAAI,WAAW;AACtB"}
@@ -1,4 +1,4 @@
1
- import { EmailClient, EmailClientConfig, SendOptions, SendResult, TemplateNames, TemplatePropsFor, TemplateRecord } from "./types-DJ4UGqFX.mjs";
1
+ import { EmailClient, EmailClientConfig, SendOptions, SendResult, TemplateNames, TemplatePropsFor, TemplateRecord } from "./types-FrE6exSX.mjs";
2
2
 
3
3
  //#region src/client.d.ts
4
4
  declare class SMTPClient<T extends TemplateRecord> implements EmailClient<T> {
@@ -14,6 +14,8 @@ declare class SMTPClient<T extends TemplateRecord> implements EmailClient<T> {
14
14
  getTemplateNames(): TemplateNames<T>[];
15
15
  }
16
16
  declare function createEmailClient<T extends TemplateRecord>(config: EmailClientConfig<T>): SMTPClient<T>;
17
+ //# sourceMappingURL=client.d.ts.map
18
+
17
19
  //#endregion
18
20
  export { SMTPClient, createEmailClient };
19
- //# sourceMappingURL=client-C6OqJtD1.d.mts.map
21
+ //# sourceMappingURL=client-BrzAoy26.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-BrzAoy26.d.mts","names":[],"sources":["../src/client.ts"],"sourcesContent":[],"mappings":";;;cAYa,qBAAqB,2BAA2B,YAAY;;EAA5D,QAAA,MAAU;EAAA,WAAA,CAAA,MAAA,EAIF,iBAJE,CAIgB,CAJhB,CAAA;EAAA,IAAW,CAAA,OAAA,EASb,WATa,CAAA,EASC,OATD,CASS,UATT,CAAA;EAAc,YAAyB,CAAA,UAmC3C,aAnC2C,CAmC7B,CAnC6B,CAAA,CAAA,CAAA,QAAA,EAoC7D,CApC6D,EAAA,OAAA,EAqC9D,IArC8D,CAqCzD,WArCyD,EAAA,UAAA,CAAA,GAAA;IAIlC,KAAA,EAkC7B,gBAlC6B,CAkCZ,CAlCY,EAkCT,CAlCS,CAAA;EAAC,CAAA,CAAA,EAoCpC,OApCiB,CAoCT,UApCS,CAAA;EAAiB,MAKjB,CAAA,CAAA,EA8CJ,OA9CI,CAAA,OAAA,CAAA;EAAW,KAAW,CAAA,CAAA,EAuD3B,OAvD2B,CAAA,IAAA,CAAA;EAAU,gBAAlB,CAAA,CAAA,EA2Dd,aA3Dc,CA2DA,CA3DA,CAAA,EAAA;;AA0BL,iBAsCd,iBAtCc,CAAA,UAsCc,cAtCd,CAAA,CAAA,MAAA,EAuCrB,iBAvCqB,CAuCH,CAvCG,CAAA,CAAA,EAwC3B,UAxC2B,CAwChB,CAxCgB,CAAA"}
@@ -1,4 +1,4 @@
1
- import { EmailClient, EmailClientConfig, SendOptions, SendResult, TemplateNames, TemplatePropsFor, TemplateRecord } from "./types-BTcAZ0B-.cjs";
1
+ import { EmailClient, EmailClientConfig, SendOptions, SendResult, TemplateNames, TemplatePropsFor, TemplateRecord } from "./types-CLVcanCB.cjs";
2
2
 
3
3
  //#region src/client.d.ts
4
4
  declare class SMTPClient<T extends TemplateRecord> implements EmailClient<T> {
@@ -14,6 +14,8 @@ declare class SMTPClient<T extends TemplateRecord> implements EmailClient<T> {
14
14
  getTemplateNames(): TemplateNames<T>[];
15
15
  }
16
16
  declare function createEmailClient<T extends TemplateRecord>(config: EmailClientConfig<T>): SMTPClient<T>;
17
+ //# sourceMappingURL=client.d.ts.map
18
+
17
19
  //#endregion
18
20
  export { SMTPClient, createEmailClient };
19
- //# sourceMappingURL=client-CgQM19MQ.d.cts.map
21
+ //# sourceMappingURL=client-CGKPzbG7.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-CGKPzbG7.d.cts","names":[],"sources":["../src/client.ts"],"sourcesContent":[],"mappings":";;;cAYa,qBAAqB,2BAA2B,YAAY;;EAA5D,QAAA,MAAU;EAAA,WAAA,CAAA,MAAA,EAIF,iBAJE,CAIgB,CAJhB,CAAA;EAAA,IAAW,CAAA,OAAA,EASb,WATa,CAAA,EASC,OATD,CASS,UATT,CAAA;EAAc,YAAyB,CAAA,UAmC3C,aAnC2C,CAmC7B,CAnC6B,CAAA,CAAA,CAAA,QAAA,EAoC7D,CApC6D,EAAA,OAAA,EAqC9D,IArC8D,CAqCzD,WArCyD,EAAA,UAAA,CAAA,GAAA;IAIlC,KAAA,EAkC7B,gBAlC6B,CAkCZ,CAlCY,EAkCT,CAlCS,CAAA;EAAC,CAAA,CAAA,EAoCpC,OApCiB,CAoCT,UApCS,CAAA;EAAiB,MAKjB,CAAA,CAAA,EA8CJ,OA9CI,CAAA,OAAA,CAAA;EAAW,KAAW,CAAA,CAAA,EAuD3B,OAvD2B,CAAA,IAAA,CAAA;EAAU,gBAAlB,CAAA,CAAA,EA2Dd,aA3Dc,CA2DA,CA3DA,CAAA,EAAA;;AA0BL,iBAsCd,iBAtCc,CAAA,UAsCc,cAtCd,CAAA,CAAA,MAAA,EAuCrB,iBAvCqB,CAuCH,CAvCG,CAAA,CAAA,EAwC3B,UAxC2B,CAwChB,CAxCgB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"client-sZNHPYYg.cjs","names":["config: EmailClientConfig<T>","options: SendOptions","template: K","options: Omit<SendOptions, 'template'> & {\n props: TemplatePropsFor<T, K>;\n }"],"sources":["../src/client.ts"],"sourcesContent":["import { render } from '@react-email/components';\nimport nodemailer, { type Transporter } from 'nodemailer';\nimport type {\n EmailClient,\n EmailClientConfig,\n SendOptions,\n SendResult,\n TemplateNames,\n TemplatePropsFor,\n TemplateRecord,\n} from './types';\n\nexport class SMTPClient<T extends TemplateRecord> implements EmailClient<T> {\n private transporter: Transporter;\n private config: EmailClientConfig<T>;\n\n constructor(config: EmailClientConfig<T>) {\n this.config = config;\n this.transporter = nodemailer.createTransport(config.smtp as any);\n }\n\n async send(options: SendOptions): Promise<SendResult> {\n const mailOptions = {\n ...this.config.defaults,\n ...options,\n };\n\n if (!mailOptions.from) {\n throw new Error(\n 'The \"from\" field is required in email options or defaults',\n );\n }\n\n if (!mailOptions.text && !mailOptions.html) {\n throw new Error('Either text or html content must be provided');\n }\n\n const info = await this.transporter.sendMail(mailOptions);\n\n return {\n messageId: info.messageId,\n accepted: info.accepted || [],\n rejected: info.rejected || [],\n response: info.response,\n };\n }\n\n async sendTemplate<K extends TemplateNames<T>>(\n template: K,\n options: Omit<SendOptions, 'template'> & {\n props: TemplatePropsFor<T, K>;\n },\n ): Promise<SendResult> {\n const Component = this.config.templates[template];\n if (!Component) {\n throw new Error(`Template \"${String(template)}\" not found`);\n }\n\n const element = Component(options.props);\n const html = await render(element);\n\n return this.send({\n ...options,\n html,\n });\n }\n\n async verify(): Promise<boolean> {\n try {\n await this.transporter.verify();\n return true;\n } catch {\n return false;\n }\n }\n\n async close(): Promise<void> {\n this.transporter.close();\n }\n\n getTemplateNames(): TemplateNames<T>[] {\n return Object.keys(this.config.templates) as TemplateNames<T>[];\n }\n}\n\nexport function createEmailClient<T extends TemplateRecord>(\n config: EmailClientConfig<T>,\n): SMTPClient<T> {\n return new SMTPClient(config);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAYA,IAAa,aAAb,MAA4E;CAC1E,AAAQ;CACR,AAAQ;CAER,YAAYA,QAA8B;AACxC,OAAK,SAAS;AACd,OAAK,cAAc,mBAAW,gBAAgB,OAAO,KAAY;CAClE;CAED,MAAM,KAAKC,SAA2C;EACpD,MAAM,cAAc;GAClB,GAAG,KAAK,OAAO;GACf,GAAG;EACJ;AAED,OAAK,YAAY,KACf,OAAM,IAAI,MACR;AAIJ,OAAK,YAAY,SAAS,YAAY,KACpC,OAAM,IAAI,MAAM;EAGlB,MAAM,OAAO,MAAM,KAAK,YAAY,SAAS,YAAY;AAEzD,SAAO;GACL,WAAW,KAAK;GAChB,UAAU,KAAK,YAAY,CAAE;GAC7B,UAAU,KAAK,YAAY,CAAE;GAC7B,UAAU,KAAK;EAChB;CACF;CAED,MAAM,aACJC,UACAC,SAGqB;EACrB,MAAM,YAAY,KAAK,OAAO,UAAU;AACxC,OAAK,UACH,OAAM,IAAI,OAAO,YAAY,OAAO,SAAS,CAAC;EAGhD,MAAM,UAAU,UAAU,QAAQ,MAAM;EACxC,MAAM,OAAO,MAAM,qCAAO,QAAQ;AAElC,SAAO,KAAK,KAAK;GACf,GAAG;GACH;EACD,EAAC;CACH;CAED,MAAM,SAA2B;AAC/B,MAAI;AACF,SAAM,KAAK,YAAY,QAAQ;AAC/B,UAAO;EACR,QAAO;AACN,UAAO;EACR;CACF;CAED,MAAM,QAAuB;AAC3B,OAAK,YAAY,OAAO;CACzB;CAED,mBAAuC;AACrC,SAAO,OAAO,KAAK,KAAK,OAAO,UAAU;CAC1C;AACF;AAED,SAAgB,kBACdH,QACe;AACf,QAAO,IAAI,WAAW;AACvB"}
1
+ {"version":3,"file":"client-sZNHPYYg.cjs","names":["config: EmailClientConfig<T>","options: SendOptions","template: K","options: Omit<SendOptions, 'template'> & {\n\t\t\tprops: TemplatePropsFor<T, K>;\n\t\t}"],"sources":["../src/client.ts"],"sourcesContent":["import { render } from '@react-email/components';\nimport nodemailer, { type Transporter } from 'nodemailer';\nimport type {\n\tEmailClient,\n\tEmailClientConfig,\n\tSendOptions,\n\tSendResult,\n\tTemplateNames,\n\tTemplatePropsFor,\n\tTemplateRecord,\n} from './types';\n\nexport class SMTPClient<T extends TemplateRecord> implements EmailClient<T> {\n\tprivate transporter: Transporter;\n\tprivate config: EmailClientConfig<T>;\n\n\tconstructor(config: EmailClientConfig<T>) {\n\t\tthis.config = config;\n\t\tthis.transporter = nodemailer.createTransport(config.smtp as any);\n\t}\n\n\tasync send(options: SendOptions): Promise<SendResult> {\n\t\tconst mailOptions = {\n\t\t\t...this.config.defaults,\n\t\t\t...options,\n\t\t};\n\n\t\tif (!mailOptions.from) {\n\t\t\tthrow new Error(\n\t\t\t\t'The \"from\" field is required in email options or defaults',\n\t\t\t);\n\t\t}\n\n\t\tif (!mailOptions.text && !mailOptions.html) {\n\t\t\tthrow new Error('Either text or html content must be provided');\n\t\t}\n\n\t\tconst info = await this.transporter.sendMail(mailOptions);\n\n\t\treturn {\n\t\t\tmessageId: info.messageId,\n\t\t\taccepted: info.accepted || [],\n\t\t\trejected: info.rejected || [],\n\t\t\tresponse: info.response,\n\t\t};\n\t}\n\n\tasync sendTemplate<K extends TemplateNames<T>>(\n\t\ttemplate: K,\n\t\toptions: Omit<SendOptions, 'template'> & {\n\t\t\tprops: TemplatePropsFor<T, K>;\n\t\t},\n\t): Promise<SendResult> {\n\t\tconst Component = this.config.templates[template];\n\t\tif (!Component) {\n\t\t\tthrow new Error(`Template \"${String(template)}\" not found`);\n\t\t}\n\n\t\tconst element = Component(options.props);\n\t\tconst html = await render(element);\n\n\t\treturn this.send({\n\t\t\t...options,\n\t\t\thtml,\n\t\t});\n\t}\n\n\tasync verify(): Promise<boolean> {\n\t\ttry {\n\t\t\tawait this.transporter.verify();\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync close(): Promise<void> {\n\t\tthis.transporter.close();\n\t}\n\n\tgetTemplateNames(): TemplateNames<T>[] {\n\t\treturn Object.keys(this.config.templates) as TemplateNames<T>[];\n\t}\n}\n\nexport function createEmailClient<T extends TemplateRecord>(\n\tconfig: EmailClientConfig<T>,\n): SMTPClient<T> {\n\treturn new SMTPClient(config);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAYA,IAAa,aAAb,MAA4E;CAC3E,AAAQ;CACR,AAAQ;CAER,YAAYA,QAA8B;AACzC,OAAK,SAAS;AACd,OAAK,cAAc,mBAAW,gBAAgB,OAAO,KAAY;CACjE;CAED,MAAM,KAAKC,SAA2C;EACrD,MAAM,cAAc;GACnB,GAAG,KAAK,OAAO;GACf,GAAG;EACH;AAED,OAAK,YAAY,KAChB,OAAM,IAAI,MACT;AAIF,OAAK,YAAY,SAAS,YAAY,KACrC,OAAM,IAAI,MAAM;EAGjB,MAAM,OAAO,MAAM,KAAK,YAAY,SAAS,YAAY;AAEzD,SAAO;GACN,WAAW,KAAK;GAChB,UAAU,KAAK,YAAY,CAAE;GAC7B,UAAU,KAAK,YAAY,CAAE;GAC7B,UAAU,KAAK;EACf;CACD;CAED,MAAM,aACLC,UACAC,SAGsB;EACtB,MAAM,YAAY,KAAK,OAAO,UAAU;AACxC,OAAK,UACJ,OAAM,IAAI,OAAO,YAAY,OAAO,SAAS,CAAC;EAG/C,MAAM,UAAU,UAAU,QAAQ,MAAM;EACxC,MAAM,OAAO,MAAM,qCAAO,QAAQ;AAElC,SAAO,KAAK,KAAK;GAChB,GAAG;GACH;EACA,EAAC;CACF;CAED,MAAM,SAA2B;AAChC,MAAI;AACH,SAAM,KAAK,YAAY,QAAQ;AAC/B,UAAO;EACP,QAAO;AACP,UAAO;EACP;CACD;CAED,MAAM,QAAuB;AAC5B,OAAK,YAAY,OAAO;CACxB;CAED,mBAAuC;AACtC,SAAO,OAAO,KAAK,KAAK,OAAO,UAAU;CACzC;AACD;AAED,SAAgB,kBACfH,QACgB;AAChB,QAAO,IAAI,WAAW;AACtB"}
package/dist/client.d.cts CHANGED
@@ -1,3 +1,3 @@
1
- import "./types-BTcAZ0B-.cjs";
2
- import { SMTPClient, createEmailClient } from "./client-CgQM19MQ.cjs";
1
+ import "./types-CLVcanCB.cjs";
2
+ import { SMTPClient, createEmailClient } from "./client-CGKPzbG7.cjs";
3
3
  export { SMTPClient, createEmailClient };
package/dist/client.d.mts CHANGED
@@ -1,3 +1,3 @@
1
- import "./types-DJ4UGqFX.mjs";
2
- import { SMTPClient, createEmailClient } from "./client-C6OqJtD1.mjs";
1
+ import "./types-FrE6exSX.mjs";
2
+ import { SMTPClient, createEmailClient } from "./client-BrzAoy26.mjs";
3
3
  export { SMTPClient, createEmailClient };
package/dist/index.d.cts CHANGED
@@ -1,3 +1,3 @@
1
- import { Attachment, EmailClient, EmailClientConfig, EmailOptions, EmailTemplate, PlainEmailOptions, SMTPConfig, SendResult } from "./types-BTcAZ0B-.cjs";
2
- import { SMTPClient, createEmailClient } from "./client-CgQM19MQ.cjs";
1
+ import { Attachment, EmailClient, EmailClientConfig, EmailOptions, EmailTemplate, PlainEmailOptions, SMTPConfig, SendResult } from "./types-CLVcanCB.cjs";
2
+ import { SMTPClient, createEmailClient } from "./client-CGKPzbG7.cjs";
3
3
  export { Attachment, EmailClient, EmailClientConfig, EmailOptions, EmailTemplate, PlainEmailOptions, SMTPClient, SMTPConfig, SendResult, createEmailClient };
package/dist/index.d.mts CHANGED
@@ -1,3 +1,3 @@
1
- import { Attachment, EmailClient, EmailClientConfig, EmailOptions, EmailTemplate, PlainEmailOptions, SMTPConfig, SendResult } from "./types-DJ4UGqFX.mjs";
2
- import { SMTPClient, createEmailClient } from "./client-C6OqJtD1.mjs";
1
+ import { Attachment, EmailClient, EmailClientConfig, EmailOptions, EmailTemplate, PlainEmailOptions, SMTPConfig, SendResult } from "./types-FrE6exSX.mjs";
2
+ import { SMTPClient, createEmailClient } from "./client-BrzAoy26.mjs";
3
3
  export { Attachment, EmailClient, EmailClientConfig, EmailOptions, EmailTemplate, PlainEmailOptions, SMTPClient, SMTPConfig, SendResult, createEmailClient };
@@ -50,9 +50,7 @@ interface SendResult {
50
50
  rejected: string[];
51
51
  response: string;
52
52
  }
53
- interface EmailTemplate<T = any> {
54
- (props: T): ReactElement;
55
- }
53
+ type EmailTemplate<T = any> = (props: T) => ReactElement;
56
54
  type TemplateProps<T> = T extends EmailTemplate<infer P> ? P : never;
57
55
  type TemplateRecord = Record<string, EmailTemplate<any>>;
58
56
  type TemplateNames<T extends TemplateRecord> = keyof T;
@@ -78,6 +76,8 @@ interface EmailClient<T extends TemplateRecord = TemplateRecord> {
78
76
  verify(): Promise<boolean>;
79
77
  close(): Promise<void>;
80
78
  }
79
+ //# sourceMappingURL=types.d.ts.map
80
+
81
81
  //#endregion
82
82
  export { Attachment, EmailClient, EmailClientConfig, EmailOptions, EmailTemplate, PlainEmailOptions, SMTPConfig, SendOptions, SendResult, TemplateNames, TemplateProps, TemplatePropsFor, TemplateRecord };
83
- //# sourceMappingURL=types-BTcAZ0B-.d.cts.map
83
+ //# sourceMappingURL=types-CLVcanCB.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-CLVcanCB.d.cts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":"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"}
@@ -50,9 +50,7 @@ interface SendResult {
50
50
  rejected: string[];
51
51
  response: string;
52
52
  }
53
- interface EmailTemplate<T = any> {
54
- (props: T): ReactElement;
55
- }
53
+ type EmailTemplate<T = any> = (props: T) => ReactElement;
56
54
  type TemplateProps<T> = T extends EmailTemplate<infer P> ? P : never;
57
55
  type TemplateRecord = Record<string, EmailTemplate<any>>;
58
56
  type TemplateNames<T extends TemplateRecord> = keyof T;
@@ -78,6 +76,8 @@ interface EmailClient<T extends TemplateRecord = TemplateRecord> {
78
76
  verify(): Promise<boolean>;
79
77
  close(): Promise<void>;
80
78
  }
79
+ //# sourceMappingURL=types.d.ts.map
80
+
81
81
  //#endregion
82
82
  export { Attachment, EmailClient, EmailClientConfig, EmailOptions, EmailTemplate, PlainEmailOptions, SMTPConfig, SendOptions, SendResult, TemplateNames, TemplateProps, TemplatePropsFor, TemplateRecord };
83
- //# sourceMappingURL=types-DJ4UGqFX.d.mts.map
83
+ //# sourceMappingURL=types-FrE6exSX.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-FrE6exSX.d.mts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;;;UAGiB,YAAA;iBACD;EADC,EAAA,EAAA,MAAA,GAAA,MAAY,EAAA,GAEJ,OAFI,EAAA;EAAA,EAAA,CAAA,EAAA,MAAA,GAAA,MAAA,EAAA,GAGH,OAHG,EAAA;EAAA,GACb,CAAA,EAAA,MAAA,GAAA,MAAA,EAAA,GAGW,OAHX,EAAA;EAAO,OACE,EAAA,MAAA;EAAO,OACN,CAAA,EAAA,MAAA,GAGN,OAHM;EAAO,WACN,CAAA,EAGZ,UAHY,EAAA;;AAGZ,UAGE,UAAA,CAHF;EAAU,QAAA,EAAA,MAAA;EAGR,OAAA,CAAA,EAAA,MAAU,GAEP,MAAA;EAMH,IAAA,CAAA,EAAA,MAAA;EAKL,WAAA,CAAA,EAAW,MAAA;EAAA,GAAA,CAAA,EAAA,MAAA;;AAAG,UALT,iBAAA,SAA0B,YAKjB,CAAA;EAAI,IACb,CAAA,EAAA,MAAA;EAAO,IAAA,CAAA,EAAA,MAAA;AAGxB;AAoBiB,KAxBL,WAAA,GAAc,IAwBC,CAxBI,iBAwBJ,EAAA,MAAA,CAAA,GAAA;EAOf,IAAA,CAAA,EAAA,MAAA,GA9BK,OA8BQ;CAAA;AAAoB,UA3B5B,UAAA,CA2B4B;EAAC,IAAK,EAAA,MAAA;EAAY,IAAA,EAAA,MAAA;EAGnD,MAAA,CAAA,EAAA,OAAa;EAAA,IAAA,CAAA,EAAA;IAAM,IAAA,EAAA,MAAA;IAAU,IAAA,EAAA,MAAA;EAAa,CAAA;EAAa,GAAA,CAAA,EAAA;IAGvD,kBAAc,CAAA,EAAA,OAAA;IAAA,UAAA,CAAA,EAAA,MAAA;EAAA,CAAA;EAA+B,IAA5B,CAAA,EAAA,OAAA;EAAM,cAAA,CAAA,EAAA,MAAA;EAGvB,WAAA,CAAA,EAAA,MAAa;EAAA,SAAA,CAAA,EAAA,MAAA;EAAA,MAAW,CAAA,EAAA,OAAA;EAAc,KAAU,CAAA,EAAA,OAAA;AAAC;AAGjD,UAnBK,UAAA,CAmBW;EAAA,SAAA,EAAA,MAAA;EAAA,QACjB,EAAA,MAAA,EAAA;EAAc,QACA,EAAA,MAAA,EAAA;EAAC,QAAf,EAAA,MAAA;;AACS,KAfR,aAeQ,CAAA,IAAA,GAAA,CAAA,GAAA,CAAA,KAAA,EAfyB,CAezB,EAAA,GAf+B,YAe/B;AAAhB,KAZQ,aAYR,CAAA,CAAA,CAAA,GAZ2B,CAY3B,SAZqC,aAYrC,CAAA,KAAA,EAAA,CAAA,GAZ8D,CAY9D,GAAA,KAAA;AAAa,KATL,cAAA,GAAiB,MASZ,CAAA,MAAA,EAT2B,aAS3B,CAAA,GAAA,CAAA,CAAA;AAEA,KARL,aAQsB,CAAA,UARE,cAQF,CAAA,GAAA,MAR0B,CAQ1B;AAAA,KALtB,gBAKsB,CAAA,UAJvB,cAIuB,EAAA,UAHvB,aAGuB,CAHT,CAGS,CAAA,CAAA,GAF9B,aAE8B,CAFhB,CAEgB,CAFd,CAEc,CAAA,CAAA;AAAW,UAA5B,iBAA4B,CAAA,UAAA,cAAA,GAAiB,cAAjB,CAAA,CAAA;EAAc,IAAG,EACvD,UADuD;EAAc,SACrE,EACK,CADL;EAAU,IACL,CAAA,EAAA;IAOM,UAAA,EAAA,MAAA;IACG,WAAA,EAAA,MAAA;IAAO,UAAA,EAAA,MAAA;EAIX,CAAA;EAAW,QAAA,EAAA;IAAW,IAAA,CAAA,EAAA,MAAA,GALrB,OAKqB;IAAiB,OAAA,CAAA,EAAA,MAAA,GAJnC,OAImC;EAAc,CAAA;;AACzC,UADZ,WACY,CAAA,UADU,cACV,GAD2B,cAC3B,CAAA,CAAA;EAAO,IACE,CAAA,OAAA,EADvB,WACuB,CAAA,EADT,OACS,CADD,UACC,CAAA;EAAC,YAAf,CAAA,UAAA,aAAA,CAAc,CAAd,CAAA,CAAA,CAAA,QAAA,EACZ,CADY,EAAA,OAAA,EAEb,WAFa,GAAA;IACZ,KAAA,EAEF,gBAFE,CAEe,CAFf,EAEkB,CAFlB,CAAA;EAAC,CAAA,CAAA,EAIT,OAHO,CAGC,UAHD,CAAA;EAAW,MACK,EAAA,EAGhB,OAHgB,CAAA,OAAA,CAAA;EAAC,KAAE,EAAA,EAIpB,OAJoB,CAAA,IAAA,CAAA"}
package/dist/types.d.cts CHANGED
@@ -1,2 +1,2 @@
1
- import { Attachment, EmailClient, EmailClientConfig, EmailOptions, EmailTemplate, PlainEmailOptions, SMTPConfig, SendOptions, SendResult, TemplateNames, TemplateProps, TemplatePropsFor, TemplateRecord } from "./types-BTcAZ0B-.cjs";
1
+ import { Attachment, EmailClient, EmailClientConfig, EmailOptions, EmailTemplate, PlainEmailOptions, SMTPConfig, SendOptions, SendResult, TemplateNames, TemplateProps, TemplatePropsFor, TemplateRecord } from "./types-CLVcanCB.cjs";
2
2
  export { Attachment, EmailClient, EmailClientConfig, EmailOptions, EmailTemplate, PlainEmailOptions, SMTPConfig, SendOptions, SendResult, TemplateNames, TemplateProps, TemplatePropsFor, TemplateRecord };
package/dist/types.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { Attachment, EmailClient, EmailClientConfig, EmailOptions, EmailTemplate, PlainEmailOptions, SMTPConfig, SendOptions, SendResult, TemplateNames, TemplateProps, TemplatePropsFor, TemplateRecord } from "./types-DJ4UGqFX.mjs";
1
+ import { Attachment, EmailClient, EmailClientConfig, EmailOptions, EmailTemplate, PlainEmailOptions, SMTPConfig, SendOptions, SendResult, TemplateNames, TemplateProps, TemplatePropsFor, TemplateRecord } from "./types-FrE6exSX.mjs";
2
2
  export { Attachment, EmailClient, EmailClientConfig, EmailOptions, EmailTemplate, PlainEmailOptions, SMTPConfig, SendOptions, SendResult, TemplateNames, TemplateProps, TemplatePropsFor, TemplateRecord };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekmidas/emailkit",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Type-safe email client with SMTP support and React templates",
5
5
  "private": false,
6
6
  "type": "module",
@@ -0,0 +1,372 @@
1
+ import type { ReactElement } from 'react';
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import { createEmailClient, SMTPClient } from '../client';
4
+ import type { EmailClientConfig, TemplateRecord } from '../types';
5
+
6
+ // Mock nodemailer
7
+ vi.mock('nodemailer', () => ({
8
+ default: {
9
+ createTransport: vi.fn(() => ({
10
+ sendMail: vi.fn(),
11
+ verify: vi.fn(),
12
+ close: vi.fn(),
13
+ })),
14
+ },
15
+ }));
16
+
17
+ // Mock @react-email/components
18
+ vi.mock('@react-email/components', () => ({
19
+ render: vi.fn(),
20
+ }));
21
+
22
+ import { render } from '@react-email/components';
23
+ // Import mocked modules
24
+ import nodemailer from 'nodemailer';
25
+
26
+ // Test templates
27
+ interface WelcomeProps {
28
+ name: string;
29
+ verificationUrl: string;
30
+ }
31
+
32
+ interface ResetPasswordProps {
33
+ resetUrl: string;
34
+ expiresIn: number;
35
+ }
36
+
37
+ const WelcomeTemplate = (props: WelcomeProps): ReactElement =>
38
+ ({ type: 'div', props: { children: `Welcome ${props.name}` } }) as any;
39
+
40
+ const ResetPasswordTemplate = (props: ResetPasswordProps): ReactElement =>
41
+ ({ type: 'div', props: { children: `Reset: ${props.resetUrl}` } }) as any;
42
+
43
+ const templates = {
44
+ welcome: WelcomeTemplate,
45
+ resetPassword: ResetPasswordTemplate,
46
+ } satisfies TemplateRecord;
47
+
48
+ type TestTemplates = typeof templates;
49
+
50
+ describe('SMTPClient', () => {
51
+ let mockTransporter: {
52
+ sendMail: ReturnType<typeof vi.fn>;
53
+ verify: ReturnType<typeof vi.fn>;
54
+ close: ReturnType<typeof vi.fn>;
55
+ };
56
+ let config: EmailClientConfig<TestTemplates>;
57
+
58
+ beforeEach(() => {
59
+ vi.clearAllMocks();
60
+
61
+ mockTransporter = {
62
+ sendMail: vi.fn().mockResolvedValue({
63
+ messageId: 'test-message-id',
64
+ accepted: ['recipient@example.com'],
65
+ rejected: [],
66
+ response: '250 OK',
67
+ }),
68
+ verify: vi.fn().mockResolvedValue(true),
69
+ close: vi.fn(),
70
+ };
71
+
72
+ vi.mocked(nodemailer.createTransport).mockReturnValue(
73
+ mockTransporter as any,
74
+ );
75
+
76
+ vi.mocked(render).mockResolvedValue('<html>Rendered HTML</html>');
77
+
78
+ config = {
79
+ smtp: {
80
+ host: 'smtp.example.com',
81
+ port: 587,
82
+ secure: false,
83
+ auth: {
84
+ user: 'test@example.com',
85
+ pass: 'password',
86
+ },
87
+ },
88
+ templates,
89
+ defaults: {
90
+ from: 'noreply@example.com',
91
+ },
92
+ };
93
+ });
94
+
95
+ describe('constructor', () => {
96
+ it('should create a transporter with SMTP config', () => {
97
+ new SMTPClient(config);
98
+
99
+ expect(nodemailer.createTransport).toHaveBeenCalledWith(config.smtp);
100
+ });
101
+ });
102
+
103
+ describe('send', () => {
104
+ it('should send an email with required options', async () => {
105
+ const client = new SMTPClient(config);
106
+
107
+ const result = await client.send({
108
+ to: 'recipient@example.com',
109
+ subject: 'Test Subject',
110
+ text: 'Test content',
111
+ });
112
+
113
+ expect(mockTransporter.sendMail).toHaveBeenCalledWith({
114
+ from: 'noreply@example.com',
115
+ to: 'recipient@example.com',
116
+ subject: 'Test Subject',
117
+ text: 'Test content',
118
+ });
119
+
120
+ expect(result).toEqual({
121
+ messageId: 'test-message-id',
122
+ accepted: ['recipient@example.com'],
123
+ rejected: [],
124
+ response: '250 OK',
125
+ });
126
+ });
127
+
128
+ it('should send an email with HTML content', async () => {
129
+ const client = new SMTPClient(config);
130
+
131
+ await client.send({
132
+ to: 'recipient@example.com',
133
+ subject: 'HTML Email',
134
+ html: '<h1>Hello</h1>',
135
+ });
136
+
137
+ expect(mockTransporter.sendMail).toHaveBeenCalledWith(
138
+ expect.objectContaining({
139
+ html: '<h1>Hello</h1>',
140
+ }),
141
+ );
142
+ });
143
+
144
+ it('should override default from address', async () => {
145
+ const client = new SMTPClient(config);
146
+
147
+ await client.send({
148
+ from: 'custom@example.com',
149
+ to: 'recipient@example.com',
150
+ subject: 'Custom From',
151
+ text: 'Content',
152
+ });
153
+
154
+ expect(mockTransporter.sendMail).toHaveBeenCalledWith(
155
+ expect.objectContaining({
156
+ from: 'custom@example.com',
157
+ }),
158
+ );
159
+ });
160
+
161
+ it('should throw error when from is not provided and no default', async () => {
162
+ const configNoDefaults: EmailClientConfig<TestTemplates> = {
163
+ ...config,
164
+ defaults: {},
165
+ };
166
+ const client = new SMTPClient(configNoDefaults);
167
+
168
+ await expect(
169
+ client.send({
170
+ to: 'recipient@example.com',
171
+ subject: 'No From',
172
+ text: 'Content',
173
+ }),
174
+ ).rejects.toThrow(
175
+ 'The "from" field is required in email options or defaults',
176
+ );
177
+ });
178
+
179
+ it('should throw error when neither text nor html is provided', async () => {
180
+ const client = new SMTPClient(config);
181
+
182
+ await expect(
183
+ client.send({
184
+ to: 'recipient@example.com',
185
+ subject: 'No Content',
186
+ }),
187
+ ).rejects.toThrow('Either text or html content must be provided');
188
+ });
189
+
190
+ it('should handle multiple recipients', async () => {
191
+ const client = new SMTPClient(config);
192
+
193
+ await client.send({
194
+ to: ['user1@example.com', 'user2@example.com'],
195
+ cc: 'cc@example.com',
196
+ bcc: ['bcc1@example.com', 'bcc2@example.com'],
197
+ subject: 'Multiple Recipients',
198
+ text: 'Content',
199
+ });
200
+
201
+ expect(mockTransporter.sendMail).toHaveBeenCalledWith(
202
+ expect.objectContaining({
203
+ to: ['user1@example.com', 'user2@example.com'],
204
+ cc: 'cc@example.com',
205
+ bcc: ['bcc1@example.com', 'bcc2@example.com'],
206
+ }),
207
+ );
208
+ });
209
+
210
+ it('should handle rejected recipients', async () => {
211
+ mockTransporter.sendMail.mockResolvedValue({
212
+ messageId: 'test-id',
213
+ accepted: ['valid@example.com'],
214
+ rejected: ['invalid@example.com'],
215
+ response: '250 OK',
216
+ });
217
+
218
+ const client = new SMTPClient(config);
219
+
220
+ const result = await client.send({
221
+ to: ['valid@example.com', 'invalid@example.com'],
222
+ subject: 'Test',
223
+ text: 'Content',
224
+ });
225
+
226
+ expect(result.accepted).toEqual(['valid@example.com']);
227
+ expect(result.rejected).toEqual(['invalid@example.com']);
228
+ });
229
+
230
+ it('should handle missing accepted/rejected in response', async () => {
231
+ mockTransporter.sendMail.mockResolvedValue({
232
+ messageId: 'test-id',
233
+ response: '250 OK',
234
+ });
235
+
236
+ const client = new SMTPClient(config);
237
+
238
+ const result = await client.send({
239
+ to: 'test@example.com',
240
+ subject: 'Test',
241
+ text: 'Content',
242
+ });
243
+
244
+ expect(result.accepted).toEqual([]);
245
+ expect(result.rejected).toEqual([]);
246
+ });
247
+ });
248
+
249
+ describe('sendTemplate', () => {
250
+ it('should render and send a template', async () => {
251
+ const client = new SMTPClient(config);
252
+
253
+ const result = await client.sendTemplate('welcome', {
254
+ to: 'user@example.com',
255
+ subject: 'Welcome!',
256
+ props: {
257
+ name: 'John',
258
+ verificationUrl: 'https://example.com/verify',
259
+ },
260
+ });
261
+
262
+ expect(render).toHaveBeenCalled();
263
+ expect(mockTransporter.sendMail).toHaveBeenCalledWith(
264
+ expect.objectContaining({
265
+ to: 'user@example.com',
266
+ subject: 'Welcome!',
267
+ html: '<html>Rendered HTML</html>',
268
+ }),
269
+ );
270
+ expect(result.messageId).toBe('test-message-id');
271
+ });
272
+
273
+ it('should render resetPassword template', async () => {
274
+ const client = new SMTPClient(config);
275
+
276
+ await client.sendTemplate('resetPassword', {
277
+ to: 'user@example.com',
278
+ subject: 'Reset Your Password',
279
+ props: {
280
+ resetUrl: 'https://example.com/reset/abc123',
281
+ expiresIn: 3600,
282
+ },
283
+ });
284
+
285
+ expect(render).toHaveBeenCalled();
286
+ expect(mockTransporter.sendMail).toHaveBeenCalled();
287
+ });
288
+
289
+ it('should throw error for unknown template', async () => {
290
+ const client = new SMTPClient(config);
291
+
292
+ await expect(
293
+ client.sendTemplate('nonexistent' as any, {
294
+ to: 'user@example.com',
295
+ subject: 'Test',
296
+ props: {},
297
+ }),
298
+ ).rejects.toThrow('Template "nonexistent" not found');
299
+ });
300
+ });
301
+
302
+ describe('verify', () => {
303
+ it('should return true when connection is valid', async () => {
304
+ mockTransporter.verify.mockResolvedValue(true);
305
+ const client = new SMTPClient(config);
306
+
307
+ const result = await client.verify();
308
+
309
+ expect(result).toBe(true);
310
+ expect(mockTransporter.verify).toHaveBeenCalled();
311
+ });
312
+
313
+ it('should return false when connection fails', async () => {
314
+ mockTransporter.verify.mockRejectedValue(new Error('Connection failed'));
315
+ const client = new SMTPClient(config);
316
+
317
+ const result = await client.verify();
318
+
319
+ expect(result).toBe(false);
320
+ });
321
+ });
322
+
323
+ describe('close', () => {
324
+ it('should close the transporter', async () => {
325
+ const client = new SMTPClient(config);
326
+
327
+ await client.close();
328
+
329
+ expect(mockTransporter.close).toHaveBeenCalled();
330
+ });
331
+ });
332
+
333
+ describe('getTemplateNames', () => {
334
+ it('should return all template names', () => {
335
+ const client = new SMTPClient(config);
336
+
337
+ const names = client.getTemplateNames();
338
+
339
+ expect(names).toContain('welcome');
340
+ expect(names).toContain('resetPassword');
341
+ expect(names).toHaveLength(2);
342
+ });
343
+ });
344
+ });
345
+
346
+ describe('createEmailClient', () => {
347
+ beforeEach(() => {
348
+ vi.clearAllMocks();
349
+ vi.mocked(nodemailer.createTransport).mockReturnValue({
350
+ sendMail: vi.fn(),
351
+ verify: vi.fn(),
352
+ close: vi.fn(),
353
+ } as any);
354
+ });
355
+
356
+ it('should create an SMTPClient instance', () => {
357
+ const config: EmailClientConfig<TestTemplates> = {
358
+ smtp: {
359
+ host: 'smtp.example.com',
360
+ port: 587,
361
+ },
362
+ templates,
363
+ defaults: {
364
+ from: 'test@example.com',
365
+ },
366
+ };
367
+
368
+ const client = createEmailClient(config);
369
+
370
+ expect(client).toBeInstanceOf(SMTPClient);
371
+ });
372
+ });
@@ -1,182 +1,185 @@
1
1
  // biome-ignore lint/correctness/noUnusedImports: <explanation>
2
2
  import React from 'react';
3
3
  import { beforeEach, describe, expect, it, vi } from 'vitest';
4
- import { type SMTPClient, createEmailClient } from '../client';
4
+ import { createEmailClient, type SMTPClient } from '../client';
5
5
  import type { EmailClientConfig } from '../types';
6
6
 
7
7
  // Mock nodemailer
8
8
  vi.mock('nodemailer', () => ({
9
- default: {
10
- createTransport: vi.fn(() => ({
11
- sendMail: vi.fn().mockResolvedValue({
12
- messageId: 'test-message-id',
13
- accepted: ['test@example.com'],
14
- rejected: [],
15
- response: '250 OK',
16
- }),
17
- verify: vi.fn().mockResolvedValue(true),
18
- close: vi.fn(),
19
- })),
20
- },
9
+ default: {
10
+ createTransport: vi.fn(() => ({
11
+ sendMail: vi.fn().mockResolvedValue({
12
+ messageId: 'test-message-id',
13
+ accepted: ['test@example.com'],
14
+ rejected: [],
15
+ response: '250 OK',
16
+ }),
17
+ verify: vi.fn().mockResolvedValue(true),
18
+ close: vi.fn(),
19
+ })),
20
+ },
21
21
  }));
22
22
 
23
23
  const TestTemplate = ({ name }: { name: string }) => <div>Hello {name}!</div>;
24
24
 
25
25
  const AnotherTemplate = ({
26
- title,
27
- message,
28
- }: { title: string; message: string }) => (
29
- <div>
30
- <h1>{title}</h1>
31
- <p>{message}</p>
32
- </div>
26
+ title,
27
+ message,
28
+ }: {
29
+ title: string;
30
+ message: string;
31
+ }) => (
32
+ <div>
33
+ <h1>{title}</h1>
34
+ <p>{message}</p>
35
+ </div>
33
36
  );
34
37
 
35
38
  describe('SMTPClient', () => {
36
- let client: SMTPClient<typeof templates>;
37
-
38
- const templates = {
39
- test: TestTemplate,
40
- another: AnotherTemplate,
41
- };
42
-
43
- const config: EmailClientConfig<typeof templates> = {
44
- smtp: {
45
- host: 'smtp.example.com',
46
- port: 587,
47
- auth: {
48
- user: 'test@example.com',
49
- pass: 'password',
50
- },
51
- },
52
- templates,
53
- defaults: {
54
- from: 'noreply@example.com',
55
- },
56
- };
57
-
58
- beforeEach(() => {
59
- client = createEmailClient(config);
60
- });
61
-
62
- describe('send', () => {
63
- it('should send plain text email', async () => {
64
- const result = await client.send({
65
- from: 'sender@example.com',
66
- to: 'recipient@example.com',
67
- subject: 'Test Email',
68
- text: 'This is a test email',
69
- });
70
-
71
- expect(result).toEqual({
72
- messageId: 'test-message-id',
73
- accepted: ['test@example.com'],
74
- rejected: [],
75
- response: '250 OK',
76
- });
77
- });
78
-
79
- it('should send HTML email', async () => {
80
- const result = await client.send({
81
- from: 'sender@example.com',
82
- to: 'recipient@example.com',
83
- subject: 'Test Email',
84
- html: '<p>This is a test email</p>',
85
- });
86
-
87
- expect(result.messageId).toBe('test-message-id');
88
- });
89
-
90
- it('should use default from address', async () => {
91
- const result = await client.send({
92
- to: 'recipient@example.com',
93
- subject: 'Test Email',
94
- text: 'This is a test email',
95
- from: {
96
- name: 'Test Sender',
97
- address: 'sender@example.com',
98
- },
99
- });
100
-
101
- expect(result.messageId).toBe('test-message-id');
102
- });
103
-
104
- it('should throw error if no content provided', async () => {
105
- await expect(
106
- client.send({
107
- from: 'sender@example.com',
108
- to: 'recipient@example.com',
109
- subject: 'Test Email',
110
- }),
111
- ).rejects.toThrow('Either text or html content must be provided');
112
- });
113
-
114
- it('should support multiple recipients', async () => {
115
- const result = await client.send({
116
- from: 'sender@example.com',
117
- to: ['recipient1@example.com', 'recipient2@example.com'],
118
- subject: 'Test Email',
119
- text: 'This is a test email',
120
- });
121
-
122
- expect(result.messageId).toBe('test-message-id');
123
- });
124
-
125
- it('should support CC and BCC', async () => {
126
- const result = await client.send({
127
- from: 'sender@example.com',
128
- to: 'recipient@example.com',
129
- cc: 'cc@example.com',
130
- bcc: ['bcc1@example.com', 'bcc2@example.com'],
131
- subject: 'Test Email',
132
- text: 'This is a test email',
133
- });
134
-
135
- expect(result.messageId).toBe('test-message-id');
136
- });
137
- });
138
-
139
- describe('sendTemplate', () => {
140
- it('should send email with React template', async () => {
141
- const result = await client.sendTemplate('test', {
142
- from: 'sender@example.com',
143
- to: 'recipient@example.com',
144
- subject: 'Test Template Email',
145
- props: { name: 'John' },
146
- });
147
-
148
- expect(result.messageId).toBe('test-message-id');
149
- });
150
-
151
- it('should send email with another template', async () => {
152
- const result = await client.sendTemplate('another', {
153
- from: 'sender@example.com',
154
- to: 'recipient@example.com',
155
- subject: 'Another Template Email',
156
- props: { title: 'Hello', message: 'World' },
157
- });
158
-
159
- expect(result.messageId).toBe('test-message-id');
160
- });
161
- });
162
-
163
- describe('verify', () => {
164
- it('should verify SMTP connection', async () => {
165
- const result = await client.verify();
166
- expect(result).toBe(true);
167
- });
168
- });
169
-
170
- describe('close', () => {
171
- it('should close SMTP connection', async () => {
172
- await expect(client.close()).resolves.not.toThrow();
173
- });
174
- });
175
-
176
- describe('template names', () => {
177
- it('should return available template names', () => {
178
- const templateNames = client.getTemplateNames();
179
- expect(templateNames).toEqual(['test', 'another']);
180
- });
181
- });
39
+ let client: SMTPClient<typeof templates>;
40
+
41
+ const templates = {
42
+ test: TestTemplate,
43
+ another: AnotherTemplate,
44
+ };
45
+
46
+ const config: EmailClientConfig<typeof templates> = {
47
+ smtp: {
48
+ host: 'smtp.example.com',
49
+ port: 587,
50
+ auth: {
51
+ user: 'test@example.com',
52
+ pass: 'password',
53
+ },
54
+ },
55
+ templates,
56
+ defaults: {
57
+ from: 'noreply@example.com',
58
+ },
59
+ };
60
+
61
+ beforeEach(() => {
62
+ client = createEmailClient(config);
63
+ });
64
+
65
+ describe('send', () => {
66
+ it('should send plain text email', async () => {
67
+ const result = await client.send({
68
+ from: 'sender@example.com',
69
+ to: 'recipient@example.com',
70
+ subject: 'Test Email',
71
+ text: 'This is a test email',
72
+ });
73
+
74
+ expect(result).toEqual({
75
+ messageId: 'test-message-id',
76
+ accepted: ['test@example.com'],
77
+ rejected: [],
78
+ response: '250 OK',
79
+ });
80
+ });
81
+
82
+ it('should send HTML email', async () => {
83
+ const result = await client.send({
84
+ from: 'sender@example.com',
85
+ to: 'recipient@example.com',
86
+ subject: 'Test Email',
87
+ html: '<p>This is a test email</p>',
88
+ });
89
+
90
+ expect(result.messageId).toBe('test-message-id');
91
+ });
92
+
93
+ it('should use default from address', async () => {
94
+ const result = await client.send({
95
+ to: 'recipient@example.com',
96
+ subject: 'Test Email',
97
+ text: 'This is a test email',
98
+ from: {
99
+ name: 'Test Sender',
100
+ address: 'sender@example.com',
101
+ },
102
+ });
103
+
104
+ expect(result.messageId).toBe('test-message-id');
105
+ });
106
+
107
+ it('should throw error if no content provided', async () => {
108
+ await expect(
109
+ client.send({
110
+ from: 'sender@example.com',
111
+ to: 'recipient@example.com',
112
+ subject: 'Test Email',
113
+ }),
114
+ ).rejects.toThrow('Either text or html content must be provided');
115
+ });
116
+
117
+ it('should support multiple recipients', async () => {
118
+ const result = await client.send({
119
+ from: 'sender@example.com',
120
+ to: ['recipient1@example.com', 'recipient2@example.com'],
121
+ subject: 'Test Email',
122
+ text: 'This is a test email',
123
+ });
124
+
125
+ expect(result.messageId).toBe('test-message-id');
126
+ });
127
+
128
+ it('should support CC and BCC', async () => {
129
+ const result = await client.send({
130
+ from: 'sender@example.com',
131
+ to: 'recipient@example.com',
132
+ cc: 'cc@example.com',
133
+ bcc: ['bcc1@example.com', 'bcc2@example.com'],
134
+ subject: 'Test Email',
135
+ text: 'This is a test email',
136
+ });
137
+
138
+ expect(result.messageId).toBe('test-message-id');
139
+ });
140
+ });
141
+
142
+ describe('sendTemplate', () => {
143
+ it('should send email with React template', async () => {
144
+ const result = await client.sendTemplate('test', {
145
+ from: 'sender@example.com',
146
+ to: 'recipient@example.com',
147
+ subject: 'Test Template Email',
148
+ props: { name: 'John' },
149
+ });
150
+
151
+ expect(result.messageId).toBe('test-message-id');
152
+ });
153
+
154
+ it('should send email with another template', async () => {
155
+ const result = await client.sendTemplate('another', {
156
+ from: 'sender@example.com',
157
+ to: 'recipient@example.com',
158
+ subject: 'Another Template Email',
159
+ props: { title: 'Hello', message: 'World' },
160
+ });
161
+
162
+ expect(result.messageId).toBe('test-message-id');
163
+ });
164
+ });
165
+
166
+ describe('verify', () => {
167
+ it('should verify SMTP connection', async () => {
168
+ const result = await client.verify();
169
+ expect(result).toBe(true);
170
+ });
171
+ });
172
+
173
+ describe('close', () => {
174
+ it('should close SMTP connection', async () => {
175
+ await expect(client.close()).resolves.not.toThrow();
176
+ });
177
+ });
178
+
179
+ describe('template names', () => {
180
+ it('should return available template names', () => {
181
+ const templateNames = client.getTemplateNames();
182
+ expect(templateNames).toEqual(['test', 'another']);
183
+ });
184
+ });
182
185
  });
package/src/client.ts CHANGED
@@ -1,90 +1,90 @@
1
1
  import { render } from '@react-email/components';
2
2
  import nodemailer, { type Transporter } from 'nodemailer';
3
3
  import type {
4
- EmailClient,
5
- EmailClientConfig,
6
- SendOptions,
7
- SendResult,
8
- TemplateNames,
9
- TemplatePropsFor,
10
- TemplateRecord,
4
+ EmailClient,
5
+ EmailClientConfig,
6
+ SendOptions,
7
+ SendResult,
8
+ TemplateNames,
9
+ TemplatePropsFor,
10
+ TemplateRecord,
11
11
  } from './types';
12
12
 
13
13
  export class SMTPClient<T extends TemplateRecord> implements EmailClient<T> {
14
- private transporter: Transporter;
15
- private config: EmailClientConfig<T>;
14
+ private transporter: Transporter;
15
+ private config: EmailClientConfig<T>;
16
16
 
17
- constructor(config: EmailClientConfig<T>) {
18
- this.config = config;
19
- this.transporter = nodemailer.createTransport(config.smtp as any);
20
- }
17
+ constructor(config: EmailClientConfig<T>) {
18
+ this.config = config;
19
+ this.transporter = nodemailer.createTransport(config.smtp as any);
20
+ }
21
21
 
22
- async send(options: SendOptions): Promise<SendResult> {
23
- const mailOptions = {
24
- ...this.config.defaults,
25
- ...options,
26
- };
22
+ async send(options: SendOptions): Promise<SendResult> {
23
+ const mailOptions = {
24
+ ...this.config.defaults,
25
+ ...options,
26
+ };
27
27
 
28
- if (!mailOptions.from) {
29
- throw new Error(
30
- 'The "from" field is required in email options or defaults',
31
- );
32
- }
28
+ if (!mailOptions.from) {
29
+ throw new Error(
30
+ 'The "from" field is required in email options or defaults',
31
+ );
32
+ }
33
33
 
34
- if (!mailOptions.text && !mailOptions.html) {
35
- throw new Error('Either text or html content must be provided');
36
- }
34
+ if (!mailOptions.text && !mailOptions.html) {
35
+ throw new Error('Either text or html content must be provided');
36
+ }
37
37
 
38
- const info = await this.transporter.sendMail(mailOptions);
38
+ const info = await this.transporter.sendMail(mailOptions);
39
39
 
40
- return {
41
- messageId: info.messageId,
42
- accepted: info.accepted || [],
43
- rejected: info.rejected || [],
44
- response: info.response,
45
- };
46
- }
40
+ return {
41
+ messageId: info.messageId,
42
+ accepted: info.accepted || [],
43
+ rejected: info.rejected || [],
44
+ response: info.response,
45
+ };
46
+ }
47
47
 
48
- async sendTemplate<K extends TemplateNames<T>>(
49
- template: K,
50
- options: Omit<SendOptions, 'template'> & {
51
- props: TemplatePropsFor<T, K>;
52
- },
53
- ): Promise<SendResult> {
54
- const Component = this.config.templates[template];
55
- if (!Component) {
56
- throw new Error(`Template "${String(template)}" not found`);
57
- }
48
+ async sendTemplate<K extends TemplateNames<T>>(
49
+ template: K,
50
+ options: Omit<SendOptions, 'template'> & {
51
+ props: TemplatePropsFor<T, K>;
52
+ },
53
+ ): Promise<SendResult> {
54
+ const Component = this.config.templates[template];
55
+ if (!Component) {
56
+ throw new Error(`Template "${String(template)}" not found`);
57
+ }
58
58
 
59
- const element = Component(options.props);
60
- const html = await render(element);
59
+ const element = Component(options.props);
60
+ const html = await render(element);
61
61
 
62
- return this.send({
63
- ...options,
64
- html,
65
- });
66
- }
62
+ return this.send({
63
+ ...options,
64
+ html,
65
+ });
66
+ }
67
67
 
68
- async verify(): Promise<boolean> {
69
- try {
70
- await this.transporter.verify();
71
- return true;
72
- } catch {
73
- return false;
74
- }
75
- }
68
+ async verify(): Promise<boolean> {
69
+ try {
70
+ await this.transporter.verify();
71
+ return true;
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
76
 
77
- async close(): Promise<void> {
78
- this.transporter.close();
79
- }
77
+ async close(): Promise<void> {
78
+ this.transporter.close();
79
+ }
80
80
 
81
- getTemplateNames(): TemplateNames<T>[] {
82
- return Object.keys(this.config.templates) as TemplateNames<T>[];
83
- }
81
+ getTemplateNames(): TemplateNames<T>[] {
82
+ return Object.keys(this.config.templates) as TemplateNames<T>[];
83
+ }
84
84
  }
85
85
 
86
86
  export function createEmailClient<T extends TemplateRecord>(
87
- config: EmailClientConfig<T>,
87
+ config: EmailClientConfig<T>,
88
88
  ): SMTPClient<T> {
89
- return new SMTPClient(config);
89
+ return new SMTPClient(config);
90
90
  }
package/src/index.ts CHANGED
@@ -1,11 +1,11 @@
1
- export { SMTPClient, createEmailClient } from './client';
1
+ export { createEmailClient, SMTPClient } from './client';
2
2
  export type {
3
- EmailClient,
4
- EmailClientConfig,
5
- SMTPConfig,
6
- EmailOptions,
7
- PlainEmailOptions,
8
- SendResult,
9
- Attachment,
10
- EmailTemplate,
3
+ Attachment,
4
+ EmailClient,
5
+ EmailClientConfig,
6
+ EmailOptions,
7
+ EmailTemplate,
8
+ PlainEmailOptions,
9
+ SendResult,
10
+ SMTPConfig,
11
11
  } from './types';
package/src/types.ts CHANGED
@@ -2,62 +2,60 @@ import type { Address } from 'nodemailer/lib/mailer';
2
2
  import type { ReactElement } from 'react';
3
3
 
4
4
  export interface EmailOptions {
5
- from: string | Address;
6
- to: string | string[] | Address[];
7
- cc?: string | string[] | Address[];
8
- bcc?: string | string[] | Address[];
9
- subject: string;
10
- replyTo?: string | Address;
11
- attachments?: Attachment[];
5
+ from: string | Address;
6
+ to: string | string[] | Address[];
7
+ cc?: string | string[] | Address[];
8
+ bcc?: string | string[] | Address[];
9
+ subject: string;
10
+ replyTo?: string | Address;
11
+ attachments?: Attachment[];
12
12
  }
13
13
 
14
14
  export interface Attachment {
15
- filename: string;
16
- content?: string | Buffer;
17
- path?: string;
18
- contentType?: string;
19
- cid?: string;
15
+ filename: string;
16
+ content?: string | Buffer;
17
+ path?: string;
18
+ contentType?: string;
19
+ cid?: string;
20
20
  }
21
21
 
22
22
  export interface PlainEmailOptions extends EmailOptions {
23
- text?: string;
24
- html?: string;
23
+ text?: string;
24
+ html?: string;
25
25
  }
26
26
 
27
27
  export type SendOptions = Omit<PlainEmailOptions, 'from'> & {
28
- from?: string | Address;
28
+ from?: string | Address;
29
29
  };
30
30
 
31
31
  export interface SMTPConfig {
32
- host: string;
33
- port: number;
34
- secure?: boolean;
35
- auth?: {
36
- user: string;
37
- pass: string;
38
- };
39
- tls?: {
40
- rejectUnauthorized?: boolean;
41
- servername?: string;
42
- };
43
- pool?: boolean;
44
- maxConnections?: number;
45
- maxMessages?: number;
46
- rateLimit?: number;
47
- logger?: boolean;
48
- debug?: boolean;
32
+ host: string;
33
+ port: number;
34
+ secure?: boolean;
35
+ auth?: {
36
+ user: string;
37
+ pass: string;
38
+ };
39
+ tls?: {
40
+ rejectUnauthorized?: boolean;
41
+ servername?: string;
42
+ };
43
+ pool?: boolean;
44
+ maxConnections?: number;
45
+ maxMessages?: number;
46
+ rateLimit?: number;
47
+ logger?: boolean;
48
+ debug?: boolean;
49
49
  }
50
50
 
51
51
  export interface SendResult {
52
- messageId: string;
53
- accepted: string[];
54
- rejected: string[];
55
- response: string;
52
+ messageId: string;
53
+ accepted: string[];
54
+ rejected: string[];
55
+ response: string;
56
56
  }
57
57
 
58
- export interface EmailTemplate<T = any> {
59
- (props: T): ReactElement;
60
- }
58
+ export type EmailTemplate<T = any> = (props: T) => ReactElement;
61
59
 
62
60
  // Extract props type from a template function
63
61
  export type TemplateProps<T> = T extends EmailTemplate<infer P> ? P : never;
@@ -70,32 +68,32 @@ export type TemplateNames<T extends TemplateRecord> = keyof T;
70
68
 
71
69
  // Extract props for a specific template
72
70
  export type TemplatePropsFor<
73
- T extends TemplateRecord,
74
- K extends TemplateNames<T>,
71
+ T extends TemplateRecord,
72
+ K extends TemplateNames<T>,
75
73
  > = TemplateProps<T[K]>;
76
74
 
77
75
  export interface EmailClientConfig<T extends TemplateRecord = TemplateRecord> {
78
- smtp: SMTPConfig;
79
- templates: T;
80
- dkim?: {
81
- domainName: string;
82
- keySelector: string;
83
- privateKey: string;
84
- };
85
- defaults: {
86
- from?: string | Address;
87
- replyTo?: string | Address;
88
- };
76
+ smtp: SMTPConfig;
77
+ templates: T;
78
+ dkim?: {
79
+ domainName: string;
80
+ keySelector: string;
81
+ privateKey: string;
82
+ };
83
+ defaults: {
84
+ from?: string | Address;
85
+ replyTo?: string | Address;
86
+ };
89
87
  }
90
88
 
91
89
  export interface EmailClient<T extends TemplateRecord = TemplateRecord> {
92
- send(options: SendOptions): Promise<SendResult>;
93
- sendTemplate<K extends TemplateNames<T>>(
94
- template: K,
95
- options: SendOptions & {
96
- props: TemplatePropsFor<T, K>;
97
- },
98
- ): Promise<SendResult>;
99
- verify(): Promise<boolean>;
100
- close(): Promise<void>;
90
+ send(options: SendOptions): Promise<SendResult>;
91
+ sendTemplate<K extends TemplateNames<T>>(
92
+ template: K,
93
+ options: SendOptions & {
94
+ props: TemplatePropsFor<T, K>;
95
+ },
96
+ ): Promise<SendResult>;
97
+ verify(): Promise<boolean>;
98
+ close(): Promise<void>;
101
99
  }
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "composite": true,
7
+ "jsx": "react-jsx"
8
+ },
9
+ "include": ["src/**/*"]
10
+ }