@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.
- package/README.md +26 -15
- package/dist/Mailable.d.ts +453 -0
- package/dist/OrbitSignal.d.ts +267 -0
- package/{src/Queueable.ts → dist/Queueable.d.ts} +1 -1
- package/{src/TypedMailable.ts → dist/TypedMailable.d.ts} +12 -13
- package/dist/dev/DevMailbox.d.ts +79 -0
- package/dist/dev/DevServer.d.ts +39 -0
- package/dist/dev/storage/FileMailboxStorage.d.ts +16 -0
- package/dist/dev/storage/MailboxStorage.d.ts +14 -0
- package/dist/dev/storage/MemoryMailboxStorage.d.ts +13 -0
- package/dist/dev/ui/mailbox.d.ts +11 -0
- package/dist/dev/ui/preview.d.ts +11 -0
- package/dist/dev/ui/shared.d.ts +15 -0
- package/dist/errors.d.ts +58 -0
- package/{src/events.ts → dist/events.d.ts} +20 -29
- package/dist/{lib-HJTRWKU5.mjs → index.cjs} +4687 -10983
- package/dist/index.d.ts +303 -1874
- package/dist/index.js +317 -45939
- package/dist/index.mjs +59748 -27
- package/dist/renderers/HtmlRenderer.d.ts +34 -0
- package/dist/renderers/MjmlRenderer.d.ts +41 -0
- package/dist/renderers/ReactMjmlRenderer.d.ts +38 -0
- package/dist/renderers/ReactRenderer.d.ts +46 -0
- package/{src/renderers/Renderer.ts → dist/renderers/Renderer.d.ts} +23 -24
- package/dist/renderers/TemplateRenderer.d.ts +55 -0
- package/dist/renderers/VueMjmlRenderer.d.ts +39 -0
- package/dist/renderers/VueRenderer.d.ts +47 -0
- package/dist/renderers/mjml-templates.d.ts +11 -0
- package/dist/transports/BaseTransport.d.ts +111 -0
- package/dist/transports/LogTransport.d.ts +42 -0
- package/dist/transports/MemoryTransport.d.ts +51 -0
- package/dist/transports/SesTransport.d.ts +79 -0
- package/dist/transports/SmtpTransport.d.ts +124 -0
- package/dist/transports/Transport.d.ts +44 -0
- package/dist/types.d.ts +294 -0
- package/{src/utils/html.ts → dist/utils/html.d.ts} +1 -15
- package/dist/webhooks/SendGridWebhookDriver.d.ts +45 -0
- package/dist/webhooks/SesWebhookDriver.d.ts +23 -0
- package/package.json +20 -8
- package/CHANGELOG.md +0 -74
- package/dist/MjmlRenderer-IUH663FT.mjs +0 -8
- package/dist/ReactMjmlRenderer-C3P5YO5L.mjs +0 -8
- package/dist/ReactRenderer-24SQ4KRU.mjs +0 -27
- package/dist/ReactRenderer-2JFLRVST.mjs +0 -45
- package/dist/ReactRenderer-CMCAOEPH.mjs +0 -28
- package/dist/ReactRenderer-KYNA4WKE.mjs +0 -28
- package/dist/ReactRenderer-LYEOSYFS.mjs +0 -28
- package/dist/ReactRenderer-V54CUUEI.mjs +0 -45
- package/dist/VueMjmlRenderer-4F4CXHDB.mjs +0 -8
- package/dist/VueMjmlRenderer-5WZR4CQG.mjs +0 -8
- package/dist/VueMjmlRenderer-U5YMWI44.mjs +0 -8
- package/dist/VueRenderer-3YBRQXME.mjs +0 -48
- package/dist/VueRenderer-46JGXTJ2.mjs +0 -48
- package/dist/VueRenderer-5KWD4R3C.mjs +0 -48
- package/dist/VueRenderer-C23U4O5E.mjs +0 -48
- package/dist/VueRenderer-DWTCD2RF.mjs +0 -31
- package/dist/VueRenderer-IIR5SYTM.mjs +0 -31
- package/dist/VueRenderer-LEVDFLHP.mjs +0 -31
- package/dist/VueRenderer-RNHSCCRI.mjs +0 -48
- package/dist/VueRenderer-SUP66ISX.mjs +0 -29
- package/dist/chunk-3WOR3XSL.mjs +0 -82
- package/dist/chunk-DBFIVHHG.mjs +0 -79
- package/dist/chunk-EBO3CZXG.mjs +0 -15
- package/dist/chunk-HEBXNMVQ.mjs +0 -48
- package/dist/chunk-KB7IDDBT.mjs +0 -82
- package/dist/chunk-LZL5UUPC.mjs +0 -82
- package/dist/chunk-W6LXIJKK.mjs +0 -57
- package/dist/chunk-XBIVBJS2.mjs +0 -8
- package/dist/index.d.mts +0 -2115
- package/dist/server-renderer-4IM3P5XZ.mjs +0 -37183
- package/dist/server-renderer-4W4FI7YG.mjs +0 -37269
- package/dist/server-renderer-7KWFSTPV.mjs +0 -37193
- package/dist/server-renderer-S5FPSTJ2.mjs +0 -37183
- package/dist/server-renderer-X5LUFVWT.mjs +0 -37193
- package/doc/OPTIMIZATION_PLAN.md +0 -496
- package/scripts/check-coverage.ts +0 -64
- package/src/Mailable.ts +0 -674
- package/src/OrbitSignal.ts +0 -451
- package/src/dev/DevMailbox.ts +0 -146
- package/src/dev/DevServer.ts +0 -192
- package/src/dev/storage/FileMailboxStorage.ts +0 -66
- package/src/dev/storage/MailboxStorage.ts +0 -15
- package/src/dev/storage/MemoryMailboxStorage.ts +0 -36
- package/src/dev/ui/mailbox.ts +0 -77
- package/src/dev/ui/preview.ts +0 -103
- package/src/dev/ui/shared.ts +0 -60
- package/src/errors.ts +0 -69
- package/src/index.ts +0 -41
- package/src/renderers/HtmlRenderer.ts +0 -41
- package/src/renderers/MjmlRenderer.ts +0 -73
- package/src/renderers/ReactMjmlRenderer.ts +0 -94
- package/src/renderers/ReactRenderer.ts +0 -66
- package/src/renderers/TemplateRenderer.ts +0 -84
- package/src/renderers/VueMjmlRenderer.ts +0 -99
- package/src/renderers/VueRenderer.ts +0 -71
- package/src/renderers/mjml-templates.ts +0 -50
- package/src/transports/BaseTransport.ts +0 -148
- package/src/transports/LogTransport.ts +0 -55
- package/src/transports/MemoryTransport.ts +0 -55
- package/src/transports/SesTransport.ts +0 -129
- package/src/transports/SmtpTransport.ts +0 -184
- package/src/transports/Transport.ts +0 -45
- package/src/types.ts +0 -309
- package/src/webhooks/SendGridWebhookDriver.ts +0 -80
- package/src/webhooks/SesWebhookDriver.ts +0 -44
- package/tests/DevMailbox.test.ts +0 -54
- package/tests/FileMailboxStorage.test.ts +0 -56
- package/tests/MjmlLayout.test.ts +0 -28
- package/tests/MjmlRenderer.test.ts +0 -53
- package/tests/OrbitSignalWebhook.test.ts +0 -56
- package/tests/ReactMjmlRenderer.test.ts +0 -33
- package/tests/SendGridWebhookDriver.test.ts +0 -69
- package/tests/SesWebhookDriver.test.ts +0 -46
- package/tests/VueMjmlRenderer.test.ts +0 -35
- package/tests/dev-server.test.ts +0 -66
- package/tests/log-transport.test.ts +0 -21
- package/tests/mailable-extra.test.ts +0 -68
- package/tests/mailable.test.ts +0 -77
- package/tests/orbit-signal.test.ts +0 -43
- package/tests/renderers.test.ts +0 -58
- package/tests/template-renderer.test.ts +0 -24
- package/tests/transports.test.ts +0 -52
- package/tests/ui.test.ts +0 -37
- 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
|
-
}
|