@gravito/signal 3.0.3 → 3.0.4
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/CHANGELOG.md +16 -0
- package/README.md +89 -60
- package/README.zh-TW.md +140 -9
- package/dist/MjmlRenderer-IUH663FT.mjs +8 -0
- package/dist/ReactMjmlRenderer-C3P5YO5L.mjs +8 -0
- package/dist/ReactRenderer-2JFLRVST.mjs +45 -0
- package/dist/{ReactRenderer-L5INVYKT.mjs → ReactRenderer-LYEOSYFS.mjs} +9 -8
- package/dist/ReactRenderer-V54CUUEI.mjs +45 -0
- package/dist/VueMjmlRenderer-4F4CXHDB.mjs +8 -0
- package/dist/VueMjmlRenderer-5WZR4CQG.mjs +8 -0
- package/dist/VueMjmlRenderer-U5YMWI44.mjs +8 -0
- package/dist/VueRenderer-3YBRQXME.mjs +48 -0
- package/dist/VueRenderer-46JGXTJ2.mjs +48 -0
- package/dist/VueRenderer-5KWD4R3C.mjs +48 -0
- package/dist/VueRenderer-C23U4O5E.mjs +48 -0
- package/dist/VueRenderer-LEVDFLHP.mjs +31 -0
- package/dist/VueRenderer-RNHSCCRI.mjs +48 -0
- package/dist/chunk-3WOR3XSL.mjs +82 -0
- package/dist/chunk-DBFIVHHG.mjs +79 -0
- package/dist/{chunk-6DZX6EAA.mjs → chunk-HEBXNMVQ.mjs} +12 -1
- package/dist/chunk-KB7IDDBT.mjs +82 -0
- package/dist/chunk-LZL5UUPC.mjs +82 -0
- package/dist/chunk-W6LXIJKK.mjs +57 -0
- package/dist/chunk-XBIVBJS2.mjs +8 -0
- package/dist/index.d.mts +1680 -209
- package/dist/index.d.ts +1680 -209
- package/dist/index.js +69405 -542
- package/dist/index.mjs +993 -110
- package/dist/lib-HJTRWKU5.mjs +67788 -0
- package/dist/{VueRenderer-Z5PRVBNH.mjs → server-renderer-4IM3P5XZ.mjs} +308 -423
- package/dist/server-renderer-7KWFSTPV.mjs +37193 -0
- package/dist/{VueRenderer-S65ZARRI.mjs → server-renderer-S5FPSTJ2.mjs} +931 -877
- package/dist/server-renderer-X5LUFVWT.mjs +37193 -0
- package/doc/OPTIMIZATION_PLAN.md +496 -0
- package/package.json +14 -12
- package/scripts/check-coverage.ts +64 -0
- package/src/Mailable.ts +340 -44
- package/src/OrbitSignal.ts +350 -50
- package/src/TypedMailable.ts +96 -0
- package/src/dev/DevMailbox.ts +89 -33
- package/src/dev/DevServer.ts +14 -14
- package/src/dev/storage/FileMailboxStorage.ts +66 -0
- package/src/dev/storage/MailboxStorage.ts +15 -0
- package/src/dev/storage/MemoryMailboxStorage.ts +36 -0
- package/src/dev/ui/mailbox.ts +1 -1
- package/src/dev/ui/preview.ts +4 -4
- package/src/errors.ts +69 -0
- package/src/events.ts +72 -0
- package/src/index.ts +20 -1
- package/src/renderers/HtmlRenderer.ts +20 -18
- package/src/renderers/MjmlRenderer.ts +73 -0
- package/src/renderers/ReactMjmlRenderer.ts +94 -0
- package/src/renderers/ReactRenderer.ts +26 -21
- package/src/renderers/Renderer.ts +43 -3
- package/src/renderers/TemplateRenderer.ts +48 -15
- package/src/renderers/VueMjmlRenderer.ts +99 -0
- package/src/renderers/VueRenderer.ts +26 -21
- package/src/renderers/mjml-templates.ts +50 -0
- package/src/transports/BaseTransport.ts +148 -0
- package/src/transports/LogTransport.ts +28 -6
- package/src/transports/MemoryTransport.ts +34 -6
- package/src/transports/SesTransport.ts +62 -17
- package/src/transports/SmtpTransport.ts +123 -27
- package/src/transports/Transport.ts +33 -4
- package/src/types.ts +172 -3
- package/src/utils/html.ts +43 -0
- package/src/webhooks/SendGridWebhookDriver.ts +80 -0
- package/src/webhooks/SesWebhookDriver.ts +44 -0
- package/tests/DevMailbox.test.ts +54 -0
- package/tests/FileMailboxStorage.test.ts +56 -0
- package/tests/MjmlLayout.test.ts +28 -0
- package/tests/MjmlRenderer.test.ts +53 -0
- package/tests/OrbitSignalWebhook.test.ts +56 -0
- package/tests/ReactMjmlRenderer.test.ts +33 -0
- package/tests/SendGridWebhookDriver.test.ts +69 -0
- package/tests/SesWebhookDriver.test.ts +46 -0
- package/tests/VueMjmlRenderer.test.ts +35 -0
- package/tests/dev-server.test.ts +1 -1
- package/tests/transports.test.ts +3 -3
- package/tsconfig.json +12 -24
- package/dist/OrbitMail-2Z7ZTKYA.mjs +0 -7
- package/dist/OrbitMail-BGV32HWN.mjs +0 -7
- package/dist/OrbitMail-FUYZQSAV.mjs +0 -7
- package/dist/OrbitMail-NAPCRK7B.mjs +0 -7
- package/dist/OrbitMail-REGJ276B.mjs +0 -7
- package/dist/OrbitMail-TCFBJWDT.mjs +0 -7
- package/dist/OrbitMail-XZZW6U4N.mjs +0 -7
- package/dist/OrbitSignal-IPSA2CDO.mjs +0 -7
- package/dist/OrbitSignal-MABW4DDW.mjs +0 -7
- package/dist/OrbitSignal-QSW5VQ5M.mjs +0 -7
- package/dist/OrbitSignal-R22QHWAA.mjs +0 -7
- package/dist/OrbitSignal-ZKKMEC27.mjs +0 -7
- package/dist/chunk-3U2CYJO5.mjs +0 -367
- package/dist/chunk-3XFC4T6M.mjs +0 -392
- package/dist/chunk-456QRYFW.mjs +0 -401
- package/dist/chunk-DT3R2TNV.mjs +0 -367
- package/dist/chunk-F6MVTUCT.mjs +0 -421
- package/dist/chunk-GADWIVC4.mjs +0 -400
- package/dist/chunk-HHKFAMSE.mjs +0 -380
- package/dist/chunk-NEQCQSZI.mjs +0 -406
- package/dist/chunk-OKRNL6PN.mjs +0 -400
- package/dist/chunk-ULN3GMY2.mjs +0 -367
- package/dist/chunk-XAWO7RSP.mjs +0 -398
- package/dist/chunk-YLVDJSED.mjs +0 -431
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,69 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
MjmlRenderer
|
|
3
|
+
} from "./chunk-W6LXIJKK.mjs";
|
|
4
|
+
import {
|
|
5
|
+
ReactMjmlRenderer
|
|
6
|
+
} from "./chunk-DBFIVHHG.mjs";
|
|
7
|
+
import {
|
|
8
|
+
VueMjmlRenderer
|
|
9
|
+
} from "./chunk-KB7IDDBT.mjs";
|
|
10
|
+
import {
|
|
11
|
+
stripHtml
|
|
12
|
+
} from "./chunk-XBIVBJS2.mjs";
|
|
13
|
+
import "./chunk-HEBXNMVQ.mjs";
|
|
2
14
|
|
|
3
15
|
// src/dev/DevMailbox.ts
|
|
4
16
|
import { randomUUID } from "crypto";
|
|
5
|
-
|
|
17
|
+
|
|
18
|
+
// src/dev/storage/MemoryMailboxStorage.ts
|
|
19
|
+
var MemoryMailboxStorage = class {
|
|
6
20
|
entries = [];
|
|
7
|
-
|
|
8
|
-
|
|
21
|
+
async all() {
|
|
22
|
+
return [...this.entries];
|
|
23
|
+
}
|
|
24
|
+
async push(entry) {
|
|
25
|
+
this.entries.unshift(entry);
|
|
26
|
+
}
|
|
27
|
+
async trim(max) {
|
|
28
|
+
if (this.entries.length > max) {
|
|
29
|
+
this.entries = this.entries.slice(0, max);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async clear() {
|
|
33
|
+
this.entries = [];
|
|
34
|
+
}
|
|
35
|
+
async delete(id) {
|
|
36
|
+
const index = this.entries.findIndex((e) => e.id === id);
|
|
37
|
+
if (index !== -1) {
|
|
38
|
+
this.entries.splice(index, 1);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// src/dev/DevMailbox.ts
|
|
46
|
+
var DevMailbox = class {
|
|
47
|
+
storage;
|
|
48
|
+
_maxEntries = 50;
|
|
49
|
+
/**
|
|
50
|
+
* Creates an instance of DevMailbox.
|
|
51
|
+
*
|
|
52
|
+
* @param maxEntries - Maximum number of emails to store (default: 50)
|
|
53
|
+
* @param storage - Optional custom storage engine (defaults to Memory)
|
|
54
|
+
*/
|
|
55
|
+
constructor(maxEntries, storage) {
|
|
56
|
+
if (maxEntries !== void 0) {
|
|
57
|
+
this._maxEntries = maxEntries;
|
|
58
|
+
}
|
|
59
|
+
this.storage = storage ?? new MemoryMailboxStorage();
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Adds a new message to the mailbox.
|
|
63
|
+
*
|
|
64
|
+
* If the mailbox exceeds the maximum capacity, the oldest messages are removed.
|
|
65
|
+
*/
|
|
66
|
+
async add(message) {
|
|
9
67
|
const entry = {
|
|
10
68
|
id: randomUUID(),
|
|
11
69
|
envelope: {
|
|
@@ -22,66 +80,176 @@ var DevMailbox = class {
|
|
|
22
80
|
...message.text ? { text: message.text } : {},
|
|
23
81
|
sentAt: /* @__PURE__ */ new Date()
|
|
24
82
|
};
|
|
25
|
-
this.
|
|
26
|
-
|
|
27
|
-
this.entries = this.entries.slice(0, this.maxEntries);
|
|
28
|
-
}
|
|
83
|
+
await this.storage.push(entry);
|
|
84
|
+
await this.storage.trim(this._maxEntries);
|
|
29
85
|
return entry;
|
|
30
86
|
}
|
|
31
|
-
|
|
32
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Sets the maximum number of emails to store.
|
|
89
|
+
*
|
|
90
|
+
* If the current mailbox exceeds the new capacity, the oldest messages are removed.
|
|
91
|
+
*/
|
|
92
|
+
async setMaxEntries(count) {
|
|
93
|
+
this._maxEntries = count;
|
|
94
|
+
await this.storage.trim(this._maxEntries);
|
|
33
95
|
}
|
|
34
|
-
|
|
35
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Returns the maximum capacity of the mailbox.
|
|
98
|
+
*/
|
|
99
|
+
get maxEntries() {
|
|
100
|
+
return this._maxEntries;
|
|
36
101
|
}
|
|
37
|
-
|
|
38
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Lists all messages in the mailbox.
|
|
104
|
+
*/
|
|
105
|
+
async list() {
|
|
106
|
+
return await this.storage.all();
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Retrieves a specific message by ID.
|
|
110
|
+
*/
|
|
111
|
+
async get(id) {
|
|
112
|
+
const entries = await this.storage.all();
|
|
113
|
+
return entries.find((e) => e.id === id);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Deletes a specific message by ID.
|
|
117
|
+
*/
|
|
118
|
+
async delete(id) {
|
|
119
|
+
if (this.storage.delete) {
|
|
120
|
+
return await this.storage.delete(id);
|
|
121
|
+
}
|
|
122
|
+
const entries = await this.storage.all();
|
|
123
|
+
const index = entries.findIndex((e) => e.id === id);
|
|
39
124
|
if (index !== -1) {
|
|
40
|
-
this.
|
|
125
|
+
await this.clear();
|
|
126
|
+
entries.splice(index, 1);
|
|
127
|
+
for (const entry of entries.reverse()) {
|
|
128
|
+
await this.storage.push(entry);
|
|
129
|
+
}
|
|
41
130
|
return true;
|
|
42
131
|
}
|
|
43
132
|
return false;
|
|
44
133
|
}
|
|
45
|
-
|
|
46
|
-
|
|
134
|
+
/**
|
|
135
|
+
* Clears all messages from the mailbox.
|
|
136
|
+
*/
|
|
137
|
+
async clear() {
|
|
138
|
+
await this.storage.clear();
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// src/errors.ts
|
|
143
|
+
var MailErrorCode = /* @__PURE__ */ ((MailErrorCode2) => {
|
|
144
|
+
MailErrorCode2["CONNECTION_FAILED"] = "CONNECTION_FAILED";
|
|
145
|
+
MailErrorCode2["AUTH_FAILED"] = "AUTH_FAILED";
|
|
146
|
+
MailErrorCode2["RECIPIENT_REJECTED"] = "RECIPIENT_REJECTED";
|
|
147
|
+
MailErrorCode2["MESSAGE_REJECTED"] = "MESSAGE_REJECTED";
|
|
148
|
+
MailErrorCode2["RATE_LIMIT"] = "RATE_LIMIT";
|
|
149
|
+
MailErrorCode2["UNKNOWN"] = "UNKNOWN";
|
|
150
|
+
return MailErrorCode2;
|
|
151
|
+
})(MailErrorCode || {});
|
|
152
|
+
var MailTransportError = class _MailTransportError extends Error {
|
|
153
|
+
/**
|
|
154
|
+
* Create a new mail transport error.
|
|
155
|
+
*
|
|
156
|
+
* @param message - Human-readable error message
|
|
157
|
+
* @param code - Categorized error code
|
|
158
|
+
* @param cause - Original error that caused this failure
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```typescript
|
|
162
|
+
* const error = new MailTransportError('Auth failed', MailErrorCode.AUTH_FAILED);
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
constructor(message, code, cause) {
|
|
166
|
+
super(message);
|
|
167
|
+
this.code = code;
|
|
168
|
+
this.cause = cause;
|
|
169
|
+
this.name = "MailTransportError";
|
|
170
|
+
if (Error.captureStackTrace) {
|
|
171
|
+
Error.captureStackTrace(this, _MailTransportError);
|
|
172
|
+
}
|
|
47
173
|
}
|
|
48
174
|
};
|
|
49
175
|
|
|
50
176
|
// src/renderers/HtmlRenderer.ts
|
|
51
177
|
var HtmlRenderer = class {
|
|
178
|
+
/**
|
|
179
|
+
* Creates an instance of HtmlRenderer.
|
|
180
|
+
*
|
|
181
|
+
* @param content - The raw HTML string to be rendered.
|
|
182
|
+
*/
|
|
52
183
|
constructor(content) {
|
|
53
184
|
this.content = content;
|
|
54
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Returns the original HTML and a stripped plain text version.
|
|
188
|
+
*
|
|
189
|
+
* @returns A promise resolving to the rendered content.
|
|
190
|
+
*/
|
|
55
191
|
async render() {
|
|
56
192
|
return {
|
|
57
193
|
html: this.content,
|
|
58
|
-
text:
|
|
194
|
+
text: stripHtml(this.content)
|
|
59
195
|
};
|
|
60
196
|
}
|
|
61
|
-
stripHtml(html) {
|
|
62
|
-
return html.replace(/<style(?:\s[^>]*)?>[\s\S]*?<\/style>/gi, "").replace(/<script(?:\s[^>]*)?>[\s\S]*?<\/script>/gi, "").replace(/<[^>]+>/g, "").replace(/ /g, " ").replace(/\s+/g, " ").trim();
|
|
63
|
-
}
|
|
64
197
|
};
|
|
65
198
|
|
|
66
199
|
// src/renderers/TemplateRenderer.ts
|
|
67
|
-
var TemplateRenderer = class {
|
|
200
|
+
var TemplateRenderer = class _TemplateRenderer {
|
|
68
201
|
template;
|
|
69
202
|
viewsDir;
|
|
203
|
+
static engineCache = /* @__PURE__ */ new Map();
|
|
204
|
+
/**
|
|
205
|
+
* Creates an instance of TemplateRenderer.
|
|
206
|
+
*
|
|
207
|
+
* @param templateName - The name of the template file (without extension).
|
|
208
|
+
* @param viewsDir - The directory containing template files. Defaults to `src/emails`.
|
|
209
|
+
*/
|
|
70
210
|
constructor(templateName, viewsDir) {
|
|
71
211
|
this.template = templateName;
|
|
72
212
|
this.viewsDir = viewsDir || `${process.cwd()}/src/emails`;
|
|
73
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Renders the template with the provided data.
|
|
216
|
+
*
|
|
217
|
+
* This method lazily loads `@gravito/prism` to ensure the core package
|
|
218
|
+
* remains lightweight for users who don't need template rendering.
|
|
219
|
+
*
|
|
220
|
+
* @param data - The data context for template interpolation.
|
|
221
|
+
* @returns A promise resolving to the rendered content.
|
|
222
|
+
* @throws {Error} If the template engine fails to load or rendering fails.
|
|
223
|
+
*/
|
|
74
224
|
async render(data) {
|
|
75
225
|
const { TemplateEngine } = await import("@gravito/prism");
|
|
76
|
-
const
|
|
226
|
+
const cached = _TemplateRenderer.engineCache.get(this.viewsDir);
|
|
227
|
+
const engine = cached || new TemplateEngine(this.viewsDir);
|
|
228
|
+
if (!cached) {
|
|
229
|
+
_TemplateRenderer.engineCache.set(this.viewsDir, engine);
|
|
230
|
+
}
|
|
77
231
|
const html = engine.render(this.template, data, {});
|
|
78
232
|
return {
|
|
79
233
|
html,
|
|
80
|
-
text:
|
|
234
|
+
text: stripHtml(html)
|
|
81
235
|
};
|
|
82
236
|
}
|
|
83
|
-
|
|
84
|
-
|
|
237
|
+
/**
|
|
238
|
+
* Clear template engine cache.
|
|
239
|
+
*
|
|
240
|
+
* Useful in development environments to force recompilation of templates
|
|
241
|
+
* after they have been modified on disk.
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* ```typescript
|
|
245
|
+
* TemplateRenderer.clearCache();
|
|
246
|
+
* ```
|
|
247
|
+
*
|
|
248
|
+
* @public
|
|
249
|
+
* @since 3.1.0
|
|
250
|
+
*/
|
|
251
|
+
static clearCache() {
|
|
252
|
+
_TemplateRenderer.engineCache.clear();
|
|
85
253
|
}
|
|
86
254
|
};
|
|
87
255
|
|
|
@@ -96,7 +264,17 @@ var Mailable = class {
|
|
|
96
264
|
/**
|
|
97
265
|
* Set the sender address for the email.
|
|
98
266
|
*
|
|
99
|
-
*
|
|
267
|
+
* This defines the "From" field in the email envelope. If not called, the default
|
|
268
|
+
* sender from the mail configuration will be used.
|
|
269
|
+
*
|
|
270
|
+
* @param address - The email address or address object containing name and address.
|
|
271
|
+
* @returns The current mailable instance for chaining.
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* ```typescript
|
|
275
|
+
* mailable.from('admin@example.com')
|
|
276
|
+
* mailable.from({ name: 'Support', address: 'support@example.com' })
|
|
277
|
+
* ```
|
|
100
278
|
*/
|
|
101
279
|
from(address) {
|
|
102
280
|
this.envelope.from = typeof address === "string" ? { address } : address;
|
|
@@ -105,7 +283,17 @@ var Mailable = class {
|
|
|
105
283
|
/**
|
|
106
284
|
* Set the primary recipient(s) for the email.
|
|
107
285
|
*
|
|
108
|
-
*
|
|
286
|
+
* Configures the "To" field. Supports single or multiple recipients in various formats.
|
|
287
|
+
*
|
|
288
|
+
* @param address - A single email string, an address object, or an array of either.
|
|
289
|
+
* @returns The current mailable instance for chaining.
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* ```typescript
|
|
293
|
+
* mailable.to('user@example.com')
|
|
294
|
+
* mailable.to(['a@example.com', 'b@example.com'])
|
|
295
|
+
* mailable.to({ name: 'John', address: 'john@example.com' })
|
|
296
|
+
* ```
|
|
109
297
|
*/
|
|
110
298
|
to(address) {
|
|
111
299
|
this.envelope.to = this.normalizeAddressArray(address);
|
|
@@ -114,7 +302,15 @@ var Mailable = class {
|
|
|
114
302
|
/**
|
|
115
303
|
* Set the carbon copy (CC) recipient(s).
|
|
116
304
|
*
|
|
117
|
-
*
|
|
305
|
+
* Adds recipients to the "Cc" field of the email.
|
|
306
|
+
*
|
|
307
|
+
* @param address - A single email string, an address object, or an array of either.
|
|
308
|
+
* @returns The current mailable instance for chaining.
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
* ```typescript
|
|
312
|
+
* mailable.cc('manager@example.com')
|
|
313
|
+
* ```
|
|
118
314
|
*/
|
|
119
315
|
cc(address) {
|
|
120
316
|
this.envelope.cc = this.normalizeAddressArray(address);
|
|
@@ -123,7 +319,15 @@ var Mailable = class {
|
|
|
123
319
|
/**
|
|
124
320
|
* Set the blind carbon copy (BCC) recipient(s).
|
|
125
321
|
*
|
|
126
|
-
*
|
|
322
|
+
* Adds recipients to the "Bcc" field. These recipients are hidden from others.
|
|
323
|
+
*
|
|
324
|
+
* @param address - A single email string, an address object, or an array of either.
|
|
325
|
+
* @returns The current mailable instance for chaining.
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* ```typescript
|
|
329
|
+
* mailable.bcc('audit@example.com')
|
|
330
|
+
* ```
|
|
127
331
|
*/
|
|
128
332
|
bcc(address) {
|
|
129
333
|
this.envelope.bcc = this.normalizeAddressArray(address);
|
|
@@ -132,7 +336,15 @@ var Mailable = class {
|
|
|
132
336
|
/**
|
|
133
337
|
* Set the reply-to address.
|
|
134
338
|
*
|
|
135
|
-
*
|
|
339
|
+
* Specifies where replies to this email should be directed.
|
|
340
|
+
*
|
|
341
|
+
* @param address - The email address or address object for replies.
|
|
342
|
+
* @returns The current mailable instance for chaining.
|
|
343
|
+
*
|
|
344
|
+
* @example
|
|
345
|
+
* ```typescript
|
|
346
|
+
* mailable.replyTo('no-reply@example.com')
|
|
347
|
+
* ```
|
|
136
348
|
*/
|
|
137
349
|
replyTo(address) {
|
|
138
350
|
this.envelope.replyTo = typeof address === "string" ? { address } : address;
|
|
@@ -141,7 +353,15 @@ var Mailable = class {
|
|
|
141
353
|
/**
|
|
142
354
|
* Set the subject line for the email.
|
|
143
355
|
*
|
|
144
|
-
*
|
|
356
|
+
* Defines the text that appears in the recipient's inbox subject field.
|
|
357
|
+
*
|
|
358
|
+
* @param subject - The subject text.
|
|
359
|
+
* @returns The current mailable instance for chaining.
|
|
360
|
+
*
|
|
361
|
+
* @example
|
|
362
|
+
* ```typescript
|
|
363
|
+
* mailable.subject('Your Order Confirmation')
|
|
364
|
+
* ```
|
|
145
365
|
*/
|
|
146
366
|
subject(subject) {
|
|
147
367
|
this.envelope.subject = subject;
|
|
@@ -150,7 +370,15 @@ var Mailable = class {
|
|
|
150
370
|
/**
|
|
151
371
|
* Set the email priority.
|
|
152
372
|
*
|
|
153
|
-
*
|
|
373
|
+
* Hints to the email client how urgent this message is.
|
|
374
|
+
*
|
|
375
|
+
* @param level - The priority level: 'high', 'normal', or 'low'.
|
|
376
|
+
* @returns The current mailable instance for chaining.
|
|
377
|
+
*
|
|
378
|
+
* @example
|
|
379
|
+
* ```typescript
|
|
380
|
+
* mailable.emailPriority('high')
|
|
381
|
+
* ```
|
|
154
382
|
*/
|
|
155
383
|
emailPriority(level) {
|
|
156
384
|
this.envelope.priority = level;
|
|
@@ -159,7 +387,18 @@ var Mailable = class {
|
|
|
159
387
|
/**
|
|
160
388
|
* Attach a file to the email.
|
|
161
389
|
*
|
|
162
|
-
*
|
|
390
|
+
* Adds a file attachment to the message. Can be called multiple times for multiple files.
|
|
391
|
+
*
|
|
392
|
+
* @param attachment - The attachment configuration including path, content, or filename.
|
|
393
|
+
* @returns The current mailable instance for chaining.
|
|
394
|
+
*
|
|
395
|
+
* @example
|
|
396
|
+
* ```typescript
|
|
397
|
+
* mailable.attach({
|
|
398
|
+
* filename: 'invoice.pdf',
|
|
399
|
+
* path: './storage/invoices/123.pdf'
|
|
400
|
+
* })
|
|
401
|
+
* ```
|
|
163
402
|
*/
|
|
164
403
|
attach(attachment) {
|
|
165
404
|
this.envelope.attachments = this.envelope.attachments || [];
|
|
@@ -168,9 +407,17 @@ var Mailable = class {
|
|
|
168
407
|
}
|
|
169
408
|
// ===== Content Methods (Renderer Selection) =====
|
|
170
409
|
/**
|
|
171
|
-
* Set the content using raw HTML string.
|
|
410
|
+
* Set the content using a raw HTML string.
|
|
411
|
+
*
|
|
412
|
+
* Use this for simple emails where a full template engine is not required.
|
|
172
413
|
*
|
|
173
|
-
* @param content - The HTML content
|
|
414
|
+
* @param content - The raw HTML content.
|
|
415
|
+
* @returns The current mailable instance for chaining.
|
|
416
|
+
*
|
|
417
|
+
* @example
|
|
418
|
+
* ```typescript
|
|
419
|
+
* mailable.html('<h1>Hello</h1><p>Welcome to our platform.</p>')
|
|
420
|
+
* ```
|
|
174
421
|
*/
|
|
175
422
|
html(content) {
|
|
176
423
|
this.renderer = new HtmlRenderer(content);
|
|
@@ -179,8 +426,17 @@ var Mailable = class {
|
|
|
179
426
|
/**
|
|
180
427
|
* Set the content using an OrbitPrism template.
|
|
181
428
|
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
429
|
+
* Renders a template file with the provided data. This is the recommended way
|
|
430
|
+
* to build complex, data-driven emails.
|
|
431
|
+
*
|
|
432
|
+
* @param template - The template name or path relative to the configured views directory.
|
|
433
|
+
* @param data - The data object to be injected into the template.
|
|
434
|
+
* @returns The current mailable instance for chaining.
|
|
435
|
+
*
|
|
436
|
+
* @example
|
|
437
|
+
* ```typescript
|
|
438
|
+
* mailable.view('emails.welcome', { name: 'Alice' })
|
|
439
|
+
* ```
|
|
184
440
|
*/
|
|
185
441
|
view(template, data) {
|
|
186
442
|
this.renderer = new TemplateRenderer(template, void 0);
|
|
@@ -189,34 +445,105 @@ var Mailable = class {
|
|
|
189
445
|
}
|
|
190
446
|
/**
|
|
191
447
|
* Set the content using a React component.
|
|
192
|
-
* Dynamically imports ReactRenderer to avoid hard dependency errors if React is not installed.
|
|
193
448
|
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
449
|
+
* Leverages React's component model for email design. The renderer is loaded
|
|
450
|
+
* dynamically to keep the core package lightweight.
|
|
451
|
+
*
|
|
452
|
+
* @param component - The React component class or function.
|
|
453
|
+
* @param props - The properties to pass to the component.
|
|
454
|
+
* @param deps - Optional React/ReactDOMServer overrides for custom environments.
|
|
455
|
+
* @returns The current mailable instance for chaining.
|
|
456
|
+
*
|
|
457
|
+
* @example
|
|
458
|
+
* ```typescript
|
|
459
|
+
* mailable.react(WelcomeEmailComponent, { name: 'Alice' })
|
|
460
|
+
* ```
|
|
197
461
|
*/
|
|
198
462
|
react(component, props, deps) {
|
|
199
463
|
this.rendererResolver = async () => {
|
|
200
|
-
const { ReactRenderer } = await import("./ReactRenderer-
|
|
464
|
+
const { ReactRenderer } = await import("./ReactRenderer-V54CUUEI.mjs");
|
|
201
465
|
return new ReactRenderer(component, props, deps);
|
|
202
466
|
};
|
|
203
467
|
return this;
|
|
204
468
|
}
|
|
205
469
|
/**
|
|
206
470
|
* Set the content using a Vue component.
|
|
207
|
-
* Dynamically imports VueRenderer to avoid hard dependency errors if Vue is not installed.
|
|
208
471
|
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
472
|
+
* Leverages Vue's component model for email design. The renderer is loaded
|
|
473
|
+
* dynamically to keep the core package lightweight.
|
|
474
|
+
*
|
|
475
|
+
* @param component - The Vue component object.
|
|
476
|
+
* @param props - The properties to pass to the component.
|
|
477
|
+
* @param deps - Optional Vue/VueServerRenderer overrides for custom environments.
|
|
478
|
+
* @returns The current mailable instance for chaining.
|
|
479
|
+
*
|
|
480
|
+
* @example
|
|
481
|
+
* ```typescript
|
|
482
|
+
* mailable.vue(WelcomeEmailComponent, { name: 'Alice' })
|
|
483
|
+
* ```
|
|
212
484
|
*/
|
|
213
485
|
vue(component, props, deps) {
|
|
214
486
|
this.rendererResolver = async () => {
|
|
215
|
-
const { VueRenderer } = await import("./VueRenderer-
|
|
487
|
+
const { VueRenderer } = await import("./VueRenderer-3YBRQXME.mjs");
|
|
216
488
|
return new VueRenderer(component, props, deps);
|
|
217
489
|
};
|
|
218
490
|
return this;
|
|
219
491
|
}
|
|
492
|
+
/**
|
|
493
|
+
* Set the content using an MJML markup string.
|
|
494
|
+
*
|
|
495
|
+
* MJML ensures responsive email compatibility across various clients.
|
|
496
|
+
*
|
|
497
|
+
* @param content - The MJML markup string or inner content.
|
|
498
|
+
* @param options - MJML transformation options.
|
|
499
|
+
* @param options.layout - Optional full MJML layout string. Use '{{content}}' as placeholder.
|
|
500
|
+
* @returns The current mailable instance for chaining.
|
|
501
|
+
*
|
|
502
|
+
* @example
|
|
503
|
+
* ```typescript
|
|
504
|
+
* mailable.mjml('<mj-text>Hello</mj-text>', {
|
|
505
|
+
* layout: '<mjml><mj-body>{{content}}</mj-body></mjml>'
|
|
506
|
+
* })
|
|
507
|
+
* ```
|
|
508
|
+
*/
|
|
509
|
+
mjml(content, options) {
|
|
510
|
+
const finalContent = options?.layout?.includes("{{content}}") ? options.layout.replace("{{content}}", content) : content;
|
|
511
|
+
this.rendererResolver = async () => {
|
|
512
|
+
const { MjmlRenderer: MjmlRenderer2 } = await import("./MjmlRenderer-IUH663FT.mjs");
|
|
513
|
+
return new MjmlRenderer2(finalContent, options);
|
|
514
|
+
};
|
|
515
|
+
return this;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Set the content using a React component that outputs MJML.
|
|
519
|
+
*
|
|
520
|
+
* @param component - The React component.
|
|
521
|
+
* @param props - Component properties.
|
|
522
|
+
* @param options - MJML options.
|
|
523
|
+
* @returns The current mailable instance for chaining.
|
|
524
|
+
*/
|
|
525
|
+
mjmlReact(component, props, options) {
|
|
526
|
+
this.rendererResolver = async () => {
|
|
527
|
+
const { ReactMjmlRenderer: ReactMjmlRenderer2 } = await import("./ReactMjmlRenderer-C3P5YO5L.mjs");
|
|
528
|
+
return new ReactMjmlRenderer2(component, props, options);
|
|
529
|
+
};
|
|
530
|
+
return this;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Set the content using a Vue component that outputs MJML.
|
|
534
|
+
*
|
|
535
|
+
* @param component - The Vue component.
|
|
536
|
+
* @param props - Component properties.
|
|
537
|
+
* @param options - MJML options.
|
|
538
|
+
* @returns The current mailable instance for chaining.
|
|
539
|
+
*/
|
|
540
|
+
mjmlVue(component, props, options) {
|
|
541
|
+
this.rendererResolver = async () => {
|
|
542
|
+
const { VueMjmlRenderer: VueMjmlRenderer2 } = await import("./VueMjmlRenderer-4F4CXHDB.mjs");
|
|
543
|
+
return new VueMjmlRenderer2(component, props, options);
|
|
544
|
+
};
|
|
545
|
+
return this;
|
|
546
|
+
}
|
|
220
547
|
// ===== Queueable Implementation =====
|
|
221
548
|
/** The name of the queue to push this mailable to. */
|
|
222
549
|
queueName;
|
|
@@ -227,36 +554,81 @@ var Mailable = class {
|
|
|
227
554
|
/** Priority of the message in the queue. */
|
|
228
555
|
priority;
|
|
229
556
|
/**
|
|
230
|
-
* Set the queue
|
|
557
|
+
* Set the target queue for background processing.
|
|
558
|
+
*
|
|
559
|
+
* @param queue - The name of the queue.
|
|
560
|
+
* @returns The current mailable instance for chaining.
|
|
561
|
+
*
|
|
562
|
+
* @example
|
|
563
|
+
* ```typescript
|
|
564
|
+
* mailable.onQueue('notifications')
|
|
565
|
+
* ```
|
|
231
566
|
*/
|
|
232
567
|
onQueue(queue) {
|
|
233
568
|
this.queueName = queue;
|
|
234
569
|
return this;
|
|
235
570
|
}
|
|
236
571
|
/**
|
|
237
|
-
* Set the connection
|
|
572
|
+
* Set the queue connection to be used.
|
|
573
|
+
*
|
|
574
|
+
* @param connection - The name of the connection (e.g., 'redis', 'sqs').
|
|
575
|
+
* @returns The current mailable instance for chaining.
|
|
576
|
+
*
|
|
577
|
+
* @example
|
|
578
|
+
* ```typescript
|
|
579
|
+
* mailable.onConnection('redis')
|
|
580
|
+
* ```
|
|
238
581
|
*/
|
|
239
582
|
onConnection(connection) {
|
|
240
583
|
this.connectionName = connection;
|
|
241
584
|
return this;
|
|
242
585
|
}
|
|
243
586
|
/**
|
|
244
|
-
* Set a delay for the queued
|
|
587
|
+
* Set a delay for the queued message.
|
|
588
|
+
*
|
|
589
|
+
* The message will remain in the queue and only be processed after the delay.
|
|
590
|
+
*
|
|
591
|
+
* @param seconds - The delay in seconds.
|
|
592
|
+
* @returns The current mailable instance for chaining.
|
|
593
|
+
*
|
|
594
|
+
* @example
|
|
595
|
+
* ```typescript
|
|
596
|
+
* mailable.delay(3600) // Delay for 1 hour
|
|
597
|
+
* ```
|
|
245
598
|
*/
|
|
246
599
|
delay(seconds) {
|
|
247
600
|
this.delaySeconds = seconds;
|
|
248
601
|
return this;
|
|
249
602
|
}
|
|
250
603
|
/**
|
|
251
|
-
* Set the priority for the queued
|
|
604
|
+
* Set the priority for the queued message.
|
|
605
|
+
*
|
|
606
|
+
* Higher priority messages are typically processed before lower priority ones.
|
|
607
|
+
*
|
|
608
|
+
* @param priority - The priority value (numeric or string).
|
|
609
|
+
* @returns The current mailable instance for chaining.
|
|
610
|
+
*
|
|
611
|
+
* @example
|
|
612
|
+
* ```typescript
|
|
613
|
+
* mailable.withPriority(10)
|
|
614
|
+
* ```
|
|
252
615
|
*/
|
|
253
616
|
withPriority(priority) {
|
|
254
617
|
this.priority = priority;
|
|
255
618
|
return this;
|
|
256
619
|
}
|
|
257
620
|
/**
|
|
258
|
-
*
|
|
259
|
-
*
|
|
621
|
+
* Push the mailable onto the configured queue.
|
|
622
|
+
*
|
|
623
|
+
* Automatically resolves the mail service from the Gravito container and
|
|
624
|
+
* dispatches this mailable for background processing.
|
|
625
|
+
*
|
|
626
|
+
* @returns A promise that resolves when the mailable is queued.
|
|
627
|
+
*
|
|
628
|
+
* @example
|
|
629
|
+
* ```typescript
|
|
630
|
+
* await new WelcomeEmail(user).queue()
|
|
631
|
+
* ```
|
|
260
632
|
*/
|
|
261
633
|
async queue() {
|
|
262
634
|
try {
|
|
@@ -273,9 +645,17 @@ var Mailable = class {
|
|
|
273
645
|
currentLocale;
|
|
274
646
|
translator;
|
|
275
647
|
/**
|
|
276
|
-
* Set the locale for the
|
|
648
|
+
* Set the locale for the email content.
|
|
649
|
+
*
|
|
650
|
+
* Used by the translator to resolve localized strings in templates or components.
|
|
651
|
+
*
|
|
652
|
+
* @param locale - The locale identifier (e.g., 'en-US', 'fr').
|
|
653
|
+
* @returns The current mailable instance for chaining.
|
|
277
654
|
*
|
|
278
|
-
* @
|
|
655
|
+
* @example
|
|
656
|
+
* ```typescript
|
|
657
|
+
* mailable.locale('es')
|
|
658
|
+
* ```
|
|
279
659
|
*/
|
|
280
660
|
locale(locale) {
|
|
281
661
|
this.currentLocale = locale;
|
|
@@ -289,11 +669,18 @@ var Mailable = class {
|
|
|
289
669
|
this.translator = translator;
|
|
290
670
|
}
|
|
291
671
|
/**
|
|
292
|
-
* Translate a
|
|
672
|
+
* Translate a key into a localized string.
|
|
293
673
|
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
* @
|
|
674
|
+
* Uses the configured translator and current locale to resolve the key.
|
|
675
|
+
*
|
|
676
|
+
* @param key - The translation key.
|
|
677
|
+
* @param replace - Key-value pairs for string interpolation.
|
|
678
|
+
* @returns The translated string, or the key itself if no translator is available.
|
|
679
|
+
*
|
|
680
|
+
* @example
|
|
681
|
+
* ```typescript
|
|
682
|
+
* const text = mailable.t('messages.welcome', { name: 'Alice' })
|
|
683
|
+
* ```
|
|
297
684
|
*/
|
|
298
685
|
t(key, replace) {
|
|
299
686
|
if (this.translator) {
|
|
@@ -303,8 +690,18 @@ var Mailable = class {
|
|
|
303
690
|
}
|
|
304
691
|
// ===== Internal Systems =====
|
|
305
692
|
/**
|
|
306
|
-
* Compile the
|
|
307
|
-
*
|
|
693
|
+
* Compile the final email envelope.
|
|
694
|
+
*
|
|
695
|
+
* Merges mailable-specific settings with global configuration defaults.
|
|
696
|
+
* This is called internally by the mail service before sending.
|
|
697
|
+
*
|
|
698
|
+
* @param configPromise - The mail configuration or a promise resolving to it.
|
|
699
|
+
* @returns The fully constructed envelope.
|
|
700
|
+
*
|
|
701
|
+
* @example
|
|
702
|
+
* ```typescript
|
|
703
|
+
* const envelope = await mailable.buildEnvelope(config)
|
|
704
|
+
* ```
|
|
308
705
|
*/
|
|
309
706
|
async buildEnvelope(configPromise) {
|
|
310
707
|
const config = await Promise.resolve(configPromise);
|
|
@@ -337,8 +734,18 @@ var Mailable = class {
|
|
|
337
734
|
return envelope;
|
|
338
735
|
}
|
|
339
736
|
/**
|
|
340
|
-
*
|
|
341
|
-
*
|
|
737
|
+
* Render the email content to HTML and plain text.
|
|
738
|
+
*
|
|
739
|
+
* Executes the chosen renderer (HTML, Template, React, or Vue) with the
|
|
740
|
+
* provided data and i18n helpers.
|
|
741
|
+
*
|
|
742
|
+
* @returns The rendered HTML and optional plain text content.
|
|
743
|
+
* @throws {Error} If no renderer has been specified.
|
|
744
|
+
*
|
|
745
|
+
* @example
|
|
746
|
+
* ```typescript
|
|
747
|
+
* const { html } = await mailable.renderContent()
|
|
748
|
+
* ```
|
|
342
749
|
*/
|
|
343
750
|
async renderContent() {
|
|
344
751
|
if (!this.renderer && this.rendererResolver) {
|
|
@@ -579,20 +986,20 @@ var DevServer = class {
|
|
|
579
986
|
};
|
|
580
987
|
router.get(
|
|
581
988
|
prefix,
|
|
582
|
-
wrap((ctx) => {
|
|
583
|
-
const entries = this.mailbox.list();
|
|
989
|
+
wrap(async (ctx) => {
|
|
990
|
+
const entries = await this.mailbox.list();
|
|
584
991
|
ctx.header("Content-Type", "text/html; charset=utf-8");
|
|
585
992
|
return ctx.html(getMailboxHtml(entries, prefix));
|
|
586
993
|
})
|
|
587
994
|
);
|
|
588
995
|
router.get(
|
|
589
996
|
`${prefix}/:id`,
|
|
590
|
-
wrap((ctx) => {
|
|
997
|
+
wrap(async (ctx) => {
|
|
591
998
|
const id = ctx.req.param("id");
|
|
592
999
|
if (!id) {
|
|
593
1000
|
return ctx.text("Bad Request", 400);
|
|
594
1001
|
}
|
|
595
|
-
const entry = this.mailbox.get(id);
|
|
1002
|
+
const entry = await this.mailbox.get(id);
|
|
596
1003
|
if (!entry) {
|
|
597
1004
|
return ctx.text("Email not found", 404);
|
|
598
1005
|
}
|
|
@@ -602,12 +1009,12 @@ var DevServer = class {
|
|
|
602
1009
|
);
|
|
603
1010
|
router.get(
|
|
604
1011
|
`${prefix}/:id/html`,
|
|
605
|
-
wrap((ctx) => {
|
|
1012
|
+
wrap(async (ctx) => {
|
|
606
1013
|
const id = ctx.req.param("id");
|
|
607
1014
|
if (!id) {
|
|
608
1015
|
return ctx.text("Bad Request", 400);
|
|
609
1016
|
}
|
|
610
|
-
const entry = this.mailbox.get(id);
|
|
1017
|
+
const entry = await this.mailbox.get(id);
|
|
611
1018
|
if (!entry) {
|
|
612
1019
|
return ctx.text("Not found", 404);
|
|
613
1020
|
}
|
|
@@ -617,12 +1024,12 @@ var DevServer = class {
|
|
|
617
1024
|
);
|
|
618
1025
|
router.get(
|
|
619
1026
|
`${prefix}/:id/text`,
|
|
620
|
-
wrap((ctx) => {
|
|
1027
|
+
wrap(async (ctx) => {
|
|
621
1028
|
const id = ctx.req.param("id");
|
|
622
1029
|
if (!id) {
|
|
623
1030
|
return ctx.text("Bad Request", 400);
|
|
624
1031
|
}
|
|
625
|
-
const entry = this.mailbox.get(id);
|
|
1032
|
+
const entry = await this.mailbox.get(id);
|
|
626
1033
|
if (!entry) {
|
|
627
1034
|
return ctx.text("Not found", 404);
|
|
628
1035
|
}
|
|
@@ -632,12 +1039,12 @@ var DevServer = class {
|
|
|
632
1039
|
);
|
|
633
1040
|
router.get(
|
|
634
1041
|
`${prefix}/:id/raw`,
|
|
635
|
-
wrap((ctx) => {
|
|
1042
|
+
wrap(async (ctx) => {
|
|
636
1043
|
const id = ctx.req.param("id");
|
|
637
1044
|
if (!id) {
|
|
638
1045
|
return ctx.json({ error: "Bad Request" }, 400);
|
|
639
1046
|
}
|
|
640
|
-
const entry = this.mailbox.get(id);
|
|
1047
|
+
const entry = await this.mailbox.get(id);
|
|
641
1048
|
if (!entry) {
|
|
642
1049
|
return ctx.json({ error: "Not found" }, 404);
|
|
643
1050
|
}
|
|
@@ -652,19 +1059,19 @@ var DevServer = class {
|
|
|
652
1059
|
);
|
|
653
1060
|
router.delete(
|
|
654
1061
|
`${prefix}/:id`,
|
|
655
|
-
wrap((ctx) => {
|
|
1062
|
+
wrap(async (ctx) => {
|
|
656
1063
|
const id = ctx.req.param("id");
|
|
657
1064
|
if (!id) {
|
|
658
1065
|
return ctx.json({ success: false, error: "Bad Request" }, 400);
|
|
659
1066
|
}
|
|
660
|
-
const success = this.mailbox.delete(id);
|
|
1067
|
+
const success = await this.mailbox.delete(id);
|
|
661
1068
|
return ctx.json({ success });
|
|
662
1069
|
})
|
|
663
1070
|
);
|
|
664
1071
|
router.delete(
|
|
665
1072
|
prefix,
|
|
666
|
-
wrap((ctx) => {
|
|
667
|
-
this.mailbox.clear();
|
|
1073
|
+
wrap(async (ctx) => {
|
|
1074
|
+
await this.mailbox.clear();
|
|
668
1075
|
return ctx.json({ success: true });
|
|
669
1076
|
})
|
|
670
1077
|
);
|
|
@@ -674,6 +1081,22 @@ var DevServer = class {
|
|
|
674
1081
|
|
|
675
1082
|
// src/transports/LogTransport.ts
|
|
676
1083
|
var LogTransport = class {
|
|
1084
|
+
/**
|
|
1085
|
+
* Outputs the message details to the system console.
|
|
1086
|
+
*
|
|
1087
|
+
* Formats the email metadata (From, To, Subject) and content size into a readable
|
|
1088
|
+
* block in the console output.
|
|
1089
|
+
*
|
|
1090
|
+
* @param message - The message to log.
|
|
1091
|
+
* @returns A promise that resolves immediately after logging.
|
|
1092
|
+
*
|
|
1093
|
+
* @example
|
|
1094
|
+
* ```typescript
|
|
1095
|
+
* const transport = new LogTransport();
|
|
1096
|
+
* await transport.send(message);
|
|
1097
|
+
* // Console: 📧 [OrbitSignal] Email Sent (Simulated)...
|
|
1098
|
+
* ```
|
|
1099
|
+
*/
|
|
677
1100
|
async send(message) {
|
|
678
1101
|
console.log("\n\u{1F4E7} [OrbitSignal] Email Sent (Simulated):");
|
|
679
1102
|
console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
|
|
@@ -690,9 +1113,33 @@ var LogTransport = class {
|
|
|
690
1113
|
|
|
691
1114
|
// src/transports/MemoryTransport.ts
|
|
692
1115
|
var MemoryTransport = class {
|
|
1116
|
+
/**
|
|
1117
|
+
* Creates a new MemoryTransport instance.
|
|
1118
|
+
*
|
|
1119
|
+
* @param mailbox - The in-memory storage where messages will be collected.
|
|
1120
|
+
*/
|
|
693
1121
|
constructor(mailbox) {
|
|
694
1122
|
this.mailbox = mailbox;
|
|
695
1123
|
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Stores the message in the associated mailbox.
|
|
1126
|
+
*
|
|
1127
|
+
* The message is added to the internal list of the `DevMailbox` instance,
|
|
1128
|
+
* making it available for retrieval by the Dev UI or test assertions.
|
|
1129
|
+
*
|
|
1130
|
+
* @param message - The message to store.
|
|
1131
|
+
* @returns A promise that resolves once the message is added to the mailbox.
|
|
1132
|
+
*
|
|
1133
|
+
* @example
|
|
1134
|
+
* ```typescript
|
|
1135
|
+
* await transport.send({
|
|
1136
|
+
* from: { address: 'dev@localhost' },
|
|
1137
|
+
* to: [{ address: 'test@example.com' }],
|
|
1138
|
+
* subject: 'Memory Test',
|
|
1139
|
+
* html: '<p>Stored in memory</p>'
|
|
1140
|
+
* });
|
|
1141
|
+
* ```
|
|
1142
|
+
*/
|
|
696
1143
|
async send(message) {
|
|
697
1144
|
this.mailbox.add(message);
|
|
698
1145
|
}
|
|
@@ -703,13 +1150,24 @@ var OrbitSignal = class {
|
|
|
703
1150
|
config;
|
|
704
1151
|
devMailbox;
|
|
705
1152
|
core;
|
|
1153
|
+
eventHandlers = /* @__PURE__ */ new Map();
|
|
706
1154
|
constructor(config = {}) {
|
|
707
1155
|
this.config = config;
|
|
708
1156
|
}
|
|
709
1157
|
/**
|
|
710
|
-
* Install the orbit into PlanetCore
|
|
1158
|
+
* Install the orbit into PlanetCore.
|
|
1159
|
+
*
|
|
1160
|
+
* Registers the mail service in the IoC container and sets up development
|
|
1161
|
+
* tools if enabled. It also injects the service into the GravitoContext
|
|
1162
|
+
* for easy access in route handlers.
|
|
711
1163
|
*
|
|
712
|
-
* @param core - The PlanetCore instance
|
|
1164
|
+
* @param core - The PlanetCore instance to install into
|
|
1165
|
+
*
|
|
1166
|
+
* @example
|
|
1167
|
+
* ```typescript
|
|
1168
|
+
* const mail = new OrbitSignal(config);
|
|
1169
|
+
* mail.install(core);
|
|
1170
|
+
* ```
|
|
713
1171
|
*/
|
|
714
1172
|
install(core) {
|
|
715
1173
|
this.core = core;
|
|
@@ -732,37 +1190,123 @@ var OrbitSignal = class {
|
|
|
732
1190
|
c.set("mail", this);
|
|
733
1191
|
return await next();
|
|
734
1192
|
});
|
|
1193
|
+
if (this.config.webhookPrefix) {
|
|
1194
|
+
core.adapter.post(`${this.config.webhookPrefix}/:driver`, async (c) => {
|
|
1195
|
+
const driverName = c.req.param("driver");
|
|
1196
|
+
const driver = this.config.webhookDrivers?.[driverName];
|
|
1197
|
+
if (!driver) {
|
|
1198
|
+
return c.json({ error: `Webhook driver "${driverName}" not found` }, 404);
|
|
1199
|
+
}
|
|
1200
|
+
try {
|
|
1201
|
+
const results = await driver.handle(c);
|
|
1202
|
+
if (results && Array.isArray(results)) {
|
|
1203
|
+
for (const result of results) {
|
|
1204
|
+
await this.handleWebhook(driverName, result.event, result.payload);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
return c.json({ success: true });
|
|
1208
|
+
} catch (error) {
|
|
1209
|
+
core.logger.error(`[OrbitSignal] Webhook error (${driverName}):`, error);
|
|
1210
|
+
return c.json({ error: "Webhook processing failed" }, 500);
|
|
1211
|
+
}
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
735
1214
|
}
|
|
736
1215
|
/**
|
|
737
|
-
*
|
|
1216
|
+
* Internal: Handle processed webhook.
|
|
1217
|
+
*/
|
|
1218
|
+
async handleWebhook(driver, event, payload) {
|
|
1219
|
+
await this.emit({
|
|
1220
|
+
type: "webhookReceived",
|
|
1221
|
+
// biome-ignore lint/suspicious/noExplicitAny: Dummy mailable for webhook events
|
|
1222
|
+
mailable: {},
|
|
1223
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
1224
|
+
webhook: { driver, event, payload }
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Send a mailable instance immediately.
|
|
1229
|
+
*
|
|
1230
|
+
* Orchestrates the full email sending lifecycle: building the envelope,
|
|
1231
|
+
* rendering content, emitting events, and delivering via the configured transport.
|
|
1232
|
+
*
|
|
1233
|
+
* @param mailable - The email definition to send
|
|
1234
|
+
* @throws {Error} If mandatory fields (from, to) are missing or transport fails
|
|
1235
|
+
*
|
|
1236
|
+
* @example
|
|
1237
|
+
* ```typescript
|
|
1238
|
+
* await mail.send(new WelcomeEmail(user));
|
|
1239
|
+
* ```
|
|
738
1240
|
*/
|
|
739
1241
|
async send(mailable) {
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
1242
|
+
try {
|
|
1243
|
+
const envelope = await mailable.buildEnvelope(this.config);
|
|
1244
|
+
if (!envelope.from) {
|
|
1245
|
+
throw new Error('Message is missing "from" address');
|
|
1246
|
+
}
|
|
1247
|
+
if (!envelope.to || envelope.to.length === 0) {
|
|
1248
|
+
throw new Error('Message is missing "to" address');
|
|
1249
|
+
}
|
|
1250
|
+
await this.emit({
|
|
1251
|
+
type: "beforeRender",
|
|
1252
|
+
mailable,
|
|
1253
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
1254
|
+
});
|
|
1255
|
+
const content = await mailable.renderContent();
|
|
1256
|
+
await this.emit({
|
|
1257
|
+
type: "afterRender",
|
|
1258
|
+
mailable,
|
|
1259
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
1260
|
+
});
|
|
1261
|
+
const message = {
|
|
1262
|
+
...envelope,
|
|
1263
|
+
from: envelope.from,
|
|
1264
|
+
to: envelope.to,
|
|
1265
|
+
subject: envelope.subject || "(No Subject)",
|
|
1266
|
+
priority: envelope.priority || "normal",
|
|
1267
|
+
html: content.html
|
|
1268
|
+
};
|
|
1269
|
+
if (content.text) {
|
|
1270
|
+
message.text = content.text;
|
|
1271
|
+
}
|
|
1272
|
+
await this.emit({
|
|
1273
|
+
type: "beforeSend",
|
|
1274
|
+
mailable,
|
|
1275
|
+
message,
|
|
1276
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
1277
|
+
});
|
|
1278
|
+
if (!this.config.transport) {
|
|
1279
|
+
throw new Error("[OrbitSignal] No transport configured. Did you call register the orbit?");
|
|
1280
|
+
}
|
|
1281
|
+
await this.config.transport.send(message);
|
|
1282
|
+
await this.emit({
|
|
1283
|
+
type: "afterSend",
|
|
1284
|
+
mailable,
|
|
1285
|
+
message,
|
|
1286
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
1287
|
+
});
|
|
1288
|
+
} catch (error) {
|
|
1289
|
+
await this.emit({
|
|
1290
|
+
type: "sendFailed",
|
|
1291
|
+
mailable,
|
|
1292
|
+
error,
|
|
1293
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
1294
|
+
});
|
|
1295
|
+
throw error;
|
|
761
1296
|
}
|
|
762
|
-
await this.config.transport.send(message);
|
|
763
1297
|
}
|
|
764
1298
|
/**
|
|
765
|
-
* Queue a mailable instance
|
|
1299
|
+
* Queue a mailable instance for background processing.
|
|
1300
|
+
*
|
|
1301
|
+
* Attempts to use the 'queue' service (OrbitStream) if available in the
|
|
1302
|
+
* container. Falls back to immediate sending if no queue service is found.
|
|
1303
|
+
*
|
|
1304
|
+
* @param mailable - The email definition to queue
|
|
1305
|
+
*
|
|
1306
|
+
* @example
|
|
1307
|
+
* ```typescript
|
|
1308
|
+
* await mail.queue(new WelcomeEmail(user));
|
|
1309
|
+
* ```
|
|
766
1310
|
*/
|
|
767
1311
|
async queue(mailable) {
|
|
768
1312
|
try {
|
|
@@ -775,14 +1319,174 @@ var OrbitSignal = class {
|
|
|
775
1319
|
}
|
|
776
1320
|
await this.send(mailable);
|
|
777
1321
|
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Register an event handler.
|
|
1324
|
+
*
|
|
1325
|
+
* @description
|
|
1326
|
+
* Subscribe to mail lifecycle events for logging, analytics, or custom processing.
|
|
1327
|
+
*
|
|
1328
|
+
* @param event - The event type to listen for
|
|
1329
|
+
* @param handler - The handler function to execute
|
|
1330
|
+
* @returns This instance for method chaining
|
|
1331
|
+
*
|
|
1332
|
+
* @example
|
|
1333
|
+
* ```typescript
|
|
1334
|
+
* mail.on('afterSend', async (event) => {
|
|
1335
|
+
* await analytics.track('email_sent', {
|
|
1336
|
+
* to: event.message?.to,
|
|
1337
|
+
* subject: event.message?.subject
|
|
1338
|
+
* })
|
|
1339
|
+
* })
|
|
1340
|
+
* ```
|
|
1341
|
+
*
|
|
1342
|
+
* @public
|
|
1343
|
+
* @since 3.1.0
|
|
1344
|
+
*/
|
|
1345
|
+
on(event, handler) {
|
|
1346
|
+
const handlers = this.eventHandlers.get(event) || [];
|
|
1347
|
+
handlers.push(handler);
|
|
1348
|
+
this.eventHandlers.set(event, handlers);
|
|
1349
|
+
return this;
|
|
1350
|
+
}
|
|
1351
|
+
async emit(event) {
|
|
1352
|
+
const handlers = this.eventHandlers.get(event.type) || [];
|
|
1353
|
+
for (const handler of handlers) {
|
|
1354
|
+
await handler(event);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
};
|
|
1358
|
+
|
|
1359
|
+
// src/renderers/mjml-templates.ts
|
|
1360
|
+
var baseLayout = `
|
|
1361
|
+
<mjml>
|
|
1362
|
+
<mj-head>
|
|
1363
|
+
<mj-attributes>
|
|
1364
|
+
<mj-all font-family="Arial, Helvetica, sans-serif" />
|
|
1365
|
+
<mj-text font-size="16px" color="#333333" line-height="1.5" />
|
|
1366
|
+
<mj-section padding="20px" />
|
|
1367
|
+
</mj-attributes>
|
|
1368
|
+
<mj-style>
|
|
1369
|
+
.link-white { color: #ffffff !important; text-decoration: none; }
|
|
1370
|
+
.footer-text { font-size: 12px; color: #999999; }
|
|
1371
|
+
</mj-style>
|
|
1372
|
+
</mj-head>
|
|
1373
|
+
<mj-body background-color="#f4f4f4">
|
|
1374
|
+
<mj-section background-color="#ffffff" padding-bottom="0px">
|
|
1375
|
+
<mj-column>
|
|
1376
|
+
<mj-image width="150px" src="https://gravito.dev/logo.png" alt="Gravito" />
|
|
1377
|
+
</mj-column>
|
|
1378
|
+
</mj-section>
|
|
1379
|
+
|
|
1380
|
+
{{content}}
|
|
1381
|
+
|
|
1382
|
+
<mj-section>
|
|
1383
|
+
<mj-column>
|
|
1384
|
+
<mj-divider border-width="1px" border-color="#dddddd" />
|
|
1385
|
+
<mj-text align="center" css-class="footer-text">
|
|
1386
|
+
© ${(/* @__PURE__ */ new Date()).getFullYear()} Gravito Framework. All rights reserved.
|
|
1387
|
+
</mj-text>
|
|
1388
|
+
</mj-column>
|
|
1389
|
+
</mj-section>
|
|
1390
|
+
</mj-body>
|
|
1391
|
+
</mjml>
|
|
1392
|
+
`;
|
|
1393
|
+
var transactionLayout = `
|
|
1394
|
+
<mj-section background-color="#ffffff" padding-top="0px">
|
|
1395
|
+
<mj-column>
|
|
1396
|
+
{{content}}
|
|
1397
|
+
</mj-column>
|
|
1398
|
+
</mj-section>
|
|
1399
|
+
`;
|
|
1400
|
+
|
|
1401
|
+
// src/TypedMailable.ts
|
|
1402
|
+
var TypedMailable = class extends Mailable {
|
|
1403
|
+
};
|
|
1404
|
+
|
|
1405
|
+
// src/transports/BaseTransport.ts
|
|
1406
|
+
var BaseTransport = class {
|
|
1407
|
+
options;
|
|
1408
|
+
/**
|
|
1409
|
+
* Initializes the transport with retry options.
|
|
1410
|
+
*
|
|
1411
|
+
* @param options - Configuration for the retry mechanism.
|
|
1412
|
+
*/
|
|
1413
|
+
constructor(options) {
|
|
1414
|
+
this.options = {
|
|
1415
|
+
maxRetries: options?.maxRetries ?? 3,
|
|
1416
|
+
retryDelay: options?.retryDelay ?? 1e3,
|
|
1417
|
+
backoffMultiplier: options?.backoffMultiplier ?? 2
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Orchestrates the message delivery with retry logic.
|
|
1422
|
+
*
|
|
1423
|
+
* This method wraps the concrete `doSend` implementation in a retry loop.
|
|
1424
|
+
* It tracks the last error encountered to provide context if all retries fail.
|
|
1425
|
+
*
|
|
1426
|
+
* @param message - The message to be delivered.
|
|
1427
|
+
* @returns A promise that resolves when the message is successfully sent.
|
|
1428
|
+
* @throws {MailTransportError} If the message cannot be sent after the maximum number of retries.
|
|
1429
|
+
*
|
|
1430
|
+
* @example
|
|
1431
|
+
* ```typescript
|
|
1432
|
+
* const transport = new SmtpTransport(config);
|
|
1433
|
+
* try {
|
|
1434
|
+
* await transport.send(message);
|
|
1435
|
+
* } catch (error) {
|
|
1436
|
+
* console.error('Failed to send email after retries', error);
|
|
1437
|
+
* }
|
|
1438
|
+
* ```
|
|
1439
|
+
*/
|
|
1440
|
+
async send(message) {
|
|
1441
|
+
let lastError;
|
|
1442
|
+
let delay = this.options.retryDelay;
|
|
1443
|
+
for (let attempt = 0; attempt <= this.options.maxRetries; attempt++) {
|
|
1444
|
+
try {
|
|
1445
|
+
return await this.doSend(message);
|
|
1446
|
+
} catch (error) {
|
|
1447
|
+
lastError = error;
|
|
1448
|
+
if (attempt < this.options.maxRetries) {
|
|
1449
|
+
await this.sleep(delay);
|
|
1450
|
+
delay *= this.options.backoffMultiplier;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
throw new MailTransportError(
|
|
1455
|
+
`Mail sending failed after ${this.options.maxRetries} retries`,
|
|
1456
|
+
"UNKNOWN" /* UNKNOWN */,
|
|
1457
|
+
lastError
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Utility method to pause execution for a given duration.
|
|
1462
|
+
*
|
|
1463
|
+
* @param ms - Milliseconds to sleep.
|
|
1464
|
+
* @returns A promise that resolves after the delay.
|
|
1465
|
+
*/
|
|
1466
|
+
sleep(ms) {
|
|
1467
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1468
|
+
}
|
|
778
1469
|
};
|
|
779
1470
|
|
|
780
1471
|
// src/transports/SesTransport.ts
|
|
781
1472
|
import { SESClient, SendRawEmailCommand } from "@aws-sdk/client-ses";
|
|
782
1473
|
import nodemailer from "nodemailer";
|
|
783
|
-
var SesTransport = class {
|
|
1474
|
+
var SesTransport = class extends BaseTransport {
|
|
784
1475
|
transporter;
|
|
1476
|
+
/**
|
|
1477
|
+
* Initializes the SES transport with the provided configuration.
|
|
1478
|
+
*
|
|
1479
|
+
* Configures the AWS SES client and wraps it in a nodemailer transporter
|
|
1480
|
+
* for consistent message handling.
|
|
1481
|
+
*
|
|
1482
|
+
* @param config - AWS SES connection and retry configuration.
|
|
1483
|
+
*/
|
|
785
1484
|
constructor(config) {
|
|
1485
|
+
super({
|
|
1486
|
+
maxRetries: config.maxRetries,
|
|
1487
|
+
retryDelay: config.retryDelay,
|
|
1488
|
+
backoffMultiplier: config.backoffMultiplier
|
|
1489
|
+
});
|
|
786
1490
|
const clientConfig = { region: config.region };
|
|
787
1491
|
if (config.accessKeyId && config.secretAccessKey) {
|
|
788
1492
|
clientConfig.credentials = {
|
|
@@ -795,7 +1499,17 @@ var SesTransport = class {
|
|
|
795
1499
|
SES: { ses, aws: { SendRawEmailCommand } }
|
|
796
1500
|
});
|
|
797
1501
|
}
|
|
798
|
-
|
|
1502
|
+
/**
|
|
1503
|
+
* Internal method to perform the actual SES delivery.
|
|
1504
|
+
*
|
|
1505
|
+
* Converts the generic `Message` object into a raw email format and sends it
|
|
1506
|
+
* via the SES `SendRawEmail` API.
|
|
1507
|
+
*
|
|
1508
|
+
* @param message - The message to deliver.
|
|
1509
|
+
* @returns A promise that resolves when SES accepts the message for delivery.
|
|
1510
|
+
* @throws {Error} If the SES API returns an error or connection fails.
|
|
1511
|
+
*/
|
|
1512
|
+
async doSend(message) {
|
|
799
1513
|
await this.transporter.sendMail({
|
|
800
1514
|
from: this.formatAddress(message.from),
|
|
801
1515
|
to: message.to.map(this.formatAddress),
|
|
@@ -816,6 +1530,12 @@ var SesTransport = class {
|
|
|
816
1530
|
}))
|
|
817
1531
|
});
|
|
818
1532
|
}
|
|
1533
|
+
/**
|
|
1534
|
+
* Formats an Address object into a standard RFC 822 string.
|
|
1535
|
+
*
|
|
1536
|
+
* @param addr - The address object to format.
|
|
1537
|
+
* @returns A string in the format "Name <email@example.com>" or just "email@example.com".
|
|
1538
|
+
*/
|
|
819
1539
|
formatAddress(addr) {
|
|
820
1540
|
return addr.name ? `"${addr.name}" <${addr.address}>` : addr.address;
|
|
821
1541
|
}
|
|
@@ -823,12 +1543,46 @@ var SesTransport = class {
|
|
|
823
1543
|
|
|
824
1544
|
// src/transports/SmtpTransport.ts
|
|
825
1545
|
import nodemailer2 from "nodemailer";
|
|
826
|
-
var SmtpTransport = class {
|
|
1546
|
+
var SmtpTransport = class extends BaseTransport {
|
|
827
1547
|
transporter;
|
|
1548
|
+
/**
|
|
1549
|
+
* Initializes the SMTP transport with the provided configuration.
|
|
1550
|
+
*
|
|
1551
|
+
* Sets up the underlying nodemailer transporter with connection pooling enabled.
|
|
1552
|
+
*
|
|
1553
|
+
* @param config - SMTP connection and retry configuration.
|
|
1554
|
+
*/
|
|
828
1555
|
constructor(config) {
|
|
829
|
-
|
|
1556
|
+
super({
|
|
1557
|
+
maxRetries: config.maxRetries,
|
|
1558
|
+
retryDelay: config.retryDelay,
|
|
1559
|
+
backoffMultiplier: config.backoffMultiplier
|
|
1560
|
+
});
|
|
1561
|
+
this.transporter = nodemailer2.createTransport({
|
|
1562
|
+
host: config.host,
|
|
1563
|
+
port: config.port,
|
|
1564
|
+
secure: config.secure,
|
|
1565
|
+
auth: config.auth,
|
|
1566
|
+
tls: config.tls,
|
|
1567
|
+
pool: true,
|
|
1568
|
+
maxConnections: config.poolSize ?? 5,
|
|
1569
|
+
maxMessages: Infinity,
|
|
1570
|
+
rateDelta: 1e3,
|
|
1571
|
+
rateLimit: 10,
|
|
1572
|
+
socketTimeout: config.maxIdleTime ?? 3e4,
|
|
1573
|
+
greetingTimeout: 3e4
|
|
1574
|
+
});
|
|
830
1575
|
}
|
|
831
|
-
|
|
1576
|
+
/**
|
|
1577
|
+
* Internal method to perform the actual SMTP delivery.
|
|
1578
|
+
*
|
|
1579
|
+
* Maps the generic `Message` object to the format expected by nodemailer.
|
|
1580
|
+
*
|
|
1581
|
+
* @param message - The message to deliver.
|
|
1582
|
+
* @returns A promise that resolves when the SMTP server accepts the message.
|
|
1583
|
+
* @throws {Error} If the SMTP server rejects the message or connection fails.
|
|
1584
|
+
*/
|
|
1585
|
+
async doSend(message) {
|
|
832
1586
|
await this.transporter.sendMail({
|
|
833
1587
|
from: this.formatAddress(message.from),
|
|
834
1588
|
to: message.to.map(this.formatAddress),
|
|
@@ -849,18 +1603,147 @@ var SmtpTransport = class {
|
|
|
849
1603
|
}))
|
|
850
1604
|
});
|
|
851
1605
|
}
|
|
1606
|
+
/**
|
|
1607
|
+
* Gracefully shuts down the transport and closes all pooled connections.
|
|
1608
|
+
*
|
|
1609
|
+
* This should be called during application shutdown to ensure no resources are leaked.
|
|
1610
|
+
*
|
|
1611
|
+
* @returns A promise that resolves when all connections are closed.
|
|
1612
|
+
*
|
|
1613
|
+
* @example
|
|
1614
|
+
* ```typescript
|
|
1615
|
+
* await transport.close();
|
|
1616
|
+
* ```
|
|
1617
|
+
*/
|
|
1618
|
+
async close() {
|
|
1619
|
+
this.transporter.close();
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Verifies the SMTP connection and authentication.
|
|
1623
|
+
*
|
|
1624
|
+
* Useful for health checks or validating configuration during startup.
|
|
1625
|
+
*
|
|
1626
|
+
* @returns A promise that resolves to true if the connection is valid, false otherwise.
|
|
1627
|
+
*
|
|
1628
|
+
* @example
|
|
1629
|
+
* ```typescript
|
|
1630
|
+
* const isValid = await transport.verify();
|
|
1631
|
+
* if (!isValid) throw new Error('SMTP configuration is invalid');
|
|
1632
|
+
* ```
|
|
1633
|
+
*/
|
|
1634
|
+
async verify() {
|
|
1635
|
+
try {
|
|
1636
|
+
await this.transporter.verify();
|
|
1637
|
+
return true;
|
|
1638
|
+
} catch {
|
|
1639
|
+
return false;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1643
|
+
* Formats an Address object into a standard RFC 822 string.
|
|
1644
|
+
*
|
|
1645
|
+
* @param addr - The address object to format.
|
|
1646
|
+
* @returns A string in the format "Name <email@example.com>" or just "email@example.com".
|
|
1647
|
+
*/
|
|
852
1648
|
formatAddress(addr) {
|
|
853
1649
|
return addr.name ? `"${addr.name}" <${addr.address}>` : addr.address;
|
|
854
1650
|
}
|
|
855
1651
|
};
|
|
1652
|
+
|
|
1653
|
+
// src/webhooks/SendGridWebhookDriver.ts
|
|
1654
|
+
var SendGridWebhookDriver = class {
|
|
1655
|
+
constructor(config = {}) {
|
|
1656
|
+
this.config = config;
|
|
1657
|
+
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Handles the SendGrid webhook request.
|
|
1660
|
+
*/
|
|
1661
|
+
async handle(c) {
|
|
1662
|
+
const body = await c.req.json();
|
|
1663
|
+
const events = Array.isArray(body) ? body : [body];
|
|
1664
|
+
if (this.config.publicKey) {
|
|
1665
|
+
const signature = c.req.header("X-Twilio-Email-Event-Webhook-Signature");
|
|
1666
|
+
const timestamp = c.req.header("X-Twilio-Email-Event-Webhook-Timestamp");
|
|
1667
|
+
if (!signature || !timestamp) {
|
|
1668
|
+
throw new Error("Missing SendGrid signature headers");
|
|
1669
|
+
}
|
|
1670
|
+
if (!this.verifySignature(JSON.stringify(body), signature, timestamp)) {
|
|
1671
|
+
throw new Error("Invalid SendGrid signature");
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
if (events.length === 0) {
|
|
1675
|
+
return null;
|
|
1676
|
+
}
|
|
1677
|
+
return events.map((e) => ({
|
|
1678
|
+
event: e.event,
|
|
1679
|
+
payload: e
|
|
1680
|
+
}));
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Verifies the SendGrid webhook signature.
|
|
1684
|
+
*
|
|
1685
|
+
* @param payload - Raw request body string.
|
|
1686
|
+
* @param signature - Signature from X-Twilio-Email-Event-Webhook-Signature header.
|
|
1687
|
+
* @param timestamp - Timestamp from X-Twilio-Email-Event-Webhook-Timestamp header.
|
|
1688
|
+
* @returns True if signature is valid.
|
|
1689
|
+
*
|
|
1690
|
+
* @remarks
|
|
1691
|
+
* Real SendGrid validation uses Elliptic Curve (ECDSA).
|
|
1692
|
+
* This is a placeholder for the logic structure.
|
|
1693
|
+
*/
|
|
1694
|
+
verifySignature(_payload, _signature, _timestamp) {
|
|
1695
|
+
if (!this.config.publicKey) {
|
|
1696
|
+
return true;
|
|
1697
|
+
}
|
|
1698
|
+
return true;
|
|
1699
|
+
}
|
|
1700
|
+
};
|
|
1701
|
+
|
|
1702
|
+
// src/webhooks/SesWebhookDriver.ts
|
|
1703
|
+
var SesWebhookDriver = class {
|
|
1704
|
+
/**
|
|
1705
|
+
* Handles the AWS SES/SNS webhook request.
|
|
1706
|
+
*
|
|
1707
|
+
* @param c - The Gravito request context.
|
|
1708
|
+
* @returns Array of processed events or null if ignored.
|
|
1709
|
+
*/
|
|
1710
|
+
async handle(c) {
|
|
1711
|
+
const body = await c.req.json();
|
|
1712
|
+
if (body.Type === "SubscriptionConfirmation") {
|
|
1713
|
+
return [{ event: "sns:subscription", payload: body }];
|
|
1714
|
+
}
|
|
1715
|
+
if (body.Type === "Notification") {
|
|
1716
|
+
const message = typeof body.Message === "string" ? JSON.parse(body.Message) : body.Message;
|
|
1717
|
+
const eventType = message.notificationType?.toLowerCase() || "unknown";
|
|
1718
|
+
return [
|
|
1719
|
+
{
|
|
1720
|
+
event: eventType,
|
|
1721
|
+
payload: message
|
|
1722
|
+
}
|
|
1723
|
+
];
|
|
1724
|
+
}
|
|
1725
|
+
return null;
|
|
1726
|
+
}
|
|
1727
|
+
};
|
|
856
1728
|
export {
|
|
1729
|
+
BaseTransport,
|
|
857
1730
|
DevMailbox,
|
|
858
1731
|
HtmlRenderer,
|
|
859
1732
|
LogTransport,
|
|
1733
|
+
MailErrorCode,
|
|
1734
|
+
MailTransportError,
|
|
860
1735
|
Mailable,
|
|
861
1736
|
MemoryTransport,
|
|
1737
|
+
MjmlRenderer,
|
|
862
1738
|
OrbitSignal,
|
|
1739
|
+
ReactMjmlRenderer,
|
|
1740
|
+
SendGridWebhookDriver,
|
|
863
1741
|
SesTransport,
|
|
1742
|
+
SesWebhookDriver,
|
|
864
1743
|
SmtpTransport,
|
|
865
|
-
TemplateRenderer
|
|
1744
|
+
TemplateRenderer,
|
|
1745
|
+
TypedMailable,
|
|
1746
|
+
VueMjmlRenderer,
|
|
1747
|
+
baseLayout,
|
|
1748
|
+
transactionLayout
|
|
866
1749
|
};
|