@gravito/signal 3.0.4 → 3.1.2

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.
Files changed (124) hide show
  1. package/README.md +26 -15
  2. package/dist/Mailable.d.ts +453 -0
  3. package/dist/OrbitSignal.d.ts +267 -0
  4. package/{src/Queueable.ts → dist/Queueable.d.ts} +1 -1
  5. package/{src/TypedMailable.ts → dist/TypedMailable.d.ts} +12 -13
  6. package/dist/dev/DevMailbox.d.ts +79 -0
  7. package/dist/dev/DevServer.d.ts +39 -0
  8. package/dist/dev/storage/FileMailboxStorage.d.ts +16 -0
  9. package/dist/dev/storage/MailboxStorage.d.ts +14 -0
  10. package/dist/dev/storage/MemoryMailboxStorage.d.ts +13 -0
  11. package/dist/dev/ui/mailbox.d.ts +11 -0
  12. package/dist/dev/ui/preview.d.ts +11 -0
  13. package/dist/dev/ui/shared.d.ts +15 -0
  14. package/dist/errors.d.ts +58 -0
  15. package/{src/events.ts → dist/events.d.ts} +20 -29
  16. package/dist/{lib-HJTRWKU5.mjs → index.cjs} +4687 -10983
  17. package/dist/index.d.ts +303 -1874
  18. package/dist/index.js +317 -45939
  19. package/dist/index.mjs +59748 -27
  20. package/dist/renderers/HtmlRenderer.d.ts +34 -0
  21. package/dist/renderers/MjmlRenderer.d.ts +41 -0
  22. package/dist/renderers/ReactMjmlRenderer.d.ts +38 -0
  23. package/dist/renderers/ReactRenderer.d.ts +46 -0
  24. package/{src/renderers/Renderer.ts → dist/renderers/Renderer.d.ts} +23 -24
  25. package/dist/renderers/TemplateRenderer.d.ts +55 -0
  26. package/dist/renderers/VueMjmlRenderer.d.ts +39 -0
  27. package/dist/renderers/VueRenderer.d.ts +47 -0
  28. package/dist/renderers/mjml-templates.d.ts +11 -0
  29. package/dist/transports/BaseTransport.d.ts +111 -0
  30. package/dist/transports/LogTransport.d.ts +42 -0
  31. package/dist/transports/MemoryTransport.d.ts +51 -0
  32. package/dist/transports/SesTransport.d.ts +79 -0
  33. package/dist/transports/SmtpTransport.d.ts +124 -0
  34. package/dist/transports/Transport.d.ts +44 -0
  35. package/dist/types.d.ts +294 -0
  36. package/{src/utils/html.ts → dist/utils/html.d.ts} +1 -15
  37. package/dist/webhooks/SendGridWebhookDriver.d.ts +45 -0
  38. package/dist/webhooks/SesWebhookDriver.d.ts +23 -0
  39. package/package.json +20 -8
  40. package/CHANGELOG.md +0 -74
  41. package/dist/MjmlRenderer-IUH663FT.mjs +0 -8
  42. package/dist/ReactMjmlRenderer-C3P5YO5L.mjs +0 -8
  43. package/dist/ReactRenderer-24SQ4KRU.mjs +0 -27
  44. package/dist/ReactRenderer-2JFLRVST.mjs +0 -45
  45. package/dist/ReactRenderer-CMCAOEPH.mjs +0 -28
  46. package/dist/ReactRenderer-KYNA4WKE.mjs +0 -28
  47. package/dist/ReactRenderer-LYEOSYFS.mjs +0 -28
  48. package/dist/ReactRenderer-V54CUUEI.mjs +0 -45
  49. package/dist/VueMjmlRenderer-4F4CXHDB.mjs +0 -8
  50. package/dist/VueMjmlRenderer-5WZR4CQG.mjs +0 -8
  51. package/dist/VueMjmlRenderer-U5YMWI44.mjs +0 -8
  52. package/dist/VueRenderer-3YBRQXME.mjs +0 -48
  53. package/dist/VueRenderer-46JGXTJ2.mjs +0 -48
  54. package/dist/VueRenderer-5KWD4R3C.mjs +0 -48
  55. package/dist/VueRenderer-C23U4O5E.mjs +0 -48
  56. package/dist/VueRenderer-DWTCD2RF.mjs +0 -31
  57. package/dist/VueRenderer-IIR5SYTM.mjs +0 -31
  58. package/dist/VueRenderer-LEVDFLHP.mjs +0 -31
  59. package/dist/VueRenderer-RNHSCCRI.mjs +0 -48
  60. package/dist/VueRenderer-SUP66ISX.mjs +0 -29
  61. package/dist/chunk-3WOR3XSL.mjs +0 -82
  62. package/dist/chunk-DBFIVHHG.mjs +0 -79
  63. package/dist/chunk-EBO3CZXG.mjs +0 -15
  64. package/dist/chunk-HEBXNMVQ.mjs +0 -48
  65. package/dist/chunk-KB7IDDBT.mjs +0 -82
  66. package/dist/chunk-LZL5UUPC.mjs +0 -82
  67. package/dist/chunk-W6LXIJKK.mjs +0 -57
  68. package/dist/chunk-XBIVBJS2.mjs +0 -8
  69. package/dist/index.d.mts +0 -2115
  70. package/dist/server-renderer-4IM3P5XZ.mjs +0 -37183
  71. package/dist/server-renderer-4W4FI7YG.mjs +0 -37269
  72. package/dist/server-renderer-7KWFSTPV.mjs +0 -37193
  73. package/dist/server-renderer-S5FPSTJ2.mjs +0 -37183
  74. package/dist/server-renderer-X5LUFVWT.mjs +0 -37193
  75. package/doc/OPTIMIZATION_PLAN.md +0 -496
  76. package/scripts/check-coverage.ts +0 -64
  77. package/src/Mailable.ts +0 -674
  78. package/src/OrbitSignal.ts +0 -451
  79. package/src/dev/DevMailbox.ts +0 -146
  80. package/src/dev/DevServer.ts +0 -192
  81. package/src/dev/storage/FileMailboxStorage.ts +0 -66
  82. package/src/dev/storage/MailboxStorage.ts +0 -15
  83. package/src/dev/storage/MemoryMailboxStorage.ts +0 -36
  84. package/src/dev/ui/mailbox.ts +0 -77
  85. package/src/dev/ui/preview.ts +0 -103
  86. package/src/dev/ui/shared.ts +0 -60
  87. package/src/errors.ts +0 -69
  88. package/src/index.ts +0 -41
  89. package/src/renderers/HtmlRenderer.ts +0 -41
  90. package/src/renderers/MjmlRenderer.ts +0 -73
  91. package/src/renderers/ReactMjmlRenderer.ts +0 -94
  92. package/src/renderers/ReactRenderer.ts +0 -66
  93. package/src/renderers/TemplateRenderer.ts +0 -84
  94. package/src/renderers/VueMjmlRenderer.ts +0 -99
  95. package/src/renderers/VueRenderer.ts +0 -71
  96. package/src/renderers/mjml-templates.ts +0 -50
  97. package/src/transports/BaseTransport.ts +0 -148
  98. package/src/transports/LogTransport.ts +0 -55
  99. package/src/transports/MemoryTransport.ts +0 -55
  100. package/src/transports/SesTransport.ts +0 -129
  101. package/src/transports/SmtpTransport.ts +0 -184
  102. package/src/transports/Transport.ts +0 -45
  103. package/src/types.ts +0 -309
  104. package/src/webhooks/SendGridWebhookDriver.ts +0 -80
  105. package/src/webhooks/SesWebhookDriver.ts +0 -44
  106. package/tests/DevMailbox.test.ts +0 -54
  107. package/tests/FileMailboxStorage.test.ts +0 -56
  108. package/tests/MjmlLayout.test.ts +0 -28
  109. package/tests/MjmlRenderer.test.ts +0 -53
  110. package/tests/OrbitSignalWebhook.test.ts +0 -56
  111. package/tests/ReactMjmlRenderer.test.ts +0 -33
  112. package/tests/SendGridWebhookDriver.test.ts +0 -69
  113. package/tests/SesWebhookDriver.test.ts +0 -46
  114. package/tests/VueMjmlRenderer.test.ts +0 -35
  115. package/tests/dev-server.test.ts +0 -66
  116. package/tests/log-transport.test.ts +0 -21
  117. package/tests/mailable-extra.test.ts +0 -68
  118. package/tests/mailable.test.ts +0 -77
  119. package/tests/orbit-signal.test.ts +0 -43
  120. package/tests/renderers.test.ts +0 -58
  121. package/tests/template-renderer.test.ts +0 -24
  122. package/tests/transports.test.ts +0 -52
  123. package/tests/ui.test.ts +0 -37
  124. package/tsconfig.json +0 -14
@@ -1,148 +0,0 @@
1
- import { MailErrorCode, MailTransportError } from '../errors'
2
- import type { Message, Transport } from '../types'
3
-
4
- /**
5
- * Transport retry configuration options.
6
- *
7
- * Defines the behavior of the automatic retry mechanism, including the number of attempts
8
- * and the timing between them using exponential backoff.
9
- *
10
- * @example
11
- * ```typescript
12
- * const options: TransportOptions = {
13
- * maxRetries: 5,
14
- * retryDelay: 500,
15
- * backoffMultiplier: 3
16
- * };
17
- * ```
18
- *
19
- * @public
20
- */
21
- export interface TransportOptions {
22
- /**
23
- * Maximum number of retry attempts before giving up.
24
- * Set to 0 to disable retries.
25
- */
26
- maxRetries?: number
27
- /**
28
- * Initial delay in milliseconds before the first retry attempt.
29
- */
30
- retryDelay?: number
31
- /**
32
- * Multiplier applied to the delay after each failed attempt.
33
- * Used to implement exponential backoff to avoid overwhelming the service.
34
- */
35
- backoffMultiplier?: number
36
- }
37
-
38
- /**
39
- * Base transport class with automatic retry mechanism.
40
- *
41
- * This abstract class provides a robust foundation for all transport implementations by
42
- * handling transient failures through an exponential backoff retry strategy. It ensures
43
- * that temporary network issues or service rate limits do not immediately fail the email delivery.
44
- *
45
- * The retry mechanism works as follows:
46
- * 1. Attempt to send the message via `doSend()`.
47
- * 2. If it fails, wait for `retryDelay` milliseconds.
48
- * 3. Increment the delay by `backoffMultiplier` for the next attempt.
49
- * 4. Repeat until success or `maxRetries` is reached.
50
- *
51
- * @example
52
- * ```typescript
53
- * class MyTransport extends BaseTransport {
54
- * constructor() {
55
- * super({ maxRetries: 3, retryDelay: 1000 })
56
- * }
57
- *
58
- * protected async doSend(message: Message): Promise<void> {
59
- * // Actual implementation of the sending logic
60
- * // If this throws, BaseTransport will catch and retry
61
- * }
62
- * }
63
- * ```
64
- *
65
- * @public
66
- */
67
- export abstract class BaseTransport implements Transport {
68
- protected options: Required<TransportOptions>
69
-
70
- /**
71
- * Initializes the transport with retry options.
72
- *
73
- * @param options - Configuration for the retry mechanism.
74
- */
75
- constructor(options?: TransportOptions) {
76
- this.options = {
77
- maxRetries: options?.maxRetries ?? 3,
78
- retryDelay: options?.retryDelay ?? 1000,
79
- backoffMultiplier: options?.backoffMultiplier ?? 2,
80
- }
81
- }
82
-
83
- /**
84
- * Orchestrates the message delivery with retry logic.
85
- *
86
- * This method wraps the concrete `doSend` implementation in a retry loop.
87
- * It tracks the last error encountered to provide context if all retries fail.
88
- *
89
- * @param message - The message to be delivered.
90
- * @returns A promise that resolves when the message is successfully sent.
91
- * @throws {MailTransportError} If the message cannot be sent after the maximum number of retries.
92
- *
93
- * @example
94
- * ```typescript
95
- * const transport = new SmtpTransport(config);
96
- * try {
97
- * await transport.send(message);
98
- * } catch (error) {
99
- * console.error('Failed to send email after retries', error);
100
- * }
101
- * ```
102
- */
103
- async send(message: Message): Promise<void> {
104
- let lastError: Error | undefined
105
- let delay = this.options.retryDelay
106
-
107
- for (let attempt = 0; attempt <= this.options.maxRetries; attempt++) {
108
- try {
109
- return await this.doSend(message)
110
- } catch (error) {
111
- lastError = error as Error
112
-
113
- if (attempt < this.options.maxRetries) {
114
- await this.sleep(delay)
115
- delay *= this.options.backoffMultiplier
116
- }
117
- }
118
- }
119
-
120
- throw new MailTransportError(
121
- `Mail sending failed after ${this.options.maxRetries} retries`,
122
- MailErrorCode.UNKNOWN,
123
- lastError
124
- )
125
- }
126
-
127
- /**
128
- * Actual transport implementation to be provided by subclasses.
129
- *
130
- * This method should contain the protocol-specific logic for delivering the message.
131
- * It will be automatically retried by the `send` method if it throws an error.
132
- *
133
- * @param message - The message to send.
134
- * @returns A promise that resolves when the delivery is successful.
135
- * @throws {Error} Any error encountered during delivery, which will trigger a retry.
136
- */
137
- protected abstract doSend(message: Message): Promise<void>
138
-
139
- /**
140
- * Utility method to pause execution for a given duration.
141
- *
142
- * @param ms - Milliseconds to sleep.
143
- * @returns A promise that resolves after the delay.
144
- */
145
- private sleep(ms: number): Promise<void> {
146
- return new Promise((resolve) => setTimeout(resolve, ms))
147
- }
148
- }
@@ -1,55 +0,0 @@
1
- import type { Message, Transport } from '../types'
2
-
3
- /**
4
- * Log transport for development and testing.
5
- *
6
- * This transport outputs email details directly to the console instead of performing
7
- * actual delivery. It is essential for local development to avoid sending real emails
8
- * while still being able to verify the content, recipients, and subject of outgoing mail.
9
- *
10
- * @example
11
- * ```typescript
12
- * import { LogTransport } from '@gravito/signal';
13
- *
14
- * const transport = new LogTransport();
15
- * await transport.send({
16
- * from: { address: 'dev@localhost' },
17
- * to: [{ address: 'user@example.com' }],
18
- * subject: 'Test Email',
19
- * html: '<h1>Hello</h1>'
20
- * });
21
- * ```
22
- *
23
- * @public
24
- */
25
- export class LogTransport implements Transport {
26
- /**
27
- * Outputs the message details to the system console.
28
- *
29
- * Formats the email metadata (From, To, Subject) and content size into a readable
30
- * block in the console output.
31
- *
32
- * @param message - The message to log.
33
- * @returns A promise that resolves immediately after logging.
34
- *
35
- * @example
36
- * ```typescript
37
- * const transport = new LogTransport();
38
- * await transport.send(message);
39
- * // Console: 📧 [OrbitSignal] Email Sent (Simulated)...
40
- * ```
41
- */
42
- async send(message: Message): Promise<void> {
43
- console.log('\n📧 [OrbitSignal] Email Sent (Simulated):')
44
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
45
- console.log(
46
- `From: ${message.from.name ? `${message.from.name} <${message.from.address}>` : message.from.address}`
47
- )
48
- console.log(`To: ${message.to.map((t) => t.address).join(', ')}`)
49
- console.log(`Subject: ${message.subject}`)
50
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
51
- // console.log(message.html); // Too verbose usually
52
- console.log(`[Content Size]: ${message.html.length} chars (HTML)`)
53
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n')
54
- }
55
- }
@@ -1,55 +0,0 @@
1
- import type { DevMailbox } from '../dev/DevMailbox'
2
- import type { Message, Transport } from '../types'
3
-
4
- /**
5
- * Memory transport for development mode.
6
- *
7
- * This transport captures outgoing emails and stores them in an in-memory mailbox.
8
- * It is primarily used by the Gravito Dev UI to provide a live preview of emails
9
- * during development without requiring an external mail server.
10
- *
11
- * @example
12
- * ```typescript
13
- * import { DevMailbox, MemoryTransport } from '@gravito/signal';
14
- *
15
- * const mailbox = new DevMailbox();
16
- * const transport = new MemoryTransport(mailbox);
17
- * await transport.send(message);
18
- *
19
- * console.log(mailbox.getAll().length); // 1
20
- * ```
21
- *
22
- * @public
23
- */
24
- export class MemoryTransport implements Transport {
25
- /**
26
- * Creates a new MemoryTransport instance.
27
- *
28
- * @param mailbox - The in-memory storage where messages will be collected.
29
- */
30
- constructor(private mailbox: DevMailbox) {}
31
-
32
- /**
33
- * Stores the message in the associated mailbox.
34
- *
35
- * The message is added to the internal list of the `DevMailbox` instance,
36
- * making it available for retrieval by the Dev UI or test assertions.
37
- *
38
- * @param message - The message to store.
39
- * @returns A promise that resolves once the message is added to the mailbox.
40
- *
41
- * @example
42
- * ```typescript
43
- * await transport.send({
44
- * from: { address: 'dev@localhost' },
45
- * to: [{ address: 'test@example.com' }],
46
- * subject: 'Memory Test',
47
- * html: '<p>Stored in memory</p>'
48
- * });
49
- * ```
50
- */
51
- async send(message: Message): Promise<void> {
52
- this.mailbox.add(message)
53
- // console.log(`[MemoryTransport] Email stored in DevMailbox for: ${message.to.map(t => t.address).join(', ')}`);
54
- }
55
- }
@@ -1,129 +0,0 @@
1
- import { SESClient, SendRawEmailCommand } from '@aws-sdk/client-ses'
2
- import nodemailer from 'nodemailer'
3
- import type { Address, Message } from '../types'
4
- import { BaseTransport, type TransportOptions } from './BaseTransport'
5
-
6
- /**
7
- * Configuration for AWS SES email transport.
8
- *
9
- * Defines the AWS region and credentials required to communicate with the
10
- * Amazon Simple Email Service API.
11
- *
12
- * @example
13
- * ```typescript
14
- * const config: SesConfig = {
15
- * region: 'us-east-1',
16
- * accessKeyId: 'AKIA...',
17
- * secretAccessKey: 'wJalr...',
18
- * maxRetries: 5
19
- * };
20
- * ```
21
- *
22
- * @public
23
- */
24
- export interface SesConfig extends TransportOptions {
25
- /** AWS region where the SES service is hosted (e.g., 'us-east-1'). */
26
- region: string
27
- /** AWS access key ID. If omitted, the SDK will attempt to use default credential providers. */
28
- accessKeyId?: string
29
- /** AWS secret access key. Required if accessKeyId is provided. */
30
- secretAccessKey?: string
31
- }
32
-
33
- /**
34
- * AWS SES (Simple Email Service) transport with automatic retry.
35
- *
36
- * This transport delivers emails via the Amazon SES API. It requires the
37
- * `@aws-sdk/client-ses` package to be installed as a dependency. It provides
38
- * a reliable way to send high volumes of email using AWS infrastructure and
39
- * includes automatic retry logic for transient API errors.
40
- *
41
- * @example
42
- * ```typescript
43
- * import { SesTransport } from '@gravito/signal';
44
- *
45
- * const transport = new SesTransport({
46
- * region: 'us-west-2'
47
- * });
48
- *
49
- * await transport.send(message);
50
- * ```
51
- *
52
- * @public
53
- */
54
- export class SesTransport extends BaseTransport {
55
- private transporter: nodemailer.Transporter
56
-
57
- /**
58
- * Initializes the SES transport with the provided configuration.
59
- *
60
- * Configures the AWS SES client and wraps it in a nodemailer transporter
61
- * for consistent message handling.
62
- *
63
- * @param config - AWS SES connection and retry configuration.
64
- */
65
- constructor(config: SesConfig) {
66
- super({
67
- maxRetries: config.maxRetries,
68
- retryDelay: config.retryDelay,
69
- backoffMultiplier: config.backoffMultiplier,
70
- })
71
-
72
- const clientConfig: any = { region: config.region }
73
-
74
- if (config.accessKeyId && config.secretAccessKey) {
75
- clientConfig.credentials = {
76
- accessKeyId: config.accessKeyId,
77
- secretAccessKey: config.secretAccessKey,
78
- }
79
- }
80
-
81
- const ses = new SESClient(clientConfig)
82
-
83
- this.transporter = nodemailer.createTransport({
84
- SES: { ses, aws: { SendRawEmailCommand } },
85
- } as any)
86
- }
87
-
88
- /**
89
- * Internal method to perform the actual SES delivery.
90
- *
91
- * Converts the generic `Message` object into a raw email format and sends it
92
- * via the SES `SendRawEmail` API.
93
- *
94
- * @param message - The message to deliver.
95
- * @returns A promise that resolves when SES accepts the message for delivery.
96
- * @throws {Error} If the SES API returns an error or connection fails.
97
- */
98
- protected async doSend(message: Message): Promise<void> {
99
- await this.transporter.sendMail({
100
- from: this.formatAddress(message.from),
101
- to: message.to.map(this.formatAddress),
102
- cc: message.cc?.map(this.formatAddress),
103
- bcc: message.bcc?.map(this.formatAddress),
104
- replyTo: message.replyTo ? this.formatAddress(message.replyTo) : undefined,
105
- subject: message.subject,
106
- html: message.html,
107
- text: message.text,
108
- headers: message.headers,
109
- priority: message.priority,
110
- attachments: message.attachments?.map((a) => ({
111
- filename: a.filename,
112
- content: a.content,
113
- contentType: a.contentType,
114
- cid: a.cid,
115
- encoding: a.encoding,
116
- })),
117
- })
118
- }
119
-
120
- /**
121
- * Formats an Address object into a standard RFC 822 string.
122
- *
123
- * @param addr - The address object to format.
124
- * @returns A string in the format "Name <email@example.com>" or just "email@example.com".
125
- */
126
- private formatAddress(addr: Address): string {
127
- return addr.name ? `"${addr.name}" <${addr.address}>` : addr.address
128
- }
129
- }
@@ -1,184 +0,0 @@
1
- import nodemailer from 'nodemailer'
2
- import type { Address, Message } from '../types'
3
- import { BaseTransport, type TransportOptions } from './BaseTransport'
4
-
5
- /**
6
- * Configuration for SMTP email transport.
7
- *
8
- * Defines the connection parameters, authentication, and pooling settings for
9
- * communicating with an SMTP server.
10
- *
11
- * @example
12
- * ```typescript
13
- * const config: SmtpConfig = {
14
- * host: 'smtp.mailtrap.io',
15
- * port: 2525,
16
- * auth: { user: 'username', pass: 'password' },
17
- * poolSize: 10
18
- * };
19
- * ```
20
- *
21
- * @public
22
- */
23
- export interface SmtpConfig extends TransportOptions {
24
- /** SMTP server hostname or IP address. */
25
- host: string
26
- /** SMTP server port (typically 25, 465, or 587). */
27
- port: number
28
- /** Whether to use a secure TLS/SSL connection. Should be true for port 465. */
29
- secure?: boolean
30
- /** Authentication credentials for the SMTP server. */
31
- auth?: {
32
- /** SMTP username. */
33
- user: string
34
- /** SMTP password. */
35
- pass: string
36
- }
37
- /** TLS specific options for the connection. */
38
- tls?: {
39
- /** Whether to reject unauthorized certificates (useful for self-signed certs). */
40
- rejectUnauthorized?: boolean
41
- /** Specific cipher suite to use for the connection. */
42
- ciphers?: string
43
- }
44
- /** Number of concurrent connections to maintain in the pool. */
45
- poolSize?: number
46
- /** Maximum time in milliseconds a connection can remain idle before being closed. */
47
- maxIdleTime?: number
48
- }
49
-
50
- /**
51
- * SMTP email transport with connection pooling and automatic retry.
52
- *
53
- * This transport uses the standard SMTP protocol to deliver emails. It leverages
54
- * `nodemailer` for robust protocol implementation and includes built-in support
55
- * for connection pooling to improve performance when sending multiple emails.
56
- * It inherits automatic retry logic from `BaseTransport`.
57
- *
58
- * @example
59
- * ```typescript
60
- * import { SmtpTransport } from '@gravito/signal';
61
- *
62
- * const transport = new SmtpTransport({
63
- * host: 'smtp.example.com',
64
- * port: 587,
65
- * auth: { user: 'user', pass: 'pass' }
66
- * });
67
- *
68
- * await transport.send(message);
69
- * ```
70
- *
71
- * @public
72
- */
73
- export class SmtpTransport extends BaseTransport {
74
- private transporter: nodemailer.Transporter
75
-
76
- /**
77
- * Initializes the SMTP transport with the provided configuration.
78
- *
79
- * Sets up the underlying nodemailer transporter with connection pooling enabled.
80
- *
81
- * @param config - SMTP connection and retry configuration.
82
- */
83
- constructor(config: SmtpConfig) {
84
- super({
85
- maxRetries: config.maxRetries,
86
- retryDelay: config.retryDelay,
87
- backoffMultiplier: config.backoffMultiplier,
88
- })
89
-
90
- this.transporter = nodemailer.createTransport({
91
- host: config.host,
92
- port: config.port,
93
- secure: config.secure,
94
- auth: config.auth,
95
- tls: config.tls,
96
- pool: true,
97
- maxConnections: config.poolSize ?? 5,
98
- maxMessages: Infinity,
99
- rateDelta: 1000,
100
- rateLimit: 10,
101
- socketTimeout: config.maxIdleTime ?? 30000,
102
- greetingTimeout: 30000,
103
- })
104
- }
105
-
106
- /**
107
- * Internal method to perform the actual SMTP delivery.
108
- *
109
- * Maps the generic `Message` object to the format expected by nodemailer.
110
- *
111
- * @param message - The message to deliver.
112
- * @returns A promise that resolves when the SMTP server accepts the message.
113
- * @throws {Error} If the SMTP server rejects the message or connection fails.
114
- */
115
- protected async doSend(message: Message): Promise<void> {
116
- await this.transporter.sendMail({
117
- from: this.formatAddress(message.from),
118
- to: message.to.map(this.formatAddress),
119
- cc: message.cc?.map(this.formatAddress),
120
- bcc: message.bcc?.map(this.formatAddress),
121
- replyTo: message.replyTo ? this.formatAddress(message.replyTo) : undefined,
122
- subject: message.subject,
123
- html: message.html,
124
- text: message.text,
125
- headers: message.headers,
126
- priority: message.priority,
127
- attachments: message.attachments?.map((a) => ({
128
- filename: a.filename,
129
- content: a.content,
130
- contentType: a.contentType,
131
- cid: a.cid,
132
- encoding: a.encoding,
133
- })),
134
- })
135
- }
136
-
137
- /**
138
- * Gracefully shuts down the transport and closes all pooled connections.
139
- *
140
- * This should be called during application shutdown to ensure no resources are leaked.
141
- *
142
- * @returns A promise that resolves when all connections are closed.
143
- *
144
- * @example
145
- * ```typescript
146
- * await transport.close();
147
- * ```
148
- */
149
- async close(): Promise<void> {
150
- this.transporter.close()
151
- }
152
-
153
- /**
154
- * Verifies the SMTP connection and authentication.
155
- *
156
- * Useful for health checks or validating configuration during startup.
157
- *
158
- * @returns A promise that resolves to true if the connection is valid, false otherwise.
159
- *
160
- * @example
161
- * ```typescript
162
- * const isValid = await transport.verify();
163
- * if (!isValid) throw new Error('SMTP configuration is invalid');
164
- * ```
165
- */
166
- async verify(): Promise<boolean> {
167
- try {
168
- await this.transporter.verify()
169
- return true
170
- } catch {
171
- return false
172
- }
173
- }
174
-
175
- /**
176
- * Formats an Address object into a standard RFC 822 string.
177
- *
178
- * @param addr - The address object to format.
179
- * @returns A string in the format "Name <email@example.com>" or just "email@example.com".
180
- */
181
- private formatAddress(addr: Address): string {
182
- return addr.name ? `"${addr.name}" <${addr.address}>` : addr.address
183
- }
184
- }
@@ -1,45 +0,0 @@
1
- import type { Message } from '../types'
2
-
3
- /**
4
- * Interface for email transport mechanisms.
5
- *
6
- * Transports are responsible for the final delivery of an email message to its destination,
7
- * whether it's a real SMTP server, a cloud service like AWS SES, or a local log for development.
8
- * This abstraction allows the core mail service to remain agnostic of the delivery method.
9
- *
10
- * @example
11
- * ```typescript
12
- * class CustomTransport implements Transport {
13
- * async send(message: Message): Promise<void> {
14
- * // Implementation logic to deliver the message
15
- * console.log(`Sending email to ${message.to[0].address}`);
16
- * }
17
- * }
18
- * ```
19
- *
20
- * @public
21
- */
22
- export interface Transport {
23
- /**
24
- * Send the given message using the underlying transport mechanism.
25
- *
26
- * This method handles the actual communication with the delivery service.
27
- * Implementations should handle connection management and protocol-specific logic.
28
- *
29
- * @param message - The finalized message object containing recipients, subject, and content.
30
- * @returns A promise that resolves when the message has been successfully handed off to the transport.
31
- * @throws {MailTransportError} If the delivery fails after all internal retry attempts.
32
- *
33
- * @example
34
- * ```typescript
35
- * const transport: Transport = new LogTransport();
36
- * await transport.send({
37
- * from: { address: 'sender@example.com' },
38
- * to: [{ address: 'receiver@example.com' }],
39
- * subject: 'Hello',
40
- * html: '<p>World</p>'
41
- * });
42
- * ```
43
- */
44
- send(message: Message): Promise<void>
45
- }