@fluojs/email 1.0.0-beta.1

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 (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.ko.md +325 -0
  3. package/README.md +325 -0
  4. package/dist/channel.d.ts +24 -0
  5. package/dist/channel.d.ts.map +1 -0
  6. package/dist/channel.js +64 -0
  7. package/dist/constants.d.ts +6 -0
  8. package/dist/constants.d.ts.map +1 -0
  9. package/dist/constants.js +17 -0
  10. package/dist/errors.d.ts +13 -0
  11. package/dist/errors.d.ts.map +1 -0
  12. package/dist/errors.js +19 -0
  13. package/dist/index.d.ts +9 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +6 -0
  16. package/dist/module.d.ts +45 -0
  17. package/dist/module.d.ts.map +1 -0
  18. package/dist/module.js +151 -0
  19. package/dist/node/node.d.ts +2 -0
  20. package/dist/node/node.d.ts.map +1 -0
  21. package/dist/node/node.js +1 -0
  22. package/dist/node/nodemailer.d.ts +104 -0
  23. package/dist/node/nodemailer.d.ts.map +1 -0
  24. package/dist/node/nodemailer.js +166 -0
  25. package/dist/node.d.ts +2 -0
  26. package/dist/node.d.ts.map +1 -0
  27. package/dist/node.js +1 -0
  28. package/dist/queue-entry.d.ts +4 -0
  29. package/dist/queue-entry.d.ts.map +1 -0
  30. package/dist/queue-entry.js +2 -0
  31. package/dist/queue.d.ts +38 -0
  32. package/dist/queue.d.ts.map +1 -0
  33. package/dist/queue.js +66 -0
  34. package/dist/service.d.ts +81 -0
  35. package/dist/service.d.ts.map +1 -0
  36. package/dist/service.js +275 -0
  37. package/dist/status.d.ts +28 -0
  38. package/dist/status.d.ts.map +1 -0
  39. package/dist/status.js +83 -0
  40. package/dist/tokens.d.ts +10 -0
  41. package/dist/tokens.d.ts.map +1 -0
  42. package/dist/tokens.js +6 -0
  43. package/dist/types.d.ts +242 -0
  44. package/dist/types.d.ts.map +1 -0
  45. package/dist/types.js +1 -0
  46. package/package.json +84 -0
@@ -0,0 +1,64 @@
1
+ let _initClass;
2
+ function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; }
3
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
4
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
5
+ function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
6
+ function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
7
+ import { Inject } from '@fluojs/core';
8
+ import { EmailService } from './service.js';
9
+ import { EMAIL_OPTIONS } from './tokens.js';
10
+ function createIncompleteDeliveryError(receipt) {
11
+ const error = new Error(`Email transport reported an incomplete delivery (accepted=${String(receipt.accepted.length)}, pending=${String(receipt.pending.length)}, rejected=${String(receipt.rejected.length)}).`);
12
+ error.name = 'EmailDeliveryError';
13
+ return error;
14
+ }
15
+
16
+ /**
17
+ * Notification channel implementation that bridges `@fluojs/notifications` to {@link EmailService}.
18
+ *
19
+ * @remarks
20
+ * This class keeps the foundation package channel-agnostic while allowing `@fluojs/email`
21
+ * to interpret email-specific payload fields, template rendering, and transport delivery.
22
+ */
23
+ let _EmailChannel;
24
+ class EmailChannel {
25
+ static {
26
+ [_EmailChannel, _initClass] = _applyDecs(this, [Inject(EmailService, EMAIL_OPTIONS)], []).c;
27
+ }
28
+ channel;
29
+ constructor(email, options) {
30
+ this.email = email;
31
+ this.channel = options.notifications.channel;
32
+ }
33
+
34
+ /**
35
+ * Sends one notifications foundation request through the configured email transport.
36
+ *
37
+ * @param notification Shared notification envelope understood by the email package.
38
+ * @param context Optional abort context propagated from the notifications service.
39
+ * @returns A normalized channel delivery result with the provider message id exposed as `externalId`.
40
+ */
41
+ async send(notification, context) {
42
+ const receipt = await this.email.sendNotification(notification, {
43
+ signal: context.signal
44
+ });
45
+ if (receipt.accepted.length === 0 || receipt.pending.length > 0 || receipt.rejected.length > 0) {
46
+ throw createIncompleteDeliveryError(receipt);
47
+ }
48
+ return {
49
+ externalId: receipt.messageId,
50
+ metadata: {
51
+ accepted: receipt.accepted,
52
+ pending: receipt.pending,
53
+ rejected: receipt.rejected,
54
+ response: receipt.response
55
+ },
56
+ receipt,
57
+ status: 'delivered'
58
+ };
59
+ }
60
+ static {
61
+ _initClass();
62
+ }
63
+ }
64
+ export { _EmailChannel as EmailChannel };
@@ -0,0 +1,6 @@
1
+ import type { EmailQueueWorkerOptions } from './types.js';
2
+ /** Stable queue job name used by the email notifications worker contract. */
3
+ export declare const EMAIL_QUEUE_JOB_NAME = "fluo.email.notification";
4
+ /** Default worker options used by the built-in email notifications queue seam. */
5
+ export declare const DEFAULT_EMAIL_QUEUE_WORKER_OPTIONS: Required<EmailQueueWorkerOptions>;
6
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D,6EAA6E;AAC7E,eAAO,MAAM,oBAAoB,4BAA4B,CAAC;AAE9D,kFAAkF;AAClF,eAAO,MAAM,kCAAkC,EAAE,QAAQ,CAAC,uBAAuB,CAY/E,CAAC"}
@@ -0,0 +1,17 @@
1
+ /** Stable queue job name used by the email notifications worker contract. */
2
+ export const EMAIL_QUEUE_JOB_NAME = 'fluo.email.notification';
3
+
4
+ /** Default worker options used by the built-in email notifications queue seam. */
5
+ export const DEFAULT_EMAIL_QUEUE_WORKER_OPTIONS = Object.freeze({
6
+ attempts: 3,
7
+ backoff: {
8
+ delayMs: 1_000,
9
+ type: 'exponential'
10
+ },
11
+ concurrency: 5,
12
+ jobName: EMAIL_QUEUE_JOB_NAME,
13
+ rateLimiter: {
14
+ duration: 1_000,
15
+ max: 50
16
+ }
17
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Base error type for caller-visible email module configuration failures.
3
+ */
4
+ export declare class EmailConfigurationError extends Error {
5
+ constructor(message: string);
6
+ }
7
+ /**
8
+ * Thrown when an email message or notification payload is missing one required contract field.
9
+ */
10
+ export declare class EmailMessageValidationError extends Error {
11
+ constructor(message: string);
12
+ }
13
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,qBAAa,2BAA4B,SAAQ,KAAK;gBACxC,OAAO,EAAE,MAAM;CAI5B"}
package/dist/errors.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Base error type for caller-visible email module configuration failures.
3
+ */
4
+ export class EmailConfigurationError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = 'EmailConfigurationError';
8
+ }
9
+ }
10
+
11
+ /**
12
+ * Thrown when an email message or notification payload is missing one required contract field.
13
+ */
14
+ export class EmailMessageValidationError extends Error {
15
+ constructor(message) {
16
+ super(message);
17
+ this.name = 'EmailMessageValidationError';
18
+ }
19
+ }
@@ -0,0 +1,9 @@
1
+ export { EmailConfigurationError, EmailMessageValidationError, } from './errors.js';
2
+ export { EmailChannel } from './channel.js';
3
+ export { EmailModule } from './module.js';
4
+ export { EmailService } from './service.js';
5
+ export { createEmailPlatformStatusSnapshot } from './status.js';
6
+ export type { EmailLifecycleState, EmailPlatformStatusSnapshot, EmailStatusAdapterInput } from './status.js';
7
+ export { EMAIL, EMAIL_CHANNEL } from './tokens.js';
8
+ export type { Email, EmailAddress, EmailAddressLike, EmailAsyncModuleOptions, EmailAttachment, EmailMessage, EmailModuleOptions, EmailNotificationDispatchRequest, EmailNotificationPayload, EmailSendBatchResult, EmailSendFailure, EmailSendManyOptions, EmailSendOptions, EmailSendResult, NormalizedEmailAddressList, NormalizedEmailMessage, EmailTemplateRenderInput, EmailTemplateRenderer, EmailTemplateRenderResult, EmailTransport, EmailTransportContext, EmailTransportFactory, EmailTransportReceipt, } from './types.js';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,2BAA2B,GAC5B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,iCAAiC,EAAE,MAAM,aAAa,CAAC;AAChE,YAAY,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAC7G,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACnD,YAAY,EACV,KAAK,EACL,YAAY,EACZ,gBAAgB,EAChB,uBAAuB,EACvB,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,gCAAgC,EAChC,wBAAwB,EACxB,oBAAoB,EACpB,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,0BAA0B,EAC1B,sBAAsB,EACtB,wBAAwB,EACxB,qBAAqB,EACrB,yBAAyB,EACzB,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { EmailConfigurationError, EmailMessageValidationError } from './errors.js';
2
+ export { EmailChannel } from './channel.js';
3
+ export { EmailModule } from './module.js';
4
+ export { EmailService } from './service.js';
5
+ export { createEmailPlatformStatusSnapshot } from './status.js';
6
+ export { EMAIL, EMAIL_CHANNEL } from './tokens.js';
@@ -0,0 +1,45 @@
1
+ import { type ModuleType } from '@fluojs/runtime';
2
+ import type { EmailAsyncModuleOptions, EmailModuleOptions } from './types.js';
3
+ /** Runtime module entrypoint for email delivery and notifications integration. */
4
+ export declare class EmailModule {
5
+ /**
6
+ * Registers email providers using static options.
7
+ *
8
+ * @param options Static email module options including transport wiring and optional template rendering behavior.
9
+ * @returns A global module definition that exports {@link EmailService}, {@link EmailChannel}, and queue integration tokens.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * EmailModule.forRoot({
14
+ * defaultFrom: 'noreply@example.com',
15
+ * transport: {
16
+ * kind: 'example-transport',
17
+ * create: async () => exampleTransport,
18
+ * },
19
+ * });
20
+ * ```
21
+ */
22
+ static forRoot(options: EmailModuleOptions): ModuleType;
23
+ /**
24
+ * Registers email providers from an async DI factory.
25
+ *
26
+ * @param options Async module options that resolve email transport and renderer configuration through DI.
27
+ * @returns A global module definition that memoizes async option resolution per module instance.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * EmailModule.forRootAsync({
32
+ * inject: [ConfigService],
33
+ * useFactory: (config) => ({
34
+ * defaultFrom: config.mail.from,
35
+ * transport: {
36
+ * kind: config.mail.kind,
37
+ * create: () => config.mail.transport,
38
+ * },
39
+ * }),
40
+ * });
41
+ * ```
42
+ */
43
+ static forRootAsync(options: EmailAsyncModuleOptions): ModuleType;
44
+ }
45
+ //# sourceMappingURL=module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAOhE,OAAO,KAAK,EAGV,uBAAuB,EAGvB,kBAAkB,EAEnB,MAAM,YAAY,CAAC;AA0HpB,kFAAkF;AAClF,qBAAa,WAAW;IACtB;;;;;;;;;;;;;;;;QAgBI;IACJ,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,kBAAkB,GAAG,UAAU;IAIvD;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,uBAAuB,GAAG,UAAU;CAGlE"}
package/dist/module.js ADDED
@@ -0,0 +1,151 @@
1
+ import { defineModule } from '@fluojs/runtime';
2
+ import { EmailConfigurationError } from './errors.js';
3
+ import { EmailChannel } from './channel.js';
4
+ import { EmailNotificationsQueueWorker } from './queue.js';
5
+ import { EmailService } from './service.js';
6
+ import { EMAIL, EMAIL_CHANNEL, EMAIL_OPTIONS } from './tokens.js';
7
+ function normalizeAddress(address) {
8
+ if (!address) {
9
+ return undefined;
10
+ }
11
+ if (typeof address === 'string') {
12
+ return {
13
+ address: address.trim()
14
+ };
15
+ }
16
+ return {
17
+ address: address.address.trim(),
18
+ ...(address.name ? {
19
+ name: address.name
20
+ } : {})
21
+ };
22
+ }
23
+ function normalizeAddressList(value) {
24
+ if (!value) {
25
+ return [];
26
+ }
27
+ const list = Array.isArray(value) ? value : [value];
28
+ return list.map(entry => normalizeAddress(entry)).filter(entry => entry !== undefined);
29
+ }
30
+ function isTransportFactory(value) {
31
+ return typeof value === 'object' && value !== null && 'create' in value;
32
+ }
33
+ function normalizeEmailModuleOptions(options) {
34
+ if (!options.transport) {
35
+ throw new EmailConfigurationError('EmailModule requires an explicit `transport` to be configured.');
36
+ }
37
+ const transport = options.transport;
38
+ const createTransport = isTransportFactory(transport) ? async () => transport.create() : async () => transport;
39
+ return {
40
+ defaultFrom: normalizeAddress(options.defaultFrom),
41
+ defaultReplyTo: normalizeAddressList(options.defaultReplyTo),
42
+ notifications: {
43
+ channel: options.notifications?.channel?.trim() || 'email'
44
+ },
45
+ renderer: options.renderer,
46
+ transport: {
47
+ create: createTransport,
48
+ kind: isTransportFactory(transport) ? transport.kind?.trim() || 'custom-factory' : 'custom-instance',
49
+ ownsResources: isTransportFactory(transport) ? transport.ownsResources ?? true : false
50
+ },
51
+ verifyOnModuleInit: options.verifyOnModuleInit ?? false
52
+ };
53
+ }
54
+ function createEmailRuntimeProviders(optionsProvider) {
55
+ return [optionsProvider, EmailService, EmailChannel, EmailNotificationsQueueWorker, {
56
+ inject: [EmailService],
57
+ provide: EMAIL,
58
+ useFactory: service => ({
59
+ send: (message, options) => service.send(message, options),
60
+ sendMany: (messages, options) => service.sendMany(messages, options),
61
+ sendNotification: (notification, options) => service.sendNotification(notification, options)
62
+ })
63
+ }, {
64
+ inject: [EmailChannel],
65
+ provide: EMAIL_CHANNEL,
66
+ useFactory: channel => channel
67
+ }];
68
+ }
69
+ function createEmailProviders(options) {
70
+ return createEmailRuntimeProviders({
71
+ provide: EMAIL_OPTIONS,
72
+ useValue: normalizeEmailModuleOptions(options)
73
+ });
74
+ }
75
+ function buildEmailModule(options) {
76
+ class EmailRootModuleDefinition {}
77
+ return defineModule(EmailRootModuleDefinition, {
78
+ exports: [EmailService, EmailChannel, EMAIL, EMAIL_CHANNEL],
79
+ global: true,
80
+ providers: createEmailProviders(options)
81
+ });
82
+ }
83
+ function buildEmailModuleAsync(options) {
84
+ class EmailAsyncModuleDefinition {}
85
+ const factory = options.useFactory;
86
+ let cachedResult;
87
+ const memoizedFactory = (...deps) => {
88
+ if (!cachedResult) {
89
+ cachedResult = Promise.resolve(factory(...deps)).then(resolved => normalizeEmailModuleOptions(resolved));
90
+ }
91
+ return cachedResult;
92
+ };
93
+ return defineModule(EmailAsyncModuleDefinition, {
94
+ exports: [EmailService, EmailChannel, EMAIL, EMAIL_CHANNEL],
95
+ global: true,
96
+ providers: createEmailRuntimeProviders({
97
+ inject: options.inject,
98
+ provide: EMAIL_OPTIONS,
99
+ scope: 'singleton',
100
+ useFactory: (...deps) => memoizedFactory(...deps)
101
+ })
102
+ });
103
+ }
104
+
105
+ /** Runtime module entrypoint for email delivery and notifications integration. */
106
+ export class EmailModule {
107
+ /**
108
+ * Registers email providers using static options.
109
+ *
110
+ * @param options Static email module options including transport wiring and optional template rendering behavior.
111
+ * @returns A global module definition that exports {@link EmailService}, {@link EmailChannel}, and queue integration tokens.
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * EmailModule.forRoot({
116
+ * defaultFrom: 'noreply@example.com',
117
+ * transport: {
118
+ * kind: 'example-transport',
119
+ * create: async () => exampleTransport,
120
+ * },
121
+ * });
122
+ * ```
123
+ */
124
+ static forRoot(options) {
125
+ return buildEmailModule(options);
126
+ }
127
+
128
+ /**
129
+ * Registers email providers from an async DI factory.
130
+ *
131
+ * @param options Async module options that resolve email transport and renderer configuration through DI.
132
+ * @returns A global module definition that memoizes async option resolution per module instance.
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * EmailModule.forRootAsync({
137
+ * inject: [ConfigService],
138
+ * useFactory: (config) => ({
139
+ * defaultFrom: config.mail.from,
140
+ * transport: {
141
+ * kind: config.mail.kind,
142
+ * create: () => config.mail.transport,
143
+ * },
144
+ * }),
145
+ * });
146
+ * ```
147
+ */
148
+ static forRootAsync(options) {
149
+ return buildEmailModuleAsync(options);
150
+ }
151
+ }
@@ -0,0 +1,2 @@
1
+ export * from './nodemailer.js';
2
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/node/node.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1 @@
1
+ export * from './nodemailer.js';
@@ -0,0 +1,104 @@
1
+ import type Mail from 'nodemailer/lib/mailer';
2
+ import type SMTPTransport from 'nodemailer/lib/smtp-transport';
3
+ import type { EmailTransport, EmailTransportContext, EmailTransportFactory, EmailTransportReceipt, NormalizedEmailMessage } from '../types.js';
4
+ /** Node-only Nodemailer transporter type used by the explicit `@fluojs/email/node` seam. */
5
+ export type NodemailerTransporter = Mail<SMTPTransport.SentMessageInfo>;
6
+ /**
7
+ * Options for wrapping an existing Nodemailer transporter without transferring resource ownership.
8
+ *
9
+ * @remarks
10
+ * Use this when your application already constructs and manages the Nodemailer transporter
11
+ * at the Node boundary, but you still want to satisfy the shared {@link EmailTransport}
12
+ * contract consumed by `@fluojs/email`.
13
+ */
14
+ export interface NodemailerEmailTransportOptions {
15
+ /** Existing Nodemailer transporter instance that remains owned by the caller. */
16
+ transporter: NodemailerTransporter;
17
+ }
18
+ /**
19
+ * Options for creating a factory-owned SMTP transporter behind `@fluojs/email/node`.
20
+ *
21
+ * @remarks
22
+ * The resulting factory advertises `ownsResources: true`, allowing the shared email module
23
+ * to close the Nodemailer transporter during application shutdown while keeping that behavior
24
+ * isolated to the Node-only subpath.
25
+ */
26
+ export interface NodemailerEmailTransportFactoryOptions {
27
+ /** Optional diagnostic label surfaced through the root email status snapshot. */
28
+ kind?: string;
29
+ /** SMTP connection settings or SMTP URL passed directly to `nodemailer.createTransport(...)`. */
30
+ smtp: SMTPTransport.Options | string;
31
+ }
32
+ /**
33
+ * Node-only {@link EmailTransport} adapter that forwards normalized email messages to Nodemailer.
34
+ *
35
+ * @remarks
36
+ * This adapter intentionally lives behind `@fluojs/email/node` so SMTP/Nodemailer behavior,
37
+ * verification, and connection cleanup never leak into the transport-agnostic root barrel.
38
+ */
39
+ export declare class NodemailerEmailTransport implements EmailTransport {
40
+ private readonly transporter;
41
+ constructor(transporter: NodemailerTransporter);
42
+ /**
43
+ * Sends one normalized email message through the wrapped Nodemailer transporter.
44
+ *
45
+ * @param message Normalized message generated by the shared email package before provider hand-off.
46
+ * @param _context Optional transport context accepted for contract compatibility with the shared root API.
47
+ * @returns A normalized receipt containing accepted/rejected recipients, response details, and message id.
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * const transport = createNodemailerEmailTransport({ transporter });
52
+ * await transport.send(message, {});
53
+ * ```
54
+ */
55
+ send(message: NormalizedEmailMessage, _context: EmailTransportContext): Promise<EmailTransportReceipt>;
56
+ /**
57
+ * Verifies SMTP connectivity and authentication through the wrapped Nodemailer transporter.
58
+ *
59
+ * @returns A promise that resolves once Nodemailer reports the transporter is ready.
60
+ */
61
+ verify(): Promise<void>;
62
+ /**
63
+ * Closes the wrapped Nodemailer transporter.
64
+ *
65
+ * @returns A promise that resolves after the close signal has been forwarded.
66
+ */
67
+ close(): Promise<void>;
68
+ }
69
+ /**
70
+ * Wraps an existing Nodemailer transporter as an `EmailTransport` without transferring ownership.
71
+ *
72
+ * @param options Existing transporter wiring owned by the caller.
73
+ * @returns A Node-only transport implementation that can be passed directly to `EmailModule.forRoot(...)`.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * const transporter = nodemailer.createTransport({ host: 'smtp.example.com', port: 587, secure: false });
78
+ * const transport = createNodemailerEmailTransport({ transporter });
79
+ * ```
80
+ */
81
+ export declare function createNodemailerEmailTransport(options: NodemailerEmailTransportOptions): EmailTransport;
82
+ /**
83
+ * Creates a factory-owned Nodemailer SMTP transport for the explicit `@fluojs/email/node` entrypoint.
84
+ *
85
+ * @param options SMTP connection settings and an optional diagnostic label.
86
+ * @returns An {@link EmailTransportFactory} that lazily creates a Nodemailer transport and marks ownership as internal.
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * EmailModule.forRoot({
91
+ * transport: createNodemailerEmailTransportFactory({
92
+ * smtp: {
93
+ * auth: { pass: 'secret', user: 'mailer' },
94
+ * host: 'smtp.example.com',
95
+ * port: 587,
96
+ * secure: false,
97
+ * },
98
+ * }),
99
+ * verifyOnModuleInit: true,
100
+ * });
101
+ * ```
102
+ */
103
+ export declare function createNodemailerEmailTransportFactory(options: NodemailerEmailTransportFactoryOptions): EmailTransportFactory;
104
+ //# sourceMappingURL=nodemailer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodemailer.d.ts","sourceRoot":"","sources":["../../src/node/nodemailer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,IAAI,MAAM,uBAAuB,CAAC;AAC9C,OAAO,KAAK,aAAa,MAAM,+BAA+B,CAAC;AAE/D,OAAO,KAAK,EAEV,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,aAAa,CAAC;AAErB,4FAA4F;AAC5F,MAAM,MAAM,qBAAqB,GAAG,IAAI,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC;AAExE;;;;;;;GAOG;AACH,MAAM,WAAW,+BAA+B;IAC9C,iFAAiF;IACjF,WAAW,EAAE,qBAAqB,CAAC;CACpC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,sCAAsC;IACrD,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,iGAAiG;IACjG,IAAI,EAAE,aAAa,CAAC,OAAO,GAAG,MAAM,CAAC;CACtC;AAoDD;;;;;;GAMG;AACH,qBAAa,wBAAyB,YAAW,cAAc;IACjD,OAAO,CAAC,QAAQ,CAAC,WAAW;gBAAX,WAAW,EAAE,qBAAqB;IAE/D;;;;;;;;;;;;OAYG;IACG,IAAI,CAAC,OAAO,EAAE,sBAAsB,EAAE,QAAQ,EAAE,qBAAqB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAa5G;;;;OAIG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAI7B;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,+BAA+B,GAAG,cAAc,CAEvG;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,qCAAqC,CACnD,OAAO,EAAE,sCAAsC,GAC9C,qBAAqB,CAQvB"}
@@ -0,0 +1,166 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import nodemailer from 'nodemailer';
3
+
4
+ /** Node-only Nodemailer transporter type used by the explicit `@fluojs/email/node` seam. */
5
+
6
+ /**
7
+ * Options for wrapping an existing Nodemailer transporter without transferring resource ownership.
8
+ *
9
+ * @remarks
10
+ * Use this when your application already constructs and manages the Nodemailer transporter
11
+ * at the Node boundary, but you still want to satisfy the shared {@link EmailTransport}
12
+ * contract consumed by `@fluojs/email`.
13
+ */
14
+
15
+ /**
16
+ * Options for creating a factory-owned SMTP transporter behind `@fluojs/email/node`.
17
+ *
18
+ * @remarks
19
+ * The resulting factory advertises `ownsResources: true`, allowing the shared email module
20
+ * to close the Nodemailer transporter during application shutdown while keeping that behavior
21
+ * isolated to the Node-only subpath.
22
+ */
23
+
24
+ function createAddress(address) {
25
+ return address.name ? `${address.name} <${address.address}>` : address.address;
26
+ }
27
+ function createAddressList(addresses) {
28
+ if (addresses.length === 0) {
29
+ return undefined;
30
+ }
31
+ return addresses.map(address => createAddress(address));
32
+ }
33
+ function createAttachments(attachments) {
34
+ if (!attachments || attachments.length === 0) {
35
+ return undefined;
36
+ }
37
+ return attachments.map(attachment => ({
38
+ content: typeof attachment.content === 'string' ? attachment.content : Buffer.from(attachment.content.buffer, attachment.content.byteOffset, attachment.content.byteLength),
39
+ contentType: attachment.contentType,
40
+ filename: attachment.filename
41
+ }));
42
+ }
43
+ function createSendMailOptions(message) {
44
+ return {
45
+ attachments: createAttachments(message.attachments),
46
+ bcc: createAddressList(message.bcc),
47
+ cc: createAddressList(message.cc),
48
+ from: createAddress(message.from),
49
+ headers: message.headers,
50
+ html: message.html,
51
+ replyTo: createAddressList(message.replyTo),
52
+ subject: message.subject,
53
+ text: message.text,
54
+ to: createAddressList(message.to)
55
+ };
56
+ }
57
+ function normalizeAddressList(value) {
58
+ if (!Array.isArray(value)) {
59
+ return [];
60
+ }
61
+ return value.map(entry => String(entry));
62
+ }
63
+
64
+ /**
65
+ * Node-only {@link EmailTransport} adapter that forwards normalized email messages to Nodemailer.
66
+ *
67
+ * @remarks
68
+ * This adapter intentionally lives behind `@fluojs/email/node` so SMTP/Nodemailer behavior,
69
+ * verification, and connection cleanup never leak into the transport-agnostic root barrel.
70
+ */
71
+ export class NodemailerEmailTransport {
72
+ constructor(transporter) {
73
+ this.transporter = transporter;
74
+ }
75
+
76
+ /**
77
+ * Sends one normalized email message through the wrapped Nodemailer transporter.
78
+ *
79
+ * @param message Normalized message generated by the shared email package before provider hand-off.
80
+ * @param _context Optional transport context accepted for contract compatibility with the shared root API.
81
+ * @returns A normalized receipt containing accepted/rejected recipients, response details, and message id.
82
+ *
83
+ * @example
84
+ * ```ts
85
+ * const transport = createNodemailerEmailTransport({ transporter });
86
+ * await transport.send(message, {});
87
+ * ```
88
+ */
89
+ async send(message, _context) {
90
+ const receipt = await this.transporter.sendMail(createSendMailOptions(message));
91
+ return {
92
+ accepted: normalizeAddressList(receipt.accepted),
93
+ messageId: typeof receipt.messageId === 'string' ? receipt.messageId : undefined,
94
+ metadata: receipt.envelope ? {
95
+ envelope: receipt.envelope
96
+ } : undefined,
97
+ pending: normalizeAddressList(receipt.pending),
98
+ rejected: normalizeAddressList(receipt.rejected),
99
+ response: typeof receipt.response === 'string' ? receipt.response : undefined
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Verifies SMTP connectivity and authentication through the wrapped Nodemailer transporter.
105
+ *
106
+ * @returns A promise that resolves once Nodemailer reports the transporter is ready.
107
+ */
108
+ async verify() {
109
+ await this.transporter.verify();
110
+ }
111
+
112
+ /**
113
+ * Closes the wrapped Nodemailer transporter.
114
+ *
115
+ * @returns A promise that resolves after the close signal has been forwarded.
116
+ */
117
+ async close() {
118
+ this.transporter.close();
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Wraps an existing Nodemailer transporter as an `EmailTransport` without transferring ownership.
124
+ *
125
+ * @param options Existing transporter wiring owned by the caller.
126
+ * @returns A Node-only transport implementation that can be passed directly to `EmailModule.forRoot(...)`.
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * const transporter = nodemailer.createTransport({ host: 'smtp.example.com', port: 587, secure: false });
131
+ * const transport = createNodemailerEmailTransport({ transporter });
132
+ * ```
133
+ */
134
+ export function createNodemailerEmailTransport(options) {
135
+ return new NodemailerEmailTransport(options.transporter);
136
+ }
137
+
138
+ /**
139
+ * Creates a factory-owned Nodemailer SMTP transport for the explicit `@fluojs/email/node` entrypoint.
140
+ *
141
+ * @param options SMTP connection settings and an optional diagnostic label.
142
+ * @returns An {@link EmailTransportFactory} that lazily creates a Nodemailer transport and marks ownership as internal.
143
+ *
144
+ * @example
145
+ * ```ts
146
+ * EmailModule.forRoot({
147
+ * transport: createNodemailerEmailTransportFactory({
148
+ * smtp: {
149
+ * auth: { pass: 'secret', user: 'mailer' },
150
+ * host: 'smtp.example.com',
151
+ * port: 587,
152
+ * secure: false,
153
+ * },
154
+ * }),
155
+ * verifyOnModuleInit: true,
156
+ * });
157
+ * ```
158
+ */
159
+ export function createNodemailerEmailTransportFactory(options) {
160
+ const kind = options.kind?.trim() || 'nodemailer-smtp';
161
+ return {
162
+ create: async () => new NodemailerEmailTransport(nodemailer.createTransport(options.smtp)),
163
+ kind,
164
+ ownsResources: true
165
+ };
166
+ }
package/dist/node.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './node/node.js';
2
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
package/dist/node.js ADDED
@@ -0,0 +1 @@
1
+ export * from './node/node.js';
@@ -0,0 +1,4 @@
1
+ export { DEFAULT_EMAIL_QUEUE_WORKER_OPTIONS } from './constants.js';
2
+ export { createEmailNotificationsQueueAdapter } from './queue.js';
3
+ export type { EmailQueueWorkerOptions } from './types.js';
4
+ //# sourceMappingURL=queue-entry.d.ts.map